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

From: martin rudalics <[email-masked]>
Date: Sat, 20 Jan 2024 10:44:51 +0100

> This looks promising.

I attach a patch.  Note that in my Emacs, window configurations are much
more lightweight so I cannot reliably test it.  You would have to do all
the testing yourself.

 > One thing I don't understand is how to get
 > the previously visited file name from this list of four entries?

But it's still there in the dead buffer (the only things that can get
collected as long as a window is in a tab are the previous and next
buffers shown in that window).  Try the scenario below, using some
suitable file as argument to 'find-file-noselect'.

(defun foo (frame windows)
   (while windows
     (let* ((quad (car windows))
	   (window (car quad))
	   (buffer (find-file-noselect
		    (buffer-file-name (nth 1 quad)))))
       (when buffer
	(set-window-buffer window buffer)
	(set-window-point window (nth 3 quad))
	(set-window-start window (nth 2 quad) t)))
     (setq windows (cdr windows))))

(add-hook 'post-set-window-configuration-functions 'foo)

(let ((window (selected-window))
       (buffer (pop-to-buffer
	       (find-file-noselect "...")))
       (window-1 (split-window))
       (window-2 (split-window nil nil t))
       configuration)
   (set-window-point window-1 5000)
   (set-window-point window-2 10000)
   (setq configuration (current-window-configuration))
   (y-or-n-p "Configuration saved ...")
   (delete-other-windows window)
   (kill-buffer buffer)
   (y-or-n-p "Configuration reset ...")
   (set-window-configuration configuration nil nil t)
   (message "Configuration restored"))

Note that window point and start are stored as positions and not as
markers which means that if you modify the buffer after the call of
'current-window-configuration', they may not be accurate any more.

One could try to get their last position before the buffer was deleted
but that would mean to give ‘marker-position’ an extra argument to omit
the

   if (XMARKER (marker)->buffer)

check with some "use at your own risk" caveats ('marker-buffer' at the
same time would still have to return nil).

martin
diff --git a/src/keyboard.c b/src/keyboard.c
index e1d738dd6ef..b6ba3c57ff8 100644
--- a/src/keyboard.c
+++ b/src/keyboard.c
@@ -2281,7 +2281,7 @@ read_char_help_form_unwind (void)
   Lisp_Object window_config = XCAR (help_form_saved_window_configs);
   help_form_saved_window_configs = XCDR (help_form_saved_window_configs);
   if (!NILP (window_config))
-    Fset_window_configuration (window_config, Qnil, Qnil);
+    Fset_window_configuration (window_config, Qnil, Qnil, Qnil);
 }
 
 #define STOP_POLLING					\
diff --git a/src/window.c b/src/window.c
index 3a54f7ce7b1..c33b987c24f 100644
--- a/src/window.c
+++ b/src/window.c
@@ -7037,6 +7037,7 @@ DEFUN ("move-to-window-line", Fmove_to_window_line, Smove_to_window_line,
   union vectorlike_header header;
 
   Lisp_Object window, buffer, start, pointm, old_pointm;
+  Lisp_Object start_pos, pointm_pos;
   Lisp_Object pixel_left, pixel_top, pixel_height, pixel_width;
   Lisp_Object left_col, top_line, total_cols, total_lines;
   Lisp_Object normal_cols, normal_lines;
@@ -7079,7 +7080,7 @@ DEFUN ("window-configuration-frame", Fwindow_configuration_frame, Swindow_config
 }
 
 DEFUN ("set-window-configuration", Fset_window_configuration,
-       Sset_window_configuration, 1, 3, 0,
+       Sset_window_configuration, 1, 4, 0,
        doc: /* Set the configuration of windows and buffers as specified by CONFIGURATION.
 CONFIGURATION must be a value previously returned
 by `current-window-configuration' (which see).
@@ -7090,16 +7091,35 @@ 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 KEEP-WINDOWS is non-nil, it will preserve such a
+window in the restored layout and show another buffer in it.
+
+After restoring the frame layout, this function runs the abnormal hook
+'post-set-window-configuration-functions' with two arguments - the frame
+whose layout is has restored and, provided KEEP-WINDOWS is non-nil, a
+list of entries for each window whose buffer has been found dead when it
+tried to restore CONFIGURATION: Each entry is a list of four elements
+<window, buffer, start, point> where `window' denotes the window whose
+buffer was found dead, `buffer' denotes the dead buffer, and `start' and
+`point' denote the positions of `window-start' and `window-point' of
+that window at the time CONFIGURATION was made. Note that these
+positions are no markers and may be no more accurate if the buffer has
+been modified afterwards.  If KEEP-WINDOWS is nil, the second argument
+is nil.
+
 If CONFIGURATION was made from a frame that is now deleted,
 only frame-independent values can be restored.  In this case,
 the return value is nil.  Otherwise the value is t.  */)
   (Lisp_Object configuration, Lisp_Object dont_set_frame,
-   Lisp_Object dont_set_miniwindow)
+   Lisp_Object dont_set_miniwindow, Lisp_Object keep_windows)
 {
   register struct save_window_data *data;
   struct Lisp_Vector *saved_windows;
   Lisp_Object new_current_buffer;
   Lisp_Object frame;
+  Lisp_Object kept_windows = Qnil;
   Lisp_Object old_frame = selected_frame;
   struct frame *f;
   ptrdiff_t old_point = -1;
@@ -7340,6 +7360,10 @@ 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 (keep_windows))
+		kept_windows = Fcons (list4 (window, p->buffer,
+					     p->start_pos, p->pointm_pos),
+				      kept_windows);
 	    }
 	  else if (!NILP (w->start))
 	    /* Leaf window has no live buffer, get one.  */
@@ -7360,6 +7384,10 @@ DEFUN ("set-window-configuration", Fset_window_configuration,
 		dead_windows = Fcons (window, dead_windows);
 	      /* Make sure window is no more dedicated.  */
 	      wset_dedicated (w, Qnil);
+	      if (!NILP (keep_windows))
+		kept_windows = Fcons (list4 (window, p->buffer,
+					     p->start_pos, p->pointm_pos),
+				      kept_windows);
 	    }
 	}
 
@@ -7411,12 +7439,13 @@ DEFUN ("set-window-configuration", Fset_window_configuration,
       unblock_input ();
 
       /* Scan dead buffer windows.  */
-      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);
-	}
+      if (NILP (keep_windows))
+	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);
+	  }
 
       /* Record the selected window's buffer here.  The window should
 	 already be the selected one from the call above.  */
@@ -7463,6 +7492,11 @@ DEFUN ("set-window-configuration", Fset_window_configuration,
   minibuf_selected_window = data->minibuf_selected_window;
 
   SAFE_FREE ();
+
+  if (!NILP (Vrun_hooks) && !NILP (Vpost_set_window_configuration_functions))
+    run_hook_with_args_2 (Qpost_set_window_configuration_functions, frame,
+			  kept_windows);
+
   return FRAME_LIVE_P (f) ? Qt : Qnil;
 }
 
@@ -7472,12 +7506,12 @@ restore_window_configuration (Lisp_Object configuration)
   if (CONSP (configuration))
     Fset_window_configuration (XCAR (configuration),
 			       Fcar_safe (XCDR (configuration)),
-			       Fcar_safe (Fcdr_safe (XCDR (configuration))));
+			       Fcar_safe (Fcdr_safe (XCDR (configuration))),
+			       Qnil);
   else
-    Fset_window_configuration (configuration, Qnil, Qnil);
+    Fset_window_configuration (configuration, Qnil, Qnil, Qnil);
 }
 
-
 /* If WINDOW is an internal window, recursively delete all child windows
    reachable via the next and contents slots of WINDOW.  Otherwise setup
    WINDOW to not show any buffer.  */
@@ -7685,16 +7719,24 @@ save_window_save (Lisp_Object window, struct Lisp_Vector *vector, ptrdiff_t i)
 	     is the selected window, then get the value of point from
 	     the buffer; pointm is garbage in the selected window.  */
 	  if (EQ (window, selected_window))
-	    p->pointm = build_marker (XBUFFER (w->contents),
-				      BUF_PT (XBUFFER (w->contents)),
-				      BUF_PT_BYTE (XBUFFER (w->contents)));
+	    {
+	      p->pointm = build_marker (XBUFFER (w->contents),
+					BUF_PT (XBUFFER (w->contents)),
+					BUF_PT_BYTE (XBUFFER (w->contents)));
+	      p->pointm_pos = make_fixnum (BUF_PT (XBUFFER (w->contents)));
+	    }
 	  else
-	    p->pointm = Fcopy_marker (w->pointm, Qnil);
+	    {
+	      p->pointm = Fcopy_marker (w->pointm, Qnil);
+	      p->pointm_pos = make_fixnum (marker_position (w->pointm));
+	    }
+
 	  p->old_pointm = Fcopy_marker (w->old_pointm, Qnil);
 	  XMARKER (p->pointm)->insertion_type = window_point_insertion_type;
 	  XMARKER (p->old_pointm)->insertion_type = window_point_insertion_type;
 
 	  p->start = Fcopy_marker (w->start, Qnil);
+	  p->start_pos = make_fixnum (marker_position (w->start));
 	  p->start_at_line_beg = w->start_at_line_beg ? Qt : Qnil;
 	}
       else
@@ -8460,6 +8502,8 @@ syms_of_window (void)
   DEFSYM (Qheader_line_format, "header-line-format");
   DEFSYM (Qtab_line_format, "tab-line-format");
   DEFSYM (Qno_other_window, "no-other-window");
+  DEFSYM (Qpost_set_window_configuration_functions,
+	  "post-set-window-configuration-functions");
 
   DEFVAR_LISP ("temp-buffer-show-function", Vtemp_buffer_show_function,
 	       doc: /* Non-nil means call as function to display a help buffer.
@@ -8617,6 +8661,21 @@ syms_of_window (void)
 call is performed with the frame temporarily selected.  */);
   Vwindow_configuration_change_hook = Qnil;
 
+  DEFVAR_LISP ("post-set-window-configuration-functions",
+	       Vpost_set_window_configuration_functions,
+	       doc: /* Functions called after restoring a window configuration.
+The value should be a list of functions that take two arguments.
+
+This function is called by `set-window-configuration' after it has
+restored the layout of a frame.  The first argument specifies the frame
+whose configuration has been restored.  The second argument, if non-nil,
+specifies a list of entries for each window whose buffer has been found
+dead at the time 'set-window-configuration' tried to restore it in that
+window.  Each entry is a list of four values - the window whose buffer
+was found dead, the dead buffer, and the positions of start and point of
+the buffer in that window.  */);
+  Vpost_set_window_configuration_functions = Qnil;
+
   DEFVAR_LISP ("recenter-redisplay", Vrecenter_redisplay,
 	       doc: /* Non-nil means `recenter' redraws entire frame.
 If this option is non-nil, then the `recenter' command with a nil