Subject: Re: bug#68235: 29.1.90; Switching tabs stops following process output in selected window

From: martin rudalics <[email-masked]>
Date: Wed, 6 Mar 2024 11:19:51 +0100

> In addition to the previous patch I also have more questions:
 >
 > 1. window-kept-windows-functions should be announced in etc/NEWS?

Maybe, once we fixed all bugs and know how to make good use of it.

 > 2. window-kept-windows-functions is called too often.
 > Most of the calls contain just the minibuffer:
 >
 >     ((#<window 4 on  *Minibuf-0*> #<buffer  *Minibuf-0*> 1 1))
 >
 > Is it possible not to include the minibuffer window?

Yes.  Emacs master never deletes a minibuffer window unless it's about
to delete its frame.

 > So when most of the time this list of kept windows will be empty,
 > then maybe better to not call the hook at all?

But earlier you said "Running the hook with an empty list of windows
makes sense as well."  So it's up to you.

 > 3. Very often the message inserted by the patch that I posted
 > are quite useless because they look like this:
 >
 >     This window displayed the buffer #<killed buffer>.
 >
 > This would be much more informative:
 >
 >     This window displayed the buffer *Help*.
 >
 > Maybe 'buffer-last-name' could help to achieve this?

I tried to implement it.  Tested with

(let ((buffer (get-buffer-create "*foo*")))
   (y-or-n-p (format "current %s last %s"
		    (buffer-name buffer) (buffer-last-name buffer)))
   (with-current-buffer buffer
     (rename-buffer "*bar*"))
   (y-or-n-p (format "current %s last %s"
		    (buffer-name buffer) (buffer-last-name buffer)))
   (kill-buffer buffer)
   (y-or-n-p (format "current %s last %s"
		    (buffer-name buffer) (buffer-last-name buffer))))

 > 4. I don't understand this part, but maybe this is already correct:
 >
 >        /* Scan dead buffer windows.  */
 >        if (!NILP (Vwindow_kept_windows_functions))
 > 	for (; CONSP (dead_windows); dead_windows = XCDR (dead_windows))
 > 	  {
 > 	    window = XCAR (dead_windows);
 > 	    if (WINDOW_LIVE_P (window) && !EQ (window, FRAME_ROOT_WINDOW (f)))
 > 	      delete_deletable_window (window);
 > 	  }
 >
 > Should it be if(NILP (Vwindow_kept_windows_functions)) instead?

It should.  Thanks for catching it.

 > However, this already works correctly in my tests.

Because 'set-window-configuration' does not try to delete a window with
a dead buffer unless that window was dedicated to its buffer.  That's
the way it was coded back in 2011.  'window-state-put' OTOH deletes
such a window even if it was not dedicated to its buffer.  I now made
'window-state-put' behave like 'set-window-configuration' in this
regard.

Have a look at the attached patch.

martin

diff --git a/doc/lispref/windows.texi b/doc/lispref/windows.texi
index fe3dc573df5..b6937b7fd48 100644
--- a/doc/lispref/windows.texi
+++ b/doc/lispref/windows.texi
@@ -6264,15 +6264,15 @@ Window Configurations
 @code{minibuffer-selected-window}.  In this case, the function returns
 @code{nil}.  Otherwise, it returns @code{t}.
 
-If the buffer of a window of @var{configuration} has been killed since
-@var{configuration} was made, that window is, as a rule, removed from
-the restored configuration.  However, if that window is the last window
-remaining in the restored configuration, another live buffer is shown in
-it.  Also, if the variable @var{window-kept-windows-functions} is
-non-@code{nil}, any window whose buffer is now dead is not deleted.
-Rather, this function will show another live buffer in that window and
-include an entry for that window when calling any function in
-@var{window-kept-windows-functions} (@pxref{Window Hooks}).
+If this function tries to restore a non-minibuffer window whose buffer
+was killed since @var{configuration} was made, it will proceed as
+follows: If the abnormal hook @code{window-kept-windows-functions} is
+@code{nil} and the window is dedicated to its buffer, it will try to
+delete that window. Otherwise, or if it cannot delete the window, it
+will show another live buffer in it.
+
+This function runs the abnormal hook @var{window-kept-windows-functions}
+(@pxref{Window Hooks}).
 
 Here is a way of using this function to get the same effect as
 @code{save-window-excursion}:
@@ -6361,19 +6361,20 @@ Window Configurations
 frame before putting @var{state} into it.  If @var{window} is @code{nil},
 it puts the window state into a new window.
 
-If the buffer of any window recorded in @var{state} has been killed
-since @var{state} was made, that window is, as a rule, not restored.
-However, if that window is the only window in @var{state}, another live
-buffer will be shown in it.  Also, if the variable
-@var{window-kept-windows-functions} is non-@code{nil}, any window whose
-buffer is now dead is restored.  This function will show another live
-buffer in it and include an entry for that window when calling a
-function in @var{window-kept-windows-functions} (@pxref{Window Hooks}).
+If this function tries to restore a non-minibuffer window whose buffer
+was killed since @var{state} was made, it will proceed as follows: If
+the abnormal hook @code{window-kept-windows-functions} is @code{nil} and
+the window is dedicated to its buffer, it will try to delete that
+window. Otherwise, or if it cannot delete the window, it will show
+another live buffer in it.
 
 If the optional argument @var{ignore} is non-@code{nil}, it means to ignore
 minimum window sizes and fixed-size restrictions.  If @var{ignore}
 is @code{safe}, this means windows can get as small as one line
 and/or two columns.
+
+This function runs the abnormal hook @var{window-kept-windows-functions}
+(@pxref{Window Hooks}).
 @end defun
 
 The functions @code{window-state-get} and @code{window-state-put} also
@@ -6641,7 +6642,7 @@ Window Hooks
    This variable holds a list of functions that Emacs will call after
 restoring a window configuration via @code{set-window-configuration} or
 state via @code{window-state-put} (@pxref{Window Configurations}).  When
-the value of this variable is non-@code{nil}, these functions will not
+the value of this variable is non-@code{nil}, these functions will never
 delete any window whose buffer has been killed since the corresponding
 configuration or state was saved, but show some live buffer in it.
 
diff --git a/lisp/window.el b/lisp/window.el
index 29336f573f8..11ef490ec68 100644
--- a/lisp/window.el
+++ b/lisp/window.el
@@ -6393,7 +6393,9 @@ window--state-put-2
 	    ;; save the window with the intention of deleting it later
 	    ;; if possible.
 	    (switch-to-prev-buffer window)
-	    (if window-kept-windows-functions
+	    (unless (window-minibuffer-p window)
+	      (cond
+	       (window-kept-windows-functions
 		(let* ((start (cdr (assq 'start state)))
 		       ;; Handle both - marker positions from writable
 		       ;; states and markers from non-writable states.
@@ -6401,12 +6403,14 @@ window--state-put-2
 				      (marker-last-position start)
 				    start))
 		       (point (cdr (assq 'point state)))
-		       (point-pos (if (markerp point)
-				      (marker-last-position point)
-				    point)))
-		  (push (list window old-buffer-or-name start-pos point-pos)
-			window-state-put-kept-windows))
-	      (push window window-state-put-stale-windows))))))))
+			 (point-pos (if (markerp point)
+					(marker-last-position point)
+				      point)))
+		    (push (list window old-buffer-or-name start-pos point-pos)
+			  window-state-put-kept-windows)))
+	       ((window-dedicated-p window)
+		(push window window-state-put-stale-windows)))
+	      (set-window-dedicated-p window nil))))))))
 
 (defun window-state-put (state &optional window ignore)
   "Put window state STATE into WINDOW.
@@ -6421,16 +6425,22 @@ window-state-put
 windows can get as small as `window-safe-min-height' and
 `window-safe-min-width'.
 
-If the abnormal hook `window-kept-windows-functions' is non-nil,
-do not delete any windows saved by STATE whose buffers were
-deleted since STATE was saved.  Rather, show some live buffer in
-them and call the functions in `window-kept-windows-functions'
-with a list of two arguments: the frame where STATE was put and a
-list of entries for each such window.  Each entry contains four
-elements - the window, its old buffer and the last positions of
-`window-start' and `window-point' for the buffer in that window.
-Always check the window for liveness because another function run
-by this hook may have deleted it."
+If this function tries to restore a non-minibuffer window whose buffer
+was killed since STATE was made, it will proceed as follows:
+
+- If the abnormal hook `window-kept-windows-functions' is nil and the
+  window is dedicated to its buffer, it will try to delete that window.
+
+- Otherwise, or if it cannot delete the window, it will show another
+  buffer in it.
+
+Call the functions in `window-kept-windows-functions' with a list of two
+arguments: the frame where STATE was put and a list of entries for each
+window whose buffer has been killed since STATE was made.  Each entry
+contains four elements - the window, its old buffer and the last
+positions of `window-start' and `window-point' for the buffer in that
+window.  Always check the window for liveness because another function
+run by this hook may have deleted it."
   (setq window-state-put-stale-windows nil)
   (setq window-state-put-kept-windows nil)
 
diff --git a/src/buffer.c b/src/buffer.c
index 126f3eb055a..e8daa93c2d9 100644
--- a/src/buffer.c
+++ b/src/buffer.c
@@ -327,6 +327,11 @@ bset_name (struct buffer *b, Lisp_Object val)
   b->name_ = val;
 }
 static void
+bset_last_name (struct buffer *b, Lisp_Object val)
+{
+  b->last_name_ = val;
+}
+static void
 bset_overwrite_mode (struct buffer *b, Lisp_Object val)
 {
   b->overwrite_mode_ = val;
@@ -647,6 +652,7 @@ DEFUN ("get-buffer-create", Fget_buffer_create, Sget_buffer_create, 1, 2, 0,
   name = Fcopy_sequence (buffer_or_name);
   set_string_intervals (name, NULL);
   bset_name (b, name);
+  bset_last_name (b, name);
 
   b->inhibit_buffer_hooks = !NILP (inhibit_buffer_hooks);
   bset_undo_list (b, SREF (name, 0) != ' ' ? Qnil : Qt);
@@ -866,6 +872,7 @@ DEFUN ("make-indirect-buffer", Fmake_indirect_buffer, Smake_indirect_buffer,
   name = Fcopy_sequence (name);
   set_string_intervals (name, NULL);
   bset_name (b, name);
+  bset_last_name (b, name);
 
   /* An indirect buffer shares undo list of its base (Bug#18180).  */
   bset_undo_list (b, BVAR (b->base_buffer, undo_list));
@@ -1282,6 +1289,16 @@ DEFUN ("buffer-name", Fbuffer_name, Sbuffer_name, 0, 1, 0,
   return BVAR (decode_buffer (buffer), name);
 }
 
+DEFUN ("buffer-last-name", Fbuffer_last_name, Sbuffer_last_name, 0, 1, 0,
+       doc: /* Return last name of BUFFER, as a string.
+BUFFER defaults to the current buffer.
+
+This is the last name of BUFFER before it has been renamed or killed. */)
+  (Lisp_Object buffer)
+{
+  return BVAR (decode_buffer (buffer), last_name);
+}
+
 DEFUN ("buffer-file-name", Fbuffer_file_name, Sbuffer_file_name, 0, 1, 0,
        doc: /* Return name of file BUFFER is visiting, or nil if none.
 No argument or nil as argument means use the current buffer.  */)
@@ -1652,6 +1669,7 @@ DEFUN ("rename-buffer", Frename_buffer, Srename_buffer, 1, 2,
   (register Lisp_Object newname, Lisp_Object unique)
 {
   register Lisp_Object tem, buf;
+  Lisp_Object oldname = BVAR (current_buffer, name);
   Lisp_Object requestedname = newname;
 
   CHECK_STRING (newname);
@@ -1669,12 +1687,12 @@ DEFUN ("rename-buffer", Frename_buffer, Srename_buffer, 1, 2,
       if (NILP (unique) && XBUFFER (tem) == current_buffer)
 	return BVAR (current_buffer, name);
       if (!NILP (unique))
-	newname = Fgenerate_new_buffer_name (newname,
-	                                     BVAR (current_buffer, name));
+	newname = Fgenerate_new_buffer_name (newname, oldname);
       else
 	error ("Buffer name `%s' is in use", SDATA (newname));
     }
 
+  bset_last_name (current_buffer, oldname);
   bset_name (current_buffer, newname);
 
   /* Catch redisplay's attention.  Unless we do this, the mode lines for
@@ -2087,6 +2105,7 @@ DEFUN ("kill-buffer", Fkill_buffer, Skill_buffer, 0, 1, "bKill buffer: ",
      This gets rid of them for certain.  */
   reset_buffer_local_variables (b, 1);
 
+  bset_last_name (b, BVAR (b, name));
   bset_name (b, Qnil);
 
   block_input ();
@@ -4658,6 +4677,7 @@ init_buffer_once (void)
   /* These used to be stuck at 0 by default, but now that the all-zero value
      means Qnil, we have to initialize them explicitly.  */
   bset_name (&buffer_local_flags, make_fixnum (0));
+  bset_last_name (&buffer_local_flags, make_fixnum (0));
   bset_mark (&buffer_local_flags, make_fixnum (0));
   bset_local_var_alist (&buffer_local_flags, make_fixnum (0));
   bset_keymap (&buffer_local_flags, make_fixnum (0));
@@ -6018,6 +6038,7 @@ Functions (implicitly) running this hook are `get-buffer-create',
   defsubr (&Smake_indirect_buffer);
   defsubr (&Sgenerate_new_buffer_name);
   defsubr (&Sbuffer_name);
+  defsubr (&Sbuffer_last_name);
   defsubr (&Sbuffer_file_name);
   defsubr (&Sbuffer_base_buffer);
   defsubr (&Sbuffer_local_value);
diff --git a/src/buffer.h b/src/buffer.h
index 87ba2802b39..bbe1aeff668 100644
--- a/src/buffer.h
+++ b/src/buffer.h
@@ -309,6 +309,9 @@ #define BVAR(buf, field) ((buf)->field ## _)
   /* The name of this buffer.  */
   Lisp_Object name_;
 
+  /* The last name of this buffer before it was renamed or killed.  */
+  Lisp_Object last_name_;
+
   /* The name of the file visited in this buffer, or nil.  */
   Lisp_Object filename_;
 
diff --git a/src/window.c b/src/window.c
index ea761fad8bc..c0fadbdff43 100644
--- a/src/window.c
+++ b/src/window.c
@@ -7109,11 +7109,14 @@ DEFUN ("set-window-configuration", Fset_window_configuration,
 the mini-window of the frame doesn't get set to the corresponding element
 of CONFIGURATION.
 
-Normally, this function will try to delete any dead window in
-CONFIGURATION whose buffer has been deleted since CONFIGURATION was
-made.  However, if the abnormal hook `window-kept-windows-functions' is
-non-nil, it will preserve such a window in the restored layout and show
-another buffer in it.
+If this function tries to restore a non-minibuffer window whose buffer
+was killed since CONFIGURATION was made, it will proceed as follows:
+
+- If the abnormal hook `window-kept-windows-functions' is nil and the
+  window is dedicated to its buffer, it will try to delete that window.
+
+- Otherwise, or if it cannot delete the window, it will show another
+  buffer in it.
 
 After restoring the frame layout, this function runs the abnormal hook
 `window-kept-windows-functions' with two arguments - the frame whose
@@ -7378,7 +7381,7 @@ DEFUN ("set-window-configuration", Fset_window_configuration,
 		   BUF_PT (XBUFFER (w->contents)),
 		   BUF_PT_BYTE (XBUFFER (w->contents)));
 	      w->start_at_line_beg = true;
-	      if (!NILP (Vwindow_kept_windows_functions))
+	      if (!NILP (Vwindow_kept_windows_functions) && !MINI_WINDOW_P (w))
 		kept_windows = Fcons (list4 (window, p->buffer,
 					     Fmarker_last_position (p->start),
 					     Fmarker_last_position (p->pointm)),
@@ -7398,16 +7401,20 @@ DEFUN ("set-window-configuration", Fset_window_configuration,
 	      set_marker_restricted_both (w->pointm, w->contents, 0, 0);
 	      set_marker_restricted_both (w->old_pointm, w->contents, 0, 0);
 	      w->start_at_line_beg = true;
-	      if (!NILP (w->dedicated))
-		/* Record this window as dead.  */
-		dead_windows = Fcons (window, dead_windows);
-	      /* Make sure window is no more dedicated.  */
-	      wset_dedicated (w, Qnil);
-	      if (!NILP (Vwindow_kept_windows_functions))
-		kept_windows = Fcons (list4 (window, p->buffer,
-					     Fmarker_last_position (p->start),
-					     Fmarker_last_position (p->pointm)),
-				      kept_windows);
+	      if (!MINI_WINDOW_P (w))
+		{
+		  if (!NILP (Vwindow_kept_windows_functions))
+		    kept_windows
+		      = Fcons (list4 (window, p->buffer,
+				      Fmarker_last_position (p->start),
+				      Fmarker_last_position (p->pointm)),
+			       kept_windows);
+		  else if (!NILP (w->dedicated))
+		    /* Try to delete this window later.  */
+		    dead_windows = Fcons (window, dead_windows);
+		  /* Make sure window is no more dedicated.  */
+		  wset_dedicated (w, Qnil);
+		}
 	    }
 	}
 
@@ -7459,7 +7466,7 @@ DEFUN ("set-window-configuration", Fset_window_configuration,
       unblock_input ();
 
       /* Scan dead buffer windows.  */
-      if (!NILP (Vwindow_kept_windows_functions))
+      if (NILP (Vwindow_kept_windows_functions))
 	for (; CONSP (dead_windows); dead_windows = XCDR (dead_windows))
 	  {
 	    window = XCAR (dead_windows);