From 8783becbba410581c6384ee021e7e83ad5236a29 Mon Sep 17 00:00:00 2001 From: Martin Rudalics Date: Sun, 19 May 2019 11:03:05 +0200 Subject: [PATCH] New buffer display action function 'display-buffer-in-direction' * 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 | 67 +++++++++++++++++- etc/NEWS | 15 ++++ lisp/window.el | 148 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 229 insertions(+), 1 deletion(-) diff --git a/doc/lispref/windows.texi b/doc/lispref/windows.texi index 32e8c2afa31..96e42a148c5 100644 --- a/doc/lispref/windows.texi +++ b/doc/lispref/windows.texi @@ -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 diff --git a/etc/NEWS b/etc/NEWS index b4aa8d98ffa..d70cda179e0 100644 --- 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. diff --git a/lisp/window.el b/lisp/window.el index b4f5ac5cc44..2c9d177d0a2 100644 --- a/lisp/window.el +++ b/lisp/window.el @@ -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 -- 2.39.2