diff --git a/lisp/window.el b/lisp/window.el index e100f25526b..868eef0b63c 100644 --- a/lisp/window.el +++ b/lisp/window.el @@ -6174,6 +6174,9 @@ window-state-put-list (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'.") + (defun window--state-put-1 (state &optional window ignore totals pixelwise) "Helper function for `window-state-put'." (let ((type (car state))) @@ -6278,9 +6281,10 @@ window--state-put-2 (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))) @@ -6375,7 +6379,20 @@ window--state-put-2 ;; 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. @@ -6388,8 +6405,20 @@ window-state-put 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, +never delete any windows saved by STATE whose buffers were +deleted since STATE was saved. Rather show some live buffer in +them and call each function 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. @@ -6492,12 +6521,17 @@ window-state-put (window--state-put-2 ignore pixelwise)) (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) diff --git a/src/marker.c b/src/marker.c index 377f6fbe8db..14b9f63f0cd 100644 --- a/src/marker.c +++ b/src/marker.c @@ -458,6 +458,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 @@ -825,6 +837,7 @@ verify_bytepos (ptrdiff_t charpos) syms_of_marker (void) { defsubr (&Smarker_position); + defsubr (&Smarker_last_position); defsubr (&Smarker_buffer); defsubr (&Sset_marker); defsubr (&Scopy_marker); diff --git a/src/window.c b/src/window.c index 3a54f7ce7b1..8e002d70db6 100644 --- a/src/window.c +++ b/src/window.c @@ -7090,6 +7090,24 @@ 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. + +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 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 the 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. */) @@ -7100,6 +7118,7 @@ DEFUN ("set-window-configuration", Fset_window_configuration, 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 +7359,11 @@ 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)) + 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. */ @@ -7360,6 +7384,11 @@ 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 (Vwindow_kept_windows_functions)) + kept_windows = Fcons (list4 (window, p->buffer, + Fmarker_last_position (p->start), + Fmarker_last_position (p->pointm)), + kept_windows); } } @@ -7411,12 +7440,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 (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. */ @@ -7463,6 +7493,11 @@ DEFUN ("set-window-configuration", Fset_window_configuration, 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; } @@ -8460,6 +8495,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. @@ -8617,6 +8654,28 @@ syms_of_window (void) 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