Handle transposing and rotating of dedicated windows (Bug#81406)

* lisp/window-x.el (transpose-dedicated-windows): New option.
(window-layout-rotate-clockwise)
(window-layout-rotate-anticlockwise)
(window-layout-flip-leftright, window-layout-flip-topdown)
(window-layout-transpose, rotate-windows-back): Say that these
functions may signal an error when they encounter dedicated,
fixed-size or atomic windows.
(rotate-windows): Handle dedicated windows like the other
transpose functions.
(window--transpose): Handle 'transpose-dedicated-windows'.
* doc/lispref/windows.texi (Changing Window Layouts): Describe new
option 'transpose-dedicated-windows'.
* etc/NEWS: Announce new option 'transpose-dedicated-windows'.
This commit is contained in:
Martin Rudalics 2026-08-07 17:44:39 +02:00
parent 7119808569
commit 321f6a07e8
3 changed files with 90 additions and 43 deletions

View file

@ -2298,7 +2298,10 @@ windows by deleting them first and resurrecting them
(@pxref{Resurrecting Windows}) in their new locations afterwards. No
window gets lost and no window is added or duplicated. Where possible,
these commands try to preserve the relative size of windows as well as
all other non-geometric properties including parameters.
all other non-geometric properties including parameters. These commands
will signal an error if windows chosen to transpose or rotate are atomic
or of fixed size. The option @code{transpose-dedicated-windows} below
decides whether dedicated windows may be transposed or rotated.
The @var{window} argument of all functions described in this section
must specify a valid parent window (@pxref{Windows and Frames}). If it
@ -2415,16 +2418,14 @@ becomes a vertical split and vice versa.
@end deffn
@cindex rotating windows
The final two commands can be used to @dfn{rotate} windows within the existing
layout. They are like the commands that rotate the layout but leave the
underlying structure of the layout unchanged. What actually changes are
the positions of windows within the existing layout. This happens in a
way that the space formerly occupied by any window will be occupied by
the window preceding (following if @var{reverse} is non-@code{nil}) it
in the cyclic ordering of windows (@pxref{Cyclic Window Ordering}) on
@var{window}'s frame. These commands may signal an error if windows
chosen to rotate are dedicated to their buffers, atomic or of fixed
size.
The final two commands can be used to @dfn{rotate} windows within the
existing layout. They are like the commands that rotate the layout but
leave the underlying structure of the layout unchanged. What actually
changes are the positions of windows within the existing layout. This
happens in a way that the space formerly occupied by any window will be
occupied by the window preceding (following if @var{reverse} is
non-@code{nil}) it in the cyclic ordering of windows (@pxref{Cyclic
Window Ordering}) on @var{window}'s frame.
@deffn Command rotate-windows &optional window reverse
This command rotates @var{window}'c child windows in cyclic ordering.
@ -2463,6 +2464,15 @@ ordering.
@end smallexample
@end deffn
All commands described above are subject to the following option.
@defopt transpose-dedicated-windows
If this is @code{nil}, the functions described in this section will
report an error when they encounter a dedicated window (@pxref{Dedicated
Windows}). If this is @code{t}, they handle dedicated windows like
non-dedicated ones.
@end defopt
The last two commands are subject to the following option.
@defopt rotate-windows-change-selected

View file

@ -373,12 +373,18 @@ every horizontal split becomes a vertical split and vice versa.
'rotate-windows-back' ('C-x w o <LEFT>') rotate windows in cyclic
ordering.
+++
*** New user option 'rotate-windows-change-selected'.
This controls whether 'rotate-windows' and 'rotate-windows-back' change
the selected window. If nil, the selected window does not change.
The default is t, which means the new selected window will be the one
that winds up at the location of the previously-selected window.
+++
*** New user option 'transpose-dedicated-windows'.
This controls how functions transposing or rotating windows handle
dedicated windows.
+++
*** Windmove commands now move to skipped windows if invoked twice in a row.
The new user option 'windmove-allow-repeated-command-override' controls

View file

@ -2,7 +2,7 @@
;; Copyright (C) 2025-2026 Free Software Foundation, Inc.
;; Author: Pranshu Sharma <pranshu@bauherren.ovh>
;; Author: Pranshu Sharma <pranshusharma366@gmail.com>
;; Martin Rudalics <rudalics@gmx.at>
;; Maintainer: emacs-devel@gnu.org
;; Keywords: window, convenience
@ -67,18 +67,32 @@ where HEIGHT and WIDTH are the normal height and width of the window.
"Return interactive window argument for window rotation commands."
(if current-prefix-arg (window-parent) (window-main-window)))
(defcustom transpose-dedicated-windows nil
"Whether dedicated windows may be transposed.
If this is nil, functions for transposing, flipping or rotating the
window layout and functions for rotating windows will report an error
when they encounter a dedicated window. If this is t, they handle
dedicated windows like non-dedicated ones."
:type 'boolean
:version "31.1"
:group 'windows)
;;;###autoload
(defun window-layout-rotate-clockwise (&optional window)
"Rotate layout of WINDOW's child windows clockwise by 90 degrees.
WINDOW must be a parent window and defaults to the main window of the
selected frame. Interactively, with a prefix argument, rotate clockwise
the layout of the child windows of the selected window's parent. Signal
an error if WINDOW is not a parent window.
the layout of the child windows of the selected window's parent.
Recursively rotate the entire layout of WINDOW's child windows clockwise
by 90 degrees. Do not change the selected window of WINDOW's frame. If
you want to rotate windows within their frame's layout, consider using
`rotate-windows' instead."
`rotate-windows' instead.
Signal an error if WINDOW is not a parent window or some descendants of
WINDOW are of fixed size or atomic. Also signal an error if
`transpose-dedicated-windows' is nil and a descendant window is
dedicated."
(interactive (list (window--rotate-interactive-arg)))
(window--transpose (or window (window-main-window)) '(left . below) nil))
@ -88,12 +102,17 @@ you want to rotate windows within their frame's layout, consider using
WINDOW must be a parent window and defaults to the main window of the
selected frame. Interactively, with a prefix argument, rotate
counterclockwise the layout of the child windows of the selected
window's parent. Signal an error if WINDOW is not a parent window.
window's parent.
Recursively rotate the entire layout of WINDOW's child windows
counterclockwise by 90 degrees. Do not change the selected window of
WINDOW's frame. If you want to rotate windows within their frame's
layout, consider using `rotate-windows-back' instead."
layout, consider using `rotate-windows-back' instead.
Signal an error if WINDOW is not a parent window or one of WINDOW's
descendants is of fixed size or atomic. Also signal an error if
`transpose-dedicated-windows' is nil and a descendant window is
dedicated."
(interactive (list (window--rotate-interactive-arg)))
(window--transpose (or window (window-main-window)) '(right . above) nil))
@ -103,10 +122,14 @@ layout, consider using `rotate-windows-back' instead."
WINDOW must be a parent window and defaults to the main window of the
selected frame. Interactively, with a prefix argument, flip
horizontally the layout of the child windows of the selected window's
parent. Signal an error if WINDOW is not a parent window.
parent.
Recursively flip the layout of WINDOW's child windows so that a child
window on the right becomes a child window on the left and vice-versa."
window on the right becomes a child window on the left and vice-versa.
Signal an error if WINDOW is not a parent window or one of WINDOW's
descendants is of fixed size or atomic. Also signal an error if
`transpose-dedicated-windows' is nil and a descendant window is
dedicated."
(interactive (list (window--rotate-interactive-arg)))
(window--transpose (or window (window-main-window)) '(below . left) t))
@ -115,11 +138,14 @@ window on the right becomes a child window on the left and vice-versa."
"Flip WINDOW's child windows vertically.
WINDOW must be a parent window and defaults to the main window of the
selected frame. Interactively, with a prefix argument, flip vertically
the layout of the child windows of the selected window's parent. Signal
an error if WINDOW is not a parent window.
the layout of the child windows of the selected window's parent.
Recursively flip the layout of WINDOW's child windows so that a child
window on the top becomes a child window on the bottom and vice-versa."
window on the top becomes a child window on the bottom and vice-versa.
Signal an error if WINDOW is not a parent window or one of WINDOW's
descendants is of fixed size or atomic. Also signal an error if
`transpose-dedicated-windows' is nil and a descendant window is
dedicated."
(interactive (list (window--rotate-interactive-arg)))
(window--transpose (or window (window-main-window)) '(above . right) t))
@ -128,11 +154,13 @@ window on the top becomes a child window on the bottom and vice-versa."
"Transpose child windows of WINDOW.
WINDOW must be a parent window and defaults to the main window of the
selected frame. Interactively, with a prefix argument, transpose the
layout of the child windows of the selected window's parent. Signal an
error if WINDOW is not a parent window.
layout of the child windows of the selected window's parent.
Recursively reorganize WINDOW's child windows so that each horizontal
split becomes a vertical split and vice versa."
split becomes a vertical split and vice versa. Signal an error if
WINDOW is not a parent window or one of WINDOW's descendants is of fixed
size or atomic. Also signal an error if `transpose-dedicated-windows'
is nil and a descendant window is dedicated."
(interactive (list (window--rotate-interactive-arg)))
(window--transpose (or window (window-main-window)) '(right . below) nil))
@ -154,9 +182,10 @@ selected frame. Interactively, with a prefix argument, rotate the child
windows of the selected window's parent.
Optional argument REVERSE non-nil means to rotate windows backwards, in
reverse cyclic order. Signal an error if WINDOW is not a parent window,
all descendants of WINDOW are dedicated or some windows are of fixed
size or atomic.
reverse cyclic order. Signal an error if WINDOW is not a parent window
or some descendants of WINDOW are of fixed size or atomic. Also signal
an error if `transpose-dedicated-windows' is nil and a descendant window
is dedicated.
Rotating windows leaves the way a frame layout has been produced via
splitting, deleting and resizing windows unaltered. It only \"moves\"
@ -183,12 +212,8 @@ vice-versa), consider running `window-layout-flip-leftright' and
(winls (or
(seq-filter
(lambda (win)
(and (window-live-p win)
(not (window-dedicated-p win))))
(flatten-list win-tree))
;; Do we really care - the window/buffer relationship
;; should not be affected by rotating.
(user-error "All windows are dedicated")))
(window-live-p win))
(flatten-list win-tree))))
(rotated-ls (if reverse
(append (cdr winls) (list (car winls)))
(append (last winls) winls)))
@ -199,13 +224,14 @@ vice-versa), consider running `window-layout-flip-leftright' and
(named-let rec ((tree win-tree))
(cond
((consp tree) (cons (rec (car tree)) (rec (cdr tree))))
((and (window-live-p tree)
(not (window-dedicated-p tree)))
((window-live-p tree)
(pop rotated-ls))
(t tree)))))
(when (or (seq-some #'window-atom-root winls)
(seq-some #'window-fixed-size-p winls))
(user-error "Cannot rotate windows due to fixed size or atomic windows"))
(seq-some #'window-fixed-size-p winls)
(and (not transpose-dedicated-windows)
(seq-some #'window-dedicated-p winls)))
(user-error "Cannot rotate windows due to dedicated, fixed size or atomic window"))
(delete-other-windows-internal first-window window)
(window--transpose-1 new-win-tree first-window '(below . right) t nil)
(when x-y
@ -222,9 +248,12 @@ vice-versa), consider running `window-layout-flip-leftright' and
"Rotate child windows of WINDOW backwards in cyclic ordering.
WINDOW must be a parent window and defaults to the main window of the
selected frame. Interactively, with a prefix argument, rotate backwards
the child windows of the selected window's parent. Signal an error if
WINDOW is not a parent window, all descendants of WINDOW are dedicated
or some of them are of fixed size or atomic.
the child windows of the selected window's parent.
Signal an error if WINDOW is not a parent window or descendants of
WINDOW are of fixed size or atomic. Also signal an error if
`transpose-dedicated-windows' is nil and a descendant window is
dedicated.
Rotating windows backwards leaves the way a frame layout has been
produced via splitting, deleting and resizing windows unaltered. It
@ -260,8 +289,10 @@ with the same proportion of the total split."
(unless (and (not (eq (car atom-windows) window))
(or no-resize
(and (not atom-windows)
(not (seq-some #'window-fixed-size-p win-list)))))
(user-error "Cannot rotate windows due to fixed size or atom windows"))
(not (seq-some #'window-fixed-size-p win-list))
(or transpose-dedicated-windows
(not (seq-some #'window-dedicated-p win-list))))))
(user-error "Cannot transpose windows due to dedicated, fixed size or atomic window"))
(delete-dups atom-windows)
(while (not (window-live-p first-window))
(setq first-window (window-child first-window)))