emacs-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

master b3dd0ce: Provide new option `delete-window-set-selected' (Bug#473


From: Martin Rudalics
Subject: master b3dd0ce: Provide new option `delete-window-set-selected' (Bug#47300)
Date: Thu, 10 Jun 2021 03:16:22 -0400 (EDT)

branch: master
commit b3dd0ce75ba9314eb7a682e5fcf8b4cfbc67655b
Author: Martin Rudalics <rudalics@gmx.at>
Commit: Martin Rudalics <rudalics@gmx.at>

    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.
---
 doc/lispref/windows.texi |  47 ++++++++------
 etc/NEWS                 |  11 ++++
 lisp/window.el           | 157 ++++++++++++++++++++++++++++++++++++++---------
 src/window.c             |  25 ++------
 4 files changed, 171 insertions(+), 69 deletions(-)

diff --git a/doc/lispref/windows.texi b/doc/lispref/windows.texi
index 44656c0..bcb492d 100644
--- a/doc/lispref/windows.texi
+++ b/doc/lispref/windows.texi
@@ -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
diff --git a/etc/NEWS b/etc/NEWS
index 3075fa0..06c6efd 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -605,6 +605,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
 current window, and when called repeatedly will try not to reuse a
diff --git a/lisp/window.el b/lisp/window.el
index fd1c617..e7551f3 100644
--- a/lisp/window.el
+++ b/lisp/window.el
@@ -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))))
diff --git a/src/window.c b/src/window.c
index 2d98ae5..db324ef 100644
--- a/src/window.c
+++ b/src/window.c
@@ -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



reply via email to

[Prev in Thread] Current Thread [Next in Thread]