Fix 'set-window-configuration' and 'window-state-put'

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'.
This commit is contained in:
Martin Rudalics 2024-03-04 10:33:49 +01:00
parent 6dacb60bb1
commit 912e37b811
4 changed files with 173 additions and 16 deletions

View 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

View file

@ -6174,6 +6174,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)))
@ -6278,9 +6284,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)))
@ -6348,7 +6355,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)
@ -6375,7 +6393,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.
@ -6388,8 +6419,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.
@ -6482,6 +6525,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
@ -6490,14 +6534,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)

View 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);

View 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