1
0
mirror of https://git.savannah.gnu.org/git/emacs.git synced 2025-01-11 16:08:13 +00:00

Provide new option `delete-window-set-selected' (Bug#47300)

When `delete-window' deletes its frame's selected window, this new
option allows to choose another window as replacement.

* lisp/window.el (get-lru-window, get-mru-window)
(get-largest-window): New optional argument NO-OTHER.
(window-at-pos): New function.
(delete-window-set-selected): New option.
(delete-window): Handle `delete-window-set-selected'.
* src/window.c (Fdelete_window_internal): Set the selected
window of WINDOW's frame to the first window on that frame and
let `delete-window' choose a more suitable window instead.
* doc/lispref/windows.texi (Deleting Windows): Describe new
option `delete-window-set-selected'.
(Cyclic Window Ordering): Describe new NO-OTHER argument for
`get-lru-window', `get-mru-window' and `get-largest-window'.
* etc/NEWS: Mention `delete-window-set-selected' and the NO-OTHER
argument.
This commit is contained in:
Martin Rudalics 2021-06-10 09:14:21 +02:00
parent dd9385b404
commit b3dd0ce75b
4 changed files with 171 additions and 69 deletions

View File

@ -1318,6 +1318,23 @@ lieu of the usual action of @code{delete-window}. @xref{Window
Parameters}.
@end deffn
When @code{delete-window} deletes the selected window of its frame, it
has to make another window the new selected window of that frame. The
following option allows to choose which window gets selected instead.
@defopt delete-window-set-selected
This option allows to specify which window should become a frame's
selected window after @code{delete-window} has deleted the previously
selected one.
Possible choices are @code{mru} (the default) to select the most
recently used window on that frame and @code{pos} to choose the window
at the position of point of the previously selected window. If this
option is @code{nil}, it means to choose the frame's first window
instead. Note that a window with a non-@code{nil}
@code{no-other-window} parameter is never chosen.
@end defopt
@deffn Command delete-other-windows &optional window
This function makes @var{window} fill its frame, deleting other
windows as necessary. If @var{window} is omitted or @code{nil}, it
@ -2007,7 +2024,7 @@ meaning as for @code{next-window}.
criterion, without selecting it:
@cindex least recently used window
@defun get-lru-window &optional all-frames dedicated not-selected
@defun get-lru-window &optional all-frames dedicated not-selected no-other
This function returns a live window which is heuristically the least
recently used. The optional argument @var{all-frames} has
the same meaning as in @code{next-window}.
@ -2018,33 +2035,25 @@ window (@pxref{Dedicated Windows}) is never a candidate unless the
optional argument @var{dedicated} is non-@code{nil}. The selected
window is never returned, unless it is the only candidate. However, if
the optional argument @var{not-selected} is non-@code{nil}, this
function returns @code{nil} in that case.
function returns @code{nil} in that case. The optional argument
@var{no-other}, if non-@code{nil}, means to never return a window whose
@code{no-other-window} parameter is non-@code{nil}.
@end defun
@cindex most recently used window
@defun get-mru-window &optional all-frames dedicated not-selected
@defun get-mru-window &optional all-frames dedicated not-selected no-other
This function is like @code{get-lru-window}, but it returns the most
recently used window instead. The meaning of the arguments is the
same as described for @code{get-lru-window}.
same as for @code{get-lru-window}.
@end defun
@cindex largest window
@defun get-largest-window &optional all-frames dedicated not-selected
@defun get-largest-window &optional all-frames dedicated not-selected no-other
This function returns the window with the largest area (height times
width). The optional argument @var{all-frames} specifies the windows to
search, and has the same meaning as in @code{next-window}.
A minibuffer window is never a candidate. A dedicated window
(@pxref{Dedicated Windows}) is never a candidate unless the optional
argument @var{dedicated} is non-@code{nil}. The selected window is not
a candidate if the optional argument @var{not-selected} is
non-@code{nil}. If the optional argument @var{not-selected} is
non-@code{nil} and the selected window is the only candidate, this
function returns @code{nil}.
If there are two candidate windows of the same size, this function
prefers the one that comes first in the cyclic ordering of windows,
starting from the selected window.
width). If there are two candidate windows of the same size, it prefers
the one that comes first in the cyclic ordering of windows, starting
from the selected window. The meaning of the arguments is the same as
for @code{get-lru-window}.
@end defun
@cindex window that satisfies a predicate

View File

@ -604,6 +604,17 @@ These options include 'windmove-default-keybindings',
** Windows
+++
*** New option 'delete-window-set-selected'.
This allows to choose a frame's selected window after deleting the
previously selected one.
+++
*** New argument NO-OTHER for some window functions.
'get-lru-window', get-mru-window and 'get-largest-window' now accept a
new optional argument NO-OTHER which if non-nil avoids returning a
window whose 'no-other-window' parameter is non-nil.
+++
*** New 'display-buffer' function 'display-buffer-use-least-recent-window'.
This is like 'display-buffer-use-some-window', but won't reuse the

View File

@ -2499,14 +2499,16 @@ and no others."
(defalias 'some-window 'get-window-with-predicate)
(defun get-lru-window (&optional all-frames dedicated not-selected)
(defun get-lru-window (&optional all-frames dedicated not-selected no-other)
"Return the least recently used window on frames specified by ALL-FRAMES.
Return a full-width window if possible. A minibuffer window is
never a candidate. A dedicated window is never a candidate
unless DEDICATED is non-nil, so if all windows are dedicated, the
value is nil. Avoid returning the selected window if possible.
Optional argument NOT-SELECTED non-nil means never return the
selected window.
selected window. Optional argument NO-OTHER non-nil means to
never return a window whose 'no-other-window' parameter is
non-nil.
The following non-nil values of the optional argument ALL-FRAMES
have special meanings:
@ -2526,7 +2528,9 @@ selected frame and no others."
(let (best-window best-time second-best-window second-best-time time)
(dolist (window (window-list-1 nil 'nomini all-frames))
(when (and (or dedicated (not (window-dedicated-p window)))
(or (not not-selected) (not (eq window (selected-window)))))
(or (not not-selected) (not (eq window (selected-window))))
(or (not no-other)
(not (window-parameter window 'no-other-window))))
(setq time (window-use-time window))
(if (or (eq window (selected-window))
(not (window-full-width-p window)))
@ -2538,12 +2542,14 @@ selected frame and no others."
(setq best-window window)))))
(or best-window second-best-window)))
(defun get-mru-window (&optional all-frames dedicated not-selected)
(defun get-mru-window (&optional all-frames dedicated not-selected no-other)
"Return the most recently used window on frames specified by ALL-FRAMES.
A minibuffer window is never a candidate. A dedicated window is
never a candidate unless DEDICATED is non-nil, so if all windows
are dedicated, the value is nil. Optional argument NOT-SELECTED
non-nil means never return the selected window.
non-nil means never return the selected window. Optional
argument NO-OTHER non-nil means to never return a window whose
'no-other-window' parameter is non-nil.
The following non-nil values of the optional argument ALL-FRAMES
have special meanings:
@ -2565,17 +2571,21 @@ selected frame and no others."
(setq time (window-use-time window))
(when (and (or dedicated (not (window-dedicated-p window)))
(or (not not-selected) (not (eq window (selected-window))))
(or (not best-time) (> time best-time)))
(or (not no-other)
(not (window-parameter window 'no-other-window)))
(or (not best-time) (> time best-time)))
(setq best-time time)
(setq best-window window)))
best-window))
(defun get-largest-window (&optional all-frames dedicated not-selected)
(defun get-largest-window (&optional all-frames dedicated not-selected no-other)
"Return the largest window on frames specified by ALL-FRAMES.
A minibuffer window is never a candidate. A dedicated window is
never a candidate unless DEDICATED is non-nil, so if all windows
are dedicated, the value is nil. Optional argument NOT-SELECTED
non-nil means never return the selected window.
non-nil means never return the selected window. Optional
argument NO-OTHER non-nil means to never return a window whose
'no-other-window' parameter is non-nil.
The following non-nil values of the optional argument ALL-FRAMES
have special meanings:
@ -2596,7 +2606,9 @@ selected frame and no others."
best-window size)
(dolist (window (window-list-1 nil 'nomini all-frames))
(when (and (or dedicated (not (window-dedicated-p window)))
(or (not not-selected) (not (eq window (selected-window)))))
(or (not not-selected) (not (eq window (selected-window))))
(or (not no-other)
(not (window-parameter window 'no-other-window))))
(setq size (* (window-pixel-height window)
(window-pixel-width window)))
(when (> size best-size)
@ -4130,18 +4142,53 @@ frame can be safely deleted."
;; of its frame.
t))))
(defun window--in-subtree-p (window root)
"Return t if WINDOW is either ROOT or a member of ROOT's subtree."
(or (eq window root)
(let ((parent (window-parent window)))
(catch 'done
(while parent
(if (eq parent root)
(throw 'done t)
(setq parent (window-parent parent))))))))
(defun window-at-pos (x y &optional frame no-other)
"Return live window at coordinates X, Y on specified FRAME.
X and Y are counted in pixels from an origin at 0, 0 of FRAME's
native frame. A coordinate on an edge shared by two windows is
attributed to the window on the right (or below). Return nil if
no such window can be found.
Optional argument FRAME must specify a live frame and defaults to
the selected one. Optional argument NO-OTHER non-nil means to
not return a window with a non-nil 'no-other-window' parameter."
(setq frame (window-normalize-frame frame))
(let* ((root-edges (window-edges (frame-root-window frame) nil nil t))
(root-left (nth 2 root-edges))
(root-bottom (nth 3 root-edges)))
(catch 'window
(walk-window-tree
(lambda (window)
(let ((edges (window-edges window nil nil t)))
(when (and (>= x (nth 0 edges))
(or (< x (nth 2 edges)) (= x root-left))
(>= y (nth 1 edges))
(or (< y (nth 3 edges)) (= y root-bottom)))
(if (and no-other (window-parameter window 'no-other-window))
(throw 'window nil)
(throw 'window window)))))
frame))))
(defcustom delete-window-set-selected 'mru
"How to choose a frame's selected window after window deletion.
When a frame's selected window gets deleted, Emacs has to choose
another live window on that frame to serve as its selected
window. This option allows to control which window gets selected
instead.
The possible choices are 'mru' (the default) to select the most
recently used window on that frame and 'pos' to choose the window
at the position of point of the previously selected window. If
this is nil, choose the frame's first window instead. A window
with a non-nil 'no-other-window' parameter is never chosen."
:type '(choice (const :tag "Most recently used" mru)
(const :tag "At position of deleted" pos)
(const :tag "Frame's first " nil))
:group 'windows
:version "28.1")
(defun delete-window (&optional window)
"Delete WINDOW.
"Delete specified WINDOW.
WINDOW must be a valid window and defaults to the selected one.
Return nil.
@ -4156,7 +4203,11 @@ Otherwise, if WINDOW is part of an atomic window, call
`delete-window' with the root of the atomic window as its
argument. Signal an error if WINDOW is either the only window on
its frame, the last non-side window, or part of an atomic window
that is its frame's root window."
that is its frame's root window.
If WINDOW is the selected window on its frame, choose some other
window as that frame's selected window according to the value of
the option `delete-window-set-selected'."
(interactive)
(setq window (window-normalize-window window))
(let* ((frame (window-frame window))
@ -4191,11 +4242,11 @@ that is its frame's root window."
(window-combination-resize
(or window-combination-resize
(window-parameter parent 'window-side)))
(frame-selected
(window--in-subtree-p (frame-selected-window frame) window))
(frame-selected-window (frame-selected-window frame))
;; Emacs 23 preferably gives WINDOW's space to its left
;; sibling.
(sibling (or (window-left window) (window-right window))))
(sibling (or (window-left window) (window-right window)))
frame-selected-window-edges frame-selected-window-pos)
(window--resize-reset frame horizontal)
(cond
((and (not (eq window-combination-resize t))
@ -4211,15 +4262,63 @@ that is its frame's root window."
(t
;; Can't do without resizing fixed-size windows.
(window--resize-siblings window (- size) horizontal t)))
(when (eq delete-window-set-selected 'pos)
;; Remember edges and position of point of the selected window
;; of WINDOW'S frame.
(setq frame-selected-window-edges
(window-edges frame-selected-window nil nil t))
(setq frame-selected-window-pos
(nth 2 (posn-at-point nil frame-selected-window))))
;; Actually delete WINDOW.
(delete-window-internal window)
(window--pixel-to-total frame horizontal)
(when (and frame-selected
(window-parameter
(frame-selected-window frame) 'no-other-window))
;; `delete-window-internal' has selected a window that should
;; not be selected, fix this here.
(other-window -1 frame))
;; If we deleted the selected window of WINDOW's frame, choose
;; another one based on `delete-window-set-selected'. Note
;; that both `window-at-pos' and `get-mru-window' may fail to
;; produce a suitable window in which case we will fall back on
;; its frame's first window, chosen by `delete-window-internal'.
(cond
((window-live-p frame-selected-window))
((and frame-selected-window-pos
;; We have a recorded position of point of the previously
;; selected window. Try to find the window that is now
;; at that position.
(let ((new-frame-selected-window
(window-at-pos
(+ (nth 0 frame-selected-window-edges)
(car frame-selected-window-pos))
(+ (nth 1 frame-selected-window-edges)
(cdr frame-selected-window-pos))
frame t)))
(and new-frame-selected-window
;; Select window at WINDOW's position at point.
(set-frame-selected-window
frame new-frame-selected-window)))))
((and (eq delete-window-set-selected 'mru)
;; Try to use the most recently used window.
(let ((mru-window (get-mru-window frame nil nil t)))
(and mru-window
(set-frame-selected-window frame mru-window)))))
((and (window-parameter
(frame-selected-window frame) 'no-other-window)
;; If `delete-window-internal' selected a window with a
;; non-nil 'no-other-window' parameter as its frame's
;; selected window, try to choose another one.
(catch 'found
(walk-window-tree
(lambda (other)
(unless (window-parameter other 'no-other-window)
(set-frame-selected-window frame other)
(throw 'found t)))
frame))))
(t
;; Record the window chosen by `delete-window-internal'.
(set-frame-selected-window
frame (frame-selected-window frame))))
(window--check frame)
;; Always return nil.
nil))))

View File

@ -5148,15 +5148,13 @@ Signal an error when WINDOW is the only window on its frame. */)
adjust_frame_glyphs (f);
if (!WINDOW_LIVE_P (FRAME_SELECTED_WINDOW (f)))
/* We deleted the frame's selected window. */
/* We apparently deleted the frame's selected window; use the
frame's first window as substitute but don't record it yet.
`delete-window' may have something better up its sleeves. */
{
/* Use the frame's first window as fallback ... */
Lisp_Object new_selected_window = Fframe_first_window (frame);
/* ... but preferably use its most recently used window. */
Lisp_Object mru_window;
/* `get-mru-window' might fail for some reason so play it safe
- promote the first window _without recording it_ first. */
if (EQ (FRAME_SELECTED_WINDOW (f), selected_window))
Fselect_window (new_selected_window, Qt);
else
@ -5164,24 +5162,9 @@ Signal an error when WINDOW is the only window on its frame. */)
last selected window on F was an active minibuffer, we
want to return to it on a later Fselect_frame. */
fset_selected_window (f, new_selected_window);
unblock_input ();
/* Now look whether `get-mru-window' gets us something. */
mru_window = call1 (Qget_mru_window, frame);
if (WINDOW_LIVE_P (mru_window)
&& EQ (XWINDOW (mru_window)->frame, frame))
new_selected_window = mru_window;
/* If all ended up well, we now promote the mru window. */
if (EQ (FRAME_SELECTED_WINDOW (f), selected_window))
Fselect_window (new_selected_window, Qnil);
else
fset_selected_window (f, new_selected_window);
}
else
unblock_input ();
unblock_input ();
FRAME_WINDOW_CHANGE (f) = true;
}
else