Further adjustments for restoring killed buffer windows (Bug#68235)

* etc/NEWS: Announce 'window-restore-killed-buffer-windows'.
* src/buffer.h (struct buffer) : New field last_name_.
* src/buffer.c (Fbuffer_last_name): New function to return last
name of buffer before it was killed or renamed.
(bset_last_name, Fget_buffer_create, Fmake_indirect_buffer)
(Frename_buffer, Fkill_buffer, init_buffer_once): Set buffer's
last_name_ field accordingly.
* src/window.c (window_restore_killed_buffer_windows): New
variable replacing Vwindow_kept_windows_functions.
(Fset_window_configuration): Use
window_restore_killed_buffer_windows instead of
Vwindow_kept_windows_functions.
* lisp/window.el (window--state-put-2, window-state-put): Use
'window-restore-killed-buffer-windows' instead of
'window-kept-windows-functions'.
* doc/lispref/windows.texi (Window Configurations): Describe
'window-restore-killed-buffer-windows' which replaces
'window-kept-windows-functions'.
This commit is contained in:
Martin Rudalics 2024-03-15 10:35:27 +01:00
parent 1c4233b9a3
commit 5bba1b95b8
6 changed files with 224 additions and 127 deletions

View file

@ -6264,15 +6264,10 @@ this function does is to restore the value of the variable
@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}).
This function consults the variable
@code{window-restore-killed-buffer-windows} (see below) when it tries to
restore a window whose buffer was killed after @var{configuration} was
recorded.
Here is a way of using this function to get the same effect as
@code{save-window-excursion}:
@ -6361,14 +6356,9 @@ 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}).
This function consults the variable
@code{window-restore-killed-buffer-windows} (see below) when it tries to
restore a window whose buffer was killed after @var{state} was recorded.
If the optional argument @var{ignore} is non-@code{nil}, it means to ignore
minimum window sizes and fixed-size restrictions. If @var{ignore}
@ -6376,6 +6366,75 @@ is @code{safe}, this means windows can get as small as one line
and/or two columns.
@end defun
By default, @code{set-window-configuration} and @code{window-state-put}
may delete a window from the restored configuration when they find out
that its buffer was killed since the corresponding configuration or
state has been recorded. The variable described next can be used to
fine-tune that behavior.
@cindex restoring windows whose buffers have been killed
@defvar window-restore-killed-buffer-windows
This variable specifies how @code{set-window-configuration} and
@code{window-state-put} shall handle a window whose buffer has been
killed since the corresponding configuration or state was made. Any
such window may be live - in which case it shows some other buffer - or
dead at the time one of these functions is called. Usually,
@code{set-window-configuration} leaves the window alone if it is live
while @code{window-state-put} deletes it.
The following values can be used to override the default behavior for
dead windows in the case of @code{set-window-configuration} and for dead
and live windows in the case of @code{window-state-put}.
@table @asis
@item @code{t}
This value means to unconditionally restore the window and show some
other buffer in it.
@item @code{delete}
This means to unconditionally try to delete the window.
@item @code{dedicated}
This means to try to delete the window if and only if it is dedicated to
its buffer.
@item @code{nil}
This is the default and means that @code{set-window-configuration} will
try to delete the window if and only if it is dedicated to its buffer
and @code{window-state-put} will unconditionally try to delete it.
@item a function
This means to restore the window, show some other buffer in it and add
an entry for that window to a list that will be later passed as argument
to that function.
@end table
If a window cannot be deleted (typically, because it is the last window
on its frame), @code{set-window-configuration} and
@code{window-state-put} will show another buffer in it.
If the value of this variable is a function, that function should take
three arguments. The first argument specifies the frame whose windows
have been restored. The third argument is either the constant
@code{configuration} if the windows are restored by
@code{set-window-configuration} or the constant @code{state} if the
windows are restored by @code{window-state-put}.
The second argument specifies a list of entries for @emph{any} window
whose previous buffer has been encountered dead at the time
@code{set-window-configuration} or @code{window-state-put} tried to
restore it in that window (minibuffer windows are excluded). This means
that the function specified by this variable may also delete windows
encountered live by @code{set-window-configuration}.
Each entry is a list of six values - the window whose buffer was found
dead, the dead buffer or its name, the positions of start and point of
the buffer in that window, the dedicated status of the window as
previously reported by @code{window-dedicated-p} and a flag that is
@code{t} if the window has been encountered live by
@code{set-window-configuration} and @code{nil} otherwise.
@end defvar
The functions @code{window-state-get} and @code{window-state-put} also
allow exchanging the contents of two live windows. The following
function does precisely that:
@ -6636,27 +6695,6 @@ 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

@ -283,6 +283,12 @@ right-aligned to is controlled by the new user option
It specifies whether the window of the displayed buffer should be
selected or deselected at the end of executing the current command.
+++
*** New variable 'window-restore-killed-buffer-windows'.
It specifies how 'set-window-configuration' and 'window-state-put'
should proceed with windows whose buffer was killed after the
corresponding configuration or state was made.
** Tab Bars and Tab Lines
---

View file

@ -6286,7 +6286,8 @@ value can be also stored on disk and read back in a new session."
(when state
(let* ((old-buffer-or-name (car state))
(buffer (get-buffer old-buffer-or-name))
(state (cdr state)))
(state (cdr state))
(dedicated (cdr (assq 'dedicated state))))
(if (buffer-live-p buffer)
(with-current-buffer buffer
(set-window-buffer window buffer)
@ -6345,7 +6346,7 @@ value can be also stored on disk and read back in a new session."
window delta t ignore nil nil nil pixelwise))
(window-resize window delta t ignore pixelwise))))
;; Set dedicated status.
(set-window-dedicated-p window (cdr (assq 'dedicated state)))
(set-window-dedicated-p window dedicated)
;; Install positions (maybe we should do this after all
;; windows have been created and sized).
(ignore-errors
@ -6388,12 +6389,12 @@ value can be also stored on disk and read back in a new session."
(set-marker (make-marker) m2
buffer))))))
prev-buffers))))
;; We don't want to raise an error in case the buffer does
;; not exist anymore, so we switch to a previous one and
;; 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)
;; Preferably show a buffer previously shown in this
;; window.
(switch-to-prev-buffer window)
(cond
((functionp window-restore-killed-buffer-windows)
(let* ((start (cdr (assq 'start state)))
;; Handle both - marker positions from writable
;; states and markers from non-writable states.
@ -6404,9 +6405,15 @@ value can be also stored on disk and read back in a new session."
(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))))))))
(push (list window old-buffer-or-name
start-pos point-pos dedicated nil)
window-state-put-kept-windows)))
((or (and dedicated
(eq window-restore-killed-buffer-windows 'dedicated))
(memq window-restore-killed-buffer-windows '(nil delete)))
;; Try to delete the 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 +6428,9 @@ sizes and fixed size restrictions. IGNORE equal `safe' means
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 consult the variable
`window-restore-killed-buffer-windows' on how to proceed."
(setq window-state-put-stale-windows nil)
(setq window-state-put-kept-windows nil)
@ -6544,10 +6544,9 @@ by this hook may have deleted it."
(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)
(when (functionp window-restore-killed-buffer-windows)
(funcall window-restore-killed-buffer-windows
frame window-state-put-kept-windows 'state)
(setq window-state-put-kept-windows nil))
(window--check frame))))

View file

@ -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 @@ even if it is dead. The return value is never nil. */)
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 @@ Interactively, CLONE and INHIBIT-BUFFER-HOOKS are nil. */)
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,17 @@ Return nil if BUFFER has been killed. */)
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 name BUFFER had before the last time it was renamed or
immediately before it was 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 +1670,7 @@ This does not change the name of the visited file (if any). */)
(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 +1688,12 @@ This does not change the name of the visited file (if any). */)
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
@ -2095,6 +2114,7 @@ cleaning up all windows currently displaying the buffer to be killed. */)
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 ();
@ -4666,6 +4686,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));
@ -6026,6 +6047,7 @@ There is no reason to change that value except for debugging purposes. */);
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);

View file

@ -309,6 +309,9 @@ struct buffer
/* 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_;

View file

@ -7109,23 +7109,9 @@ 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."
This function consults the variable `window-restore-killed-buffer-windows'
when restoring a window whose buffer was killed after CONFIGURATION was
recorded.
If CONFIGURATION was made from a frame that is now deleted,
only frame-independent values can be restored. In this case,
@ -7378,10 +7364,12 @@ 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,
if (FUNCTIONP (window_restore_killed_buffer_windows)
&& !MINI_WINDOW_P (w))
kept_windows = Fcons (listn (6, window, p->buffer,
Fmarker_last_position (p->start),
Fmarker_last_position (p->pointm)),
Fmarker_last_position (p->pointm),
p->dedicated, Qt),
kept_windows);
}
else if (!NILP (w->start))
@ -7398,16 +7386,25 @@ the return value is nil. Otherwise the value is t. */)
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 (FUNCTIONP (window_restore_killed_buffer_windows))
kept_windows
= Fcons (listn (6, window, p->buffer,
Fmarker_last_position (p->start),
Fmarker_last_position (p->pointm),
p->dedicated, Qnil),
kept_windows);
else if (EQ (window_restore_killed_buffer_windows, Qdelete)
|| (!NILP (p->dedicated)
&& (NILP (window_restore_killed_buffer_windows)
|| EQ (window_restore_killed_buffer_windows,
Qdedicated))))
/* Try to delete this window later. */
dead_windows = Fcons (window, dead_windows);
/* Make sure window is no more dedicated. */
wset_dedicated (w, Qnil);
}
}
}
@ -7459,13 +7456,12 @@ the return value is nil. Otherwise the value is t. */)
unblock_input ();
/* 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);
}
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. */
@ -7513,9 +7509,9 @@ the return value is nil. Otherwise the value is t. */)
SAFE_FREE ();
if (!NILP (Vrun_hooks) && !NILP (Vwindow_kept_windows_functions))
run_hook_with_args_2 (Qwindow_kept_windows_functions, frame,
kept_windows);
if (!NILP (Vrun_hooks) && FUNCTIONP (window_restore_killed_buffer_windows))
safe_calln (window_restore_killed_buffer_windows,
frame, kept_windows, Qconfiguration);
return FRAME_LIVE_P (f) ? Qt : Qnil;
}
@ -8514,8 +8510,9 @@ 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");
DEFSYM (Qconfiguration, "configuration");
DEFSYM (Qdelete, "delete");
DEFSYM (Qdedicated, "dedicated");
DEFVAR_LISP ("temp-buffer-show-function", Vtemp_buffer_show_function,
doc: /* Non-nil means call as function to display a help buffer.
@ -8673,27 +8670,59 @@ 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.
DEFVAR_LISP ("window-restore-killed-buffer-windows",
window_restore_killed_buffer_windows,
doc: /* Control restoring windows whose buffer was killed.
This variable specifies how the functions `set-window-configuration' and
`window-state-put' shall handle a window whose buffer has been killed
since the corresponding configuration or state was made. Any such
window may be live - in which case it shows some other buffer - or dead
at the time one of these functions is called.
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;
As a rule, `set-window-configuration' leaves the window alone if it is
live while `window-state-put' deletes it. The following values can be
used to override the default behavior for dead windows in the case of
`set-window-configuration' and for dead and live windows in the case of
`window-state-put'.
- t means to restore the window and show some other buffer in it.
- `delete' means to try to delete the window.
- `dedicated' means to try to delete the window if and only if it is
dedicated to its buffer.
- nil, the default, means that `set-window-configuration' will try to
delete the window if and only if it is dedicated to its buffer while
`window-state-put' will unconditionally try to delete it.
- a function means to restore the window, show some other buffer in it
and add an entry for that window to a list that will be later passed
as argument to that function.
If a window cannot be deleted (typically, because it is the last window
on its frame), show another buffer in it.
If the value is a function, it should take three arguments. The first
argument specifies the frame whose windows have been restored. The
third argument is the constant `configuration' if the windows are
restored by `set-window-configuration' and the constant `state' if the
windows are restored by `window-state-put'.
The second argument specifies a list of entries for @emph{any} window
whose previous buffer has been encountered dead at the time
`set-window-configuration' or `window-state-put' tried to restore it in
that window (minibuffer windows are excluded). This means that the
function specified by this variable may also delete windows encountered
live by `set-window-configuration'.
Each entry is a list of six values - the window whose buffer was found
dead, the dead buffer or its name, the positions of start and point of
the buffer in that window, the dedicated status of the window as
reported by `window-dedicated-p' and a boolean - t if the window was
live when `set-window-configuration' tried to restore it and nil
otherwise. */);
window_restore_killed_buffer_windows = Qnil;
DEFVAR_LISP ("recenter-redisplay", Vrecenter_redisplay,
doc: /* Non-nil means `recenter' redraws entire frame.