]> git.eshelyaron.com Git - emacs.git/commitdiff
Fix 'set-window-configuration' and 'window-state-put'
authorMartin Rudalics <rudalics@gmx.at>
Mon, 4 Mar 2024 09:33:49 +0000 (10:33 +0100)
committerEshel Yaron <me@eshelyaron.com>
Tue, 5 Mar 2024 15:33:35 +0000 (16:33 +0100)
Fix some bugs with 'window-state-put' (Bug#69093).  Add new
hook 'window-kept-windows-functions' (Bug#68235).

* doc/lispref/windows.texi (Window Configurations): Mention
'window-kept-windows-functions'.
(Window Hooks): Describe new abnormal hook
'window-kept-windows-functions'.
* src/marker.c (Fmarker_last_position): New function to return
the last position of a marker even if its buffer is now dead.
* src/window.c (Fset_window_configuration): If
'window-kept-windows-functions' is non-nil, do not delete any
window whose buffer is now dead but remember all such windows in
a list to pass to 'window-kept-windows-functions'.  Run
'window-kept-windows-functions' if it is non-nil.
(Vwindow_kept_windows_functions): New abnormal hook run by
Fset_window_configuration and 'window-state-put' with two
arguments - the frame whose configuration is restored and a list
of entries for each window whose buffer was found dead during
restoration.  Each entry is a list of four elements, the window,
the dead buffer, and the last know positions of the start and
point of that window.
* lisp/window.el (window-state-put-kept-windows)
(window-state-put-selected-window): New variables.
(window--state-put-2): Make sure buffer is live before restoring
its state.  Set 'window-state-put-selected-window' to state's
selected window.  If 'window-kept-windows-functions' is non-nil,
do not delete any windows whose buffer is found dead but
remember all such windows in a list to pass to
'window-kept-windows-functions'.
(window-state-put): Run 'window-kept-windows-functions' if it is
non-nil.  Select window recorded in
'window-state-put-selected-window'.

(cherry picked from commit 912e37b811107768e0cb3bc95184177f817dbdb2)

doc/lispref/windows.texi
lisp/window.el
src/marker.c
src/window.c

index f14e74bc785f07583c3adf56b33198e61cd54553..fe3dc573df596e65a4422bd7cfd4a97098aff253 100644 (file)
@@ -6266,9 +6266,13 @@ this function does is to restore the value of the variable
 
 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.
+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}).
 
 Here is a way of using this function to get the same effect as
 @code{save-window-excursion}:
@@ -6357,6 +6361,15 @@ a live window, it is replaced by a new live window created on the same
 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 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
@@ -6623,6 +6636,27 @@ Lock fontification function, which will be called whenever parts of a
 buffer are (re)fontified because a window was scrolled or its size
 changed.  @xref{Other Font Lock Variables}.
 
+@cindex window kept windows functions
+@defvar window-kept-windows-functions
+   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
+delete any window whose buffer has been killed since the corresponding
+configuration or state was saved, but show some live buffer in it.
+
+The value should be a list of functions that take two arguments.  The
+first argument specifies the frame whose windows have been restored.
+The second argument specifies a list of entries for each window whose
+buffer has been found dead at the time @code{set-window-configuration}
+or @code{window-state-put} tried to restore it.  Each entry is a list of
+four values - the window whose buffer was found dead, the dead buffer,
+and the last known positions of start and point of the buffer in that
+window.  Any function run by this hook should check that the window is
+live since another function run by this hook may have deleted it in the
+meantime.
+@end defvar
+
 @cindex window change functions
    The remainder of this section covers six hooks that are called
 during redisplay provided a significant, non-scrolling change of a
index 6a279431f91fe96e4b18a5c85b5b18907b5ac44c..76d1511fb6daff05ac8b94461f93e3c8b5d4321f 100644 (file)
@@ -6180,6 +6180,12 @@ value can be also stored on disk and read back in a new session."
 (defvar window-state-put-stale-windows nil
   "Helper variable for `window-state-put'.")
 
+(defvar window-state-put-kept-windows nil
+  "Helper variable for `window-state-put'.")
+
+(defvar window-state-put-selected-window nil
+  "Helper variable for `window-state-put'.")
+
 (defun window--state-put-1 (state &optional window ignore totals pixelwise)
   "Helper function for `window-state-put'."
   (let ((type (car state)))
@@ -6284,9 +6290,10 @@ value can be also stored on disk and read back in a new session."
          (set-window-parameter window (car parameter) (cdr parameter))))
       ;; Process buffer related state.
       (when state
-       (let ((buffer (get-buffer (car state)))
-             (state (cdr state)))
-         (if buffer
+       (let* ((old-buffer-or-name (car state))
+              (buffer (get-buffer old-buffer-or-name))
+              (state (cdr state)))
+         (if (buffer-live-p buffer)
              (with-current-buffer buffer
                (set-window-buffer window buffer)
                (set-window-hscroll window (cdr (assq 'hscroll state)))
@@ -6354,7 +6361,18 @@ value can be also stored on disk and read back in a new session."
                  (set-window-point window (cdr (assq 'point state))))
                ;; Select window if it's the selected one.
                (when (cdr (assq 'selected state))
-                 (select-window window))
+                 ;; This used to call 'select-window' which, however,
+                 ;; can be partially undone because the current buffer
+                 ;; may subsequently change twice: When leaving the
+                 ;; present 'with-current-buffer' and when leaving the
+                 ;; containing 'with-temp-buffer' form (Bug#69093).
+                 ;; 'window-state-put-selected-window' should now work
+                 ;; around that bug but we leave this 'select-window'
+                 ;; in since some code run before the part that fixed
+                 ;; it might still refer to this window as the selected
+                 ;; one.
+                 (select-window window)
+                 (setq window-state-put-selected-window window))
                 (set-window-next-buffers
                  window
                  (delq nil (mapcar (lambda (buffer)
@@ -6381,7 +6399,20 @@ value can be also stored on disk and read back in a new session."
            ;; save the window with the intention of deleting it later
            ;; if possible.
            (switch-to-prev-buffer window)
-           (push window window-state-put-stale-windows)))))))
+           (if window-kept-windows-functions
+               (let* ((start (cdr (assq 'start state)))
+                      ;; Handle both - marker positions from writable
+                      ;; states and markers from non-writable states.
+                      (start-pos (if (markerp start)
+                                     (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))))))))
 
 (defun window-state-put (state &optional window ignore)
   "Put window state STATE into WINDOW.
@@ -6394,8 +6425,20 @@ If WINDOW is nil, create a new window before putting STATE into it.
 Optional argument IGNORE non-nil means ignore minimum window
 sizes and fixed size restrictions.  IGNORE equal `safe' means
 windows can get as small as `window-safe-min-height' and
-`window-safe-min-width'."
+`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."
   (setq window-state-put-stale-windows nil)
+  (setq window-state-put-kept-windows nil)
 
   ;; When WINDOW is internal or nil, reduce it to a live one,
   ;; then create a new window on the same frame to put STATE into.
@@ -6488,6 +6531,7 @@ windows can get as small as `window-safe-min-height' and
        (error "Window %s too small to accommodate state" window)
       (setq state (cdr state))
       (setq window-state-put-list nil)
+      (setq window-state-put-selected-window nil)
       ;; Work on the windows of a temporary buffer to make sure that
       ;; splitting proceeds regardless of any buffer local values of
       ;; `window-size-fixed'.  Release that buffer after the buffers of
@@ -6496,14 +6540,21 @@ windows can get as small as `window-safe-min-height' and
        (set-window-buffer window (current-buffer))
        (window--state-put-1 state window nil totals pixelwise)
        (window--state-put-2 ignore pixelwise))
+      (when (window-live-p window-state-put-selected-window)
+       (select-window window-state-put-selected-window))
       (while window-state-put-stale-windows
        (let ((window (pop window-state-put-stale-windows)))
-          ;; Avoid that 'window-deletable-p' throws an error if window
+         ;; Avoid that 'window-deletable-p' throws an error if window
           ;; was already deleted when exiting 'with-temp-buffer' above
           ;; (Bug#54028).
          (when (and (window-valid-p window)
                      (eq (window-deletable-p window) t))
            (delete-window window))))
+      (when window-kept-windows-functions
+       (run-hook-with-args
+        'window-kept-windows-functions
+        frame window-state-put-kept-windows)
+       (setq window-state-put-kept-windows nil))
       (window--check frame))))
 
 (defun window-state-buffers (state)
index 1559dd52719eea71a305eebe17e4709578bbed43..2abc951fc76dff271bba0f1fce3ff4bdb6aaeb0c 100644 (file)
@@ -463,6 +463,18 @@ DEFUN ("marker-position", Fmarker_position, Smarker_position, 1, 1, 0,
   return Qnil;
 }
 
+DEFUN ("marker-last-position", Fmarker_last_position, Smarker_last_position, 1, 1, 0,
+       doc: /* Return last position of MARKER in its buffer.
+This is like `marker-position' with one exception:  If the buffer of
+MARKER is dead, it returns the last position of MARKER in that buffer
+before it was killed.  */)
+  (Lisp_Object marker)
+{
+  CHECK_MARKER (marker);
+
+  return make_fixnum (XMARKER (marker)->charpos);
+}
+
 /* Change M so it points to B at CHARPOS and BYTEPOS.  */
 
 static void
@@ -830,6 +842,7 @@ void
 syms_of_marker (void)
 {
   defsubr (&Smarker_position);
+  defsubr (&Smarker_last_position);
   defsubr (&Smarker_buffer);
   defsubr (&Sset_marker);
   defsubr (&Scopy_marker);
index 0c84b4f4bf3ead16edb208b8ddcb1f0664abc706..ea761fad8bc0c73d993aa6a634b1c22b84c71d42 100644 (file)
@@ -7109,6 +7109,24 @@ current at the start of the function.  If DONT-SET-MINIWINDOW is non-nil,
 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.
+
+After restoring the frame layout, this function runs the abnormal hook
+`window-kept-windows-functions' with two arguments - the frame whose
+layout it has restored and 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 last known
+positions of `window-start' and `window-point' of the buffer in that
+window.  Any function run by this hook should check such a window for
+liveness because another function run by this hook may have deleted it
+in the meantime."
+
 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.  */)
@@ -7119,6 +7137,7 @@ the return value is nil.  Otherwise the value is t.  */)
   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;
@@ -7359,6 +7378,11 @@ the return value is nil.  Otherwise the value is t.  */)
                   BUF_PT (XBUFFER (w->contents)),
                   BUF_PT_BYTE (XBUFFER (w->contents)));
              w->start_at_line_beg = true;
+             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->start))
            /* Leaf window has no live buffer, get one.  */
@@ -7379,6 +7403,11 @@ the return value is nil.  Otherwise the value is t.  */)
                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);
            }
        }
 
@@ -7430,12 +7459,13 @@ the return value is nil.  Otherwise the value is t.  */)
       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 (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);
+         }
 
       /* Record the selected window's buffer here.  The window should
         already be the selected one from the call above.  */
@@ -7482,6 +7512,11 @@ the return value is nil.  Otherwise the value is t.  */)
   minibuf_selected_window = data->minibuf_selected_window;
 
   SAFE_FREE ();
+
+  if (!NILP (Vrun_hooks) && !NILP (Vwindow_kept_windows_functions))
+    run_hook_with_args_2 (Qwindow_kept_windows_functions, frame,
+                         kept_windows);
+
   return FRAME_LIVE_P (f) ? Qt : Qnil;
 }
 
@@ -8479,6 +8514,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 (Qwindow_kept_windows_functions,
+         "window-kept-windows-functions");
 
   DEFVAR_LISP ("temp-buffer-show-function", Vtemp_buffer_show_function,
               doc: /* Non-nil means call as function to display a help buffer.
@@ -8636,6 +8673,28 @@ its buffer or its total or body size since the last redisplay.  Each
 call is performed with the frame temporarily selected.  */);
   Vwindow_configuration_change_hook = Qnil;
 
+  DEFVAR_LISP ("window-kept-windows-functions",
+              Vwindow_kept_windows_functions,
+              doc: /* Functions run after restoring a window configuration or state.
+These functions are called by `set-window-configuration' and
+`window-state-put'.  When the value of this variable is non-nil, these
+functions restore any window whose buffer has been deleted since the
+corresponding configuration or state was saved.  Rather than deleting
+such a window, `set-window-configuration' and `window-state-put' show
+some live buffer in it.
+
+The value should be a list of functions that take two arguments.  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' or `window-state-put' 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.  Note that the window may be already
+dead since another function on this list may have deleted it in the
+meantime.  */);
+  Vwindow_kept_windows_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