Optionally have 'display-buffer' reuse windows of indirect buffers

* lisp/window.el (window-indirect-buffer-p): New function.
(get-buffer-window-list): New argument INDIRECT.
(display-buffer-reuse-window): New alist entry 'reuse-indirect'
to reuse a window indirectly related to the BUFFER argument.
* doc/lispref/windows.texi (Buffers and Windows): Describe new
function 'window-indirect-buffer-p' and new argument INDIRECT of
'get-buffer-window-list'.
(Buffer Display Action Functions): Describe new action alist
entry 'reuse-indirect'.
* etc/NEWS: Announce new argument for 'get-buffer-window-list' and
new 'display-buffer' action alist entry 'reuse-indirect'.
This commit is contained in:
Martin Rudalics 2025-02-24 10:17:10 +01:00
parent be60601ae8
commit a205d55452
3 changed files with 144 additions and 24 deletions

View file

@ -2697,7 +2697,37 @@ Ordering}). This function may be changed in a future version of Emacs
to eliminate this discrepancy.
@end defun
@defun get-buffer-window-list &optional buffer-or-name minibuf all-frames
The following function can tell for a specific window whether its buffer
shares the text of some other buffer (@pxref{Indirect Buffers}).
@defun window-indirect-buffer-p &optional window buffer-or-name
This function returns non-@code{nil} if @var{window} is indirectly
related to @var{buffer-or-name}. @var{window} must be a live window and
defaults to the selected window. @var{buffer-or-name} may be a buffer
or the name of an existing buffer and defaults to the current buffer.
@var{window} is indirectly related to @var{buffer-or-name} if one of the
following conditions hold:
@itemize @bullet
@item
@var{buffer-or-name} specifies an indirect buffer and @var{window}'s
buffer is its base buffer.
@item
@var{window}'s buffer is an indirect buffer whose base buffer is the
buffer specified by @var{buffer-or-name}.
@item
Both, @var{window}'s buffer and the buffer specified by
@var{buffer-or-name}, are indirect buffer's sharing the same base
buffer.
@end itemize
It returns @code{nil} if none of the above holds.
@end defun
@defun get-buffer-window-list &optional buffer-or-name minibuf all-frames indirect
This function returns a list of all windows currently displaying
@var{buffer-or-name}. @var{buffer-or-name} should be a buffer or the
name of an existing buffer. If omitted or @code{nil}, it defaults to
@ -2709,6 +2739,13 @@ The arguments @var{minibuf} and @var{all-frames} have the same
meanings as in the function @code{next-window} (@pxref{Cyclic Window
Ordering}). Note that the @var{all-frames} argument does @emph{not}
behave exactly like in @code{get-buffer-window}.
The optional argument @var{indirect} non-@code{nil} means to append to
the list of windows showing @var{buffer-or-name} a list of all windows
that are indirectly related to @var{buffer-or-name}, that is, windows
for which @code{window-indirect-buffer-p} (see above) with the window
and the buffer specified by @var{buffer-or-name} as arguments returns
non-@code{nil}.
@end defun
@deffn Command replace-buffer-in-windows &optional buffer-or-name
@ -3165,6 +3202,17 @@ searches just the selected frame.
If this function chooses a window on another frame, it makes that
frame visible and, unless @var{alist} contains an
@code{inhibit-switch-frame} entry, raises that frame if necessary.
If @var{alist} has a non-@code{nil} @code{reuse-indirect} entry and no
window showing @var{buffer} has been found, this function tries to find
a window that is indirectly related to @var{buffer}---a window for which
@code{window-indirect-buffer-p} (@pxref{Buffers and Windows}) with the
window and @var{buffer} as arguments returns non-@code{nil}. If such a
window has been found and the @sc{cdr} of the @code{reuse-indirect}
entry equals the symbol @code{buffer}, it does not replace the buffer of
that window with @var{buffer} but returns the window with its old buffer
in place. Otherwise, it puts @var{buffer} into that window and returns
that window.
@end defun
@defun display-buffer-reuse-mode-window buffer alist

View file

@ -247,6 +247,17 @@ landscape shape for instance, Emacs could split horizontally before
splitting vertically. The default setting preserves Emacs historical
behavior to try to split vertically first.
+++
*** New argument INDIRECT for 'get-buffer-window-list'.
With this argument non-nil, 'get-buffer-window-list' will include in the
return value windows whose buffers share their text with BUFFER-OR-NAME.
+++
*** New 'display-buffer' action alist entry 'reuse-indirect'.
With such an entry, 'display-buffer-reuse-window' may also choose a
window whose buffer shares text with the buffer to display.
** Frames
+++

View file

@ -2616,7 +2616,36 @@ selected frame and no others."
(setq best-window window))))
best-window))
(defun get-buffer-window-list (&optional buffer-or-name minibuf all-frames)
(defun window-indirect-buffer-p (&optional window buffer-or-name)
"Return non-nil if specified WINDOW is indirectly related to BUFFER-OR-NAME.
WINDOW must be a live window and defaults to the selected window.
BUFFER-OR-NAME may be a buffer or the name of an existing buffer and
defaults to the current buffer.
WINODW is indirectly related to BUFFER-OR-NAME if one of the following
conditions hold:
- BUFFER-OR-NAME specifies an indirect buffer and WINDOW's buffer is its
base buffer.
- WINDOW's buffer is an indirect buffer whose base buffer is the buffer
specified by BUFFER-OR-NAME.
- Both, WINDOW's buffer and the buffer specified by BUFFER-OR-NAME, are
indirect buffer's sharing the same base buffer.
Return nil if none of the above holds."
(let* ((window (window-normalize-window window t))
(window-buffer (window-buffer window))
(window-base-buffer (buffer-base-buffer window-buffer))
(buffer (window-normalize-buffer buffer-or-name))
(buffer-base-buffer (buffer-base-buffer buffer)))
(or (eq buffer-base-buffer window-buffer)
(eq window-base-buffer buffer)
(and buffer-base-buffer
(eq buffer-base-buffer window-base-buffer)))))
(defun get-buffer-window-list (&optional buffer-or-name minibuf all-frames indirect)
"Return list of all windows displaying BUFFER-OR-NAME, or nil if none.
BUFFER-OR-NAME may be a buffer or the name of an existing buffer
and defaults to the current buffer. If the selected window displays
@ -2645,12 +2674,23 @@ non-nil values of ALL-FRAMES have special meanings:
- A frame means consider all windows on that frame only.
Anything else means consider all windows on the selected frame
and no others."
and no others.
INDIRECT non-nil means to append to the list of windows showing
BUFFER-OR-NAME a list of all windows that are indirectly related to
BUFFER-OR-NAME, that is, windows for which `window-indirect-buffer-p'
with the window and the buffer specified by BUFFER-OR-NAME as arguments
returns non-nil."
(let ((buffer (window-normalize-buffer buffer-or-name))
(window-list (window-list-1 (selected-window) minibuf all-frames))
windows)
(dolist (window (window-list-1 (selected-window) minibuf all-frames))
(dolist (window window-list)
(when (eq (window-buffer window) buffer)
(setq windows (cons window windows))))
(when indirect
(dolist (window window-list)
(when (window-indirect-buffer-p window buffer)
(setq windows (cons window windows)))))
(nreverse windows)))
(defun minibuffer-window-active-p (window)
@ -8348,35 +8388,56 @@ If ALIST has a non-nil `inhibit-switch-frame' entry, then in the
event that a window on another frame is chosen, avoid raising
that frame.
If ALIST has a non-nil `reuse-indirect' entry and no window showing
BUFFER has been found, try to find a window that is indirectly related
to BUFFER and return that window. This would be a window for which
`window-indirect-buffer-p' with the window and BUFFER as arguments
returns non-nil. If a suitable window has been found and the cdr of the
entry equals the symbol `buffer', do not replace the buffer of that
window with BUFFER but return the window with its old buffer in place.
Otherwise, put BUFFER into that window and return the window.
This is an action function for buffer display, see Info
node `(elisp) Buffer Display Action Functions'. It should be
called only by `display-buffer' or a function directly or
indirectly called by the latter."
(let* ((alist-entry (assq 'reusable-frames alist))
(frames (cond (alist-entry (cdr alist-entry))
(let* ((reusable-frames (assq 'reusable-frames alist))
(reuse-indirect (assq 'reuse-indirect alist))
(frames (cond (reusable-frames (cdr reusable-frames))
((window--pop-up-frames alist)
0)
(display-buffer-reuse-frames 0)
(t (last-nonminibuffer-frame))))
(window (if (and (eq buffer (window-buffer))
(not (cdr (assq 'inhibit-same-window alist))))
(selected-window)
;; Preferably use a window on the selected frame,
;; if such a window exists (Bug#36680).
(let* ((windows (delq (selected-window)
(get-buffer-window-list
buffer 'nomini frames)))
(first (car windows))
(this-frame (selected-frame)))
(cond
((eq (window-frame first) this-frame)
first)
((catch 'found
(dolist (next (cdr windows))
(when (eq (window-frame next) this-frame)
(throw 'found next)))))
(t first))))))
(inhibit-same (cdr (assq 'inhibit-same-window alist)))
(window
;; Avoid calling 'get-buffer-window-list' if the selected
;; window already shows BUFFER and can be used.
(if (and (eq buffer (window-buffer)) (not inhibit-same))
(selected-window)
;; Preferably use a window on the selected frame,
;; if such a window exists (Bug#36680).
(let* ((windows-raw
(get-buffer-window-list
buffer 'nomini frames reuse-indirect))
(windows (if inhibit-same
(delq (selected-window) windows-raw)
windows-raw))
(first (car windows))
(this-frame (selected-frame)))
(cond
((eq (window-frame first) this-frame)
first)
((catch 'found
(dolist (next (cdr windows))
(when (eq (window-frame next) this-frame)
(throw 'found next)))))
(t first))))))
(when (window-live-p window)
(when (and (eq (cdr reuse-indirect) 'buffer)
(not (eq (window-buffer window) buffer)))
;; Pretend we were asking for a window showing the buffer of
;; that window.
(setq buffer (window-buffer window)))
(prog1 (window--display-buffer buffer window 'reuse alist)
(unless (cdr (assq 'inhibit-switch-frame alist))
(window--maybe-raise-frame (window-frame window)))))))