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)
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}:
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
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
(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)))
(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)))
(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)
;; 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.
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.
(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
(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)
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
syms_of_marker (void)
{
defsubr (&Smarker_position);
+ defsubr (&Smarker_last_position);
defsubr (&Smarker_buffer);
defsubr (&Sset_marker);
defsubr (&Scopy_marker);
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. */)
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;
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. */
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);
}
}
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. */
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;
}
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.
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