]> git.eshelyaron.com Git - emacs.git/commitdiff
New buffer display action function 'display-buffer-in-direction'
authorMartin Rudalics <rudalics@gmx.at>
Sun, 19 May 2019 09:03:05 +0000 (11:03 +0200)
committerMartin Rudalics <rudalics@gmx.at>
Sun, 19 May 2019 09:03:05 +0000 (11:03 +0200)
* lisp/window.el (windows-sharing-edge)
(window--try-to-split-window-in-direction)
(display-buffer-in-direction): New functions.
* doc/lispref/windows.texi (Buffer Display Action Functions):
Describe new action function 'display-buffer-in-direction'.
(Buffer Display Action Alists): Describe new entry 'direction'.
Amend description of 'window' entry.
* etc/NEWS: Mention 'display-buffer-in-direction' and 'direction'
and 'window' action alist entries.

doc/lispref/windows.texi
etc/NEWS
lisp/window.el

index 32e8c2afa31c2fae8656e5674cc172ba1845114e..96e42a148c56c054d06f303106d089409586330e 100644 (file)
@@ -2601,6 +2601,63 @@ window and displaying the buffer in that window.  It can fail if all
 windows are dedicated to other buffers (@pxref{Dedicated Windows}).
 @end defun
 
+@defun display-buffer-in-direction buffer alist
+This function tries to display @var{buffer} at a location specified by
+@var{alist}.  For this purpose, @var{alist} should contain a
+@code{direction} entry whose value is one of @code{left}, @code{above}
+(or @code{up}), @code{right} and @code{below} (or @code{down}).  Other
+values are usually interpreted as @code{below}.
+
+If @var{alist} also contains a @code{window} entry, its value
+specifies a reference window.  That value can be a special symbol like
+@code{main} which stands for the selected frame's main window
+(@pxref{Side Window Options and Functions}) or @code{root} standing
+for the selected frame's root window (@pxref{Windows and Frames}).  It
+can also specify an arbitrary valid window.  Any other value (or
+omitting the @code{window} entry entirely) means to use the selected
+window as reference window.
+
+This function first tries to reuse a window in the specified direction
+that already shows @var{buffer}.  If no such window exists, it tries
+to split the reference window in order to produce a new window in the
+specified direction.  If this fails as well, it will try to display
+@var{buffer} in an existing window in the specified direction.  In
+either case, the window chosen will appear on the side of the
+reference window specified by the @code{direction} entry, sharing at
+least one edge with the reference window.
+
+If the reference window is live, the edge the chosen window will share
+with it is always the opposite of the one specified by the
+@code{direction} entry.  For example, if the value of the
+@code{direction} entry is @code{left}, the chosen window's right edge
+coordinate (@pxref{Coordinates and Windows}) will equal the reference
+window's left edge coordinate.
+
+If the reference window is internal, a reused window must share with
+it the edge specified by the @code{direction} entry.  Hence if, for
+example, the reference window is the frame's root window and the value
+of the @code{direction} entry is @code{left}, a reused window must be
+on the left of the frame.  This means that the left edge coordinate of
+the chosen window and that of the reference window are the same.
+
+A new window, however, will be created by splitting the reference
+window such that the chosen window will share the opposite edge with
+the reference window.  In our example, a new root window would be
+created with a new live window and the reference window as its
+children.  The chosen window's right edge coordinate would then equal
+the left edge coordinate of the reference window.  Its left edge
+coordinate would equal the left edge coordinate of the frame's new
+root window.
+
+Four special values for @code{direction} entries allow to implicitly
+specify the selected frame's main window as the reference window:
+@code{leftmost}, @code{top}, @code{rightmost} and @code{bottom}.  This
+means that instead of, for example, @w{@code{(direction . left)
+(window . main)}} one can just specify @w{@code{(direction
+. leftmost)}}.  An existing @code{window} @var{alist} entry is ignored
+in such cases.
+@end defun
+
 @defun display-buffer-below-selected buffer alist
 This function tries to display @var{buffer} in a window below the
 selected window.  If there is a window below the selected one and that
@@ -2934,12 +2991,20 @@ If non-@code{nil}, the value specifies the slot of the side window
 supposed to display the buffer.  This entry is used only by
 @code{display-buffer-in-side-window}.
 
+@vindex direction@r{, a buffer display action alist entry}
+@item direction
+The value specifies a direction which, together with a @code{window}
+entry, allows @code{display-buffer-in-direction} to determine the
+location of the window to display the buffer.
+
 @vindex window@r{, a buffer display action alist entry}
 @item window
 The value specifies a window that is in some way related to the window
 chosen by @code{display-buffer}.  This entry is currently used by
 @code{display-buffer-in-atom-window} to indicate the window on whose
-side the new window shall be created.
+side the new window shall be created.  It is also used by
+@code{display-buffer-in-direction} to specify the reference window on
+whose side the resulting window shall appear.
 
 @vindex allow-no-window@r{, a buffer display action alist entry}
 @item allow-no-window
index b4aa8d98ffa0d0cfc0c4e94799ac306ea2371d43..d70cda179e05fec56dee4adada836abed4137ee1 100644 (file)
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -1778,6 +1778,11 @@ detailed explanation of the new behavior.
 This option allows to automatically resize minibuffer-only frames
 similarly to how minibuffer windows are resized on "normal" frames.
 
++++
+** New buffer display action function 'display-buffer-in-direction'.
+This function allows to specify the location of the window chosen by
+'display-buffer' in various ways.
+
 +++
 ** New buffer display action alist entry 'dedicated'.
 Such an entry allows to specify the dedicated status of a window
@@ -1789,6 +1794,16 @@ Such an entry allows to specify a minimum height of the window used
 for displaying a buffer.  'display-buffer-below-selected' is the only
 action function to respect it at the moment.
 
++++
+** New buffer display action alist entry 'direction'.
+This entry is used to specify the location of the window chosen by
+'display-buffer-in-direction'.
+
++++
+** Additional meaning of display action alist entry 'window'.
+A 'window' entry can now also specify a reference window for
+'display-buffer-in-direction'.
+
 +++
 ** The function 'assoc-delete-all' now takes an optional predicate argument.
 
index b4f5ac5cc44af79799b63d52630638adf6e68771..2c9d177d0a2add51f37bf332f07f8846dc60446c 100644 (file)
@@ -7534,6 +7534,152 @@ be added to ALIST."
       (unless (cdr (assq 'inhibit-switch-frame alist))
        (window--maybe-raise-frame frame)))))
 
+(defun windows-sharing-edge (&optional window edge within)
+  "Return list of live windows sharing the same edge with WINDOW.
+WINDOW must be a valid window and defaults to the selected one.
+EDGE stands for the edge to share and must be either 'left',
+'above', 'right' or 'below'.  Omitted or nil, EDGE defaults to
+'left'.
+
+WITHIN nil means to find a live window that shares the opposite
+EDGE with WINDOW.  For example, if EDGE equals 'left', WINDOW has
+to share (part of) the right edge of any window returned.  WITHIN
+non-nil means to find all live windows that share the same EDGE
+with WINDOW (Window must be internal in this case).  So if EDGE
+equals 'left', WINDOW's left edge has to fully encompass the left
+edge of any window returned."
+  (setq window (window-normalize-window window))
+  (setq edge (or edge 'left))
+  (when (and within (window-live-p window))
+    (error "Cannot share edge from within live window %s" window))
+  (let ((window-edges (window-edges window nil nil t))
+       (horizontal (memq edge '(left right)))
+       (n (pcase edge
+            ('left 0) ('above 1) ('right 2) ('below 3))))
+    (unless (numberp n)
+      (error "Invalid EDGE %s" edge))
+    (let ((o (mod (+ 2 n) 4))
+         (p (if horizontal 1 0))
+         (q (if horizontal 3 2))
+         windows)
+      (walk-window-tree
+       (lambda (other)
+        (let ((other-edges (window-edges other nil nil t)))
+          (when (and (not (eq window other))
+                     (= (nth n window-edges)
+                        (nth (if within n o) other-edges))
+                     (cond
+                      ((= (nth p window-edges) (nth p other-edges)))
+                      ((< (nth p window-edges) (nth p other-edges))
+                       (< (nth p other-edges) (nth q window-edges)))
+                      (t
+                       (< (nth p window-edges) (nth q other-edges)))))
+            (setq windows (cons other windows)))))
+       (window-frame window) nil 'nomini)
+      (reverse windows))))
+
+(defun window--try-to-split-window-in-direction (window direction alist)
+  "Try to split WINDOW in DIRECTION.
+DIRECTION is passed as SIDE argument to `split-window-no-error'.
+ALIST is a buffer display alist."
+  (and (not (frame-parameter (window-frame window) 'unsplittable))
+       (let* ((window-combination-limit
+              ;; When `window-combination-limit' equals
+              ;; `display-buffer' or equals `resize-window' and a
+              ;; `window-height' or `window-width' alist entry are
+              ;; present, bind it to t so resizing steals space
+              ;; preferably from the window that was split.
+              (if (or (eq window-combination-limit 'display-buffer)
+                      (and (eq window-combination-limit 'window-size)
+                           (or (cdr (assq 'window-height alist))
+                               (cdr (assq 'window-width alist)))))
+                  t
+                window-combination-limit))
+             (new-window (split-window-no-error window nil direction)))
+        (and (window-live-p new-window) new-window))))
+
+(defun display-buffer-in-direction (buffer alist)
+  "Try to display BUFFER in a direction specified by ALIST.
+ALIST has to contain a 'direction' entry whose value should be
+one of 'left', 'above' (or 'up'), 'right', and 'below' (or
+'down').  Other values are usually interpreted as 'below'.
+
+If ALIST also contains a 'window' entry, its value specifies a
+reference window.  That value can be a special symbol like
+'main' (which stands for the selected frame's main window) or
+'root' (standings for the selected frame's root window) or an
+arbitrary valid window.  Any other value (or omitting the
+'window' entry) means to use the selected window as reference
+window.
+
+This function tries to reuse or split a window such that the
+window produced this way is on the side of the reference window
+specified by the 'direction' entry.
+
+Four special values for 'direction' entries allow to implicitly
+specify the selected frame's main window as reference window:
+'leftmost', 'top', 'rightmost' and 'bottom'.  Hence, instead of
+'(direction . left) (window . main)' one can simply write
+'(direction . leftmost)'."
+  (let ((direction (cdr (assq 'direction alist))))
+    (when direction
+      (let ((window (cdr (assq 'window alist)))
+           within windows other-window-shows-buffer other-window)
+       ;; Sanitize WINDOW.
+       (cond
+        ((or (eq window 'main)
+              (memq direction '(top bottom leftmost rightmost)))
+         (setq window (window-main-window)))
+        ((eq window 'root)
+         (setq window (frame-root-window)))
+        ((window-valid-p window))
+        (t
+         (setq window (selected-window))))
+       (setq within (not (window-live-p window)))
+       ;; Sanitize DIRECTION
+       (cond
+        ((memq direction '(left above right below)))
+        ((eq direction 'leftmost)
+         (setq direction 'left))
+        ((memq direction '(top up))
+         (setq direction 'above))
+        ((eq direction 'rightmost)
+         (setq direction 'right))
+        ((memq direction '(bottom down))
+         (setq direction 'below))
+        (t
+         (setq direction 'below)))
+
+       (setq alist
+             (append alist
+                     `(,(if temp-buffer-resize-mode
+                            '(window-height . resize-temp-buffer-window)
+                          '(window-height . fit-window-to-buffer))
+                       ,(when temp-buffer-resize-mode
+                          '(preserve-size . (nil . t))))))
+
+       (setq windows (windows-sharing-edge window direction within))
+       (dolist (other windows)
+         (cond
+          ((and (not other-window-shows-buffer)
+                (eq buffer (window-buffer other)))
+           (setq other-window-shows-buffer t)
+           (setq other-window other))
+          ((not other-window)
+           (setq other-window other))))
+       (or (and other-window-shows-buffer
+                (window--display-buffer buffer other-window 'reuse alist))
+           (and (setq other-window
+                      (window--try-to-split-window-in-direction
+                       window direction alist))
+                (window--display-buffer buffer other-window 'window alist))
+           (and (setq window other-window)
+                (not (window-dedicated-p other-window))
+                (not (window-minibuffer-p other-window))
+                (window--display-buffer buffer other-window 'reuse alist)))))))
+
+;; This should be rewritten as
+;; (display-buffer-in-direction buffer (cons '(direction . below) alist))
 (defun display-buffer-below-selected (buffer alist)
   "Try displaying BUFFER in a window below the selected window.
 If there is a window below the selected one and that window
@@ -7589,6 +7735,8 @@ must also contain a 'window-height' entry with the same value."
         (display-buffer--maybe-pop-up-frame buffer alist)
         (display-buffer-at-bottom buffer alist))))
 
+;; This should be rewritten as
+;; (display-buffer-in-direction buffer (cons '(direction . bottom) alist))
 (defun display-buffer-at-bottom (buffer alist)
   "Try displaying BUFFER in a window at the bottom of the selected frame.
 This either reuses such a window provided it shows BUFFER