Fix issues related to window layout changes (Bug#81406)

* src/window.c (Vwindow_combination_resize): In doc-string say
that binding this to nil may be necessary to produce specific,
predefined frame layouts (Bug#81406).
* lisp/window-x.el (window-layout-rotate-clockwise)
(window-layout-rotate-anticlockwise)
(window-layout-flip-leftright, window-layout-flip-topdown)
(window-layout-transpose, rotate-windows-change-selected)
(rotate-windows, rotate-windows-back): Rewrite doc-strings.
Make sure that non-interactive calls with nil or absent WINDOW
argument work on the frame's main window.  Fix infinite loop
when 'rotate-windows' is called for a non-selected frame.
* doc/lispref/windows.texi (Recombining Windows): Say that
binding 'window-combination-resize' to nil may be necessary to
produce specific, predefined frame layouts (Bug#81406).
(Changing Window Layouts): Minor rewrite.
* etc/NEWS: Announce names of new commands to change window
layout (Bug#81406).
This commit is contained in:
Martin Rudalics 2026-07-14 11:23:42 +02:00
parent 86b65d82c7
commit 4be321d511
4 changed files with 136 additions and 96 deletions

View file

@ -2025,6 +2025,13 @@ combination.
The default is @code{nil}. Other values are reserved for future use. A
specific split operation may ignore the value of this variable if it is
affected by a non-@code{nil} value of @code{window-combination-limit}.
If you plan to use a sequence of @code{split-window} calls to produce a
specific, predefined layout of windows on a frame, bind this variable
temporarily to nil. @code{window-state-put} (@pxref{Window
Configurations}) and the functions to change window layouts
(@pxref{Changing Window Layouts}) do that in order to restore a previous
or systematically changed layout of a frame.
@end defopt
To illustrate the effect of @code{window-combination-resize}, consider
@ -2302,8 +2309,8 @@ below, we shall always assume that the windows displayed constitute
their frame's main window and the @var{window} argument is @code{nil}
unless stated otherwise.
The first two commands rotate a window layout either clockwise or
counterclockwise.
The first two commands rotate an entire window layout either
clockwise or counterclockwise.
@cindex rotate window layout
@deffn Command window-layout-rotate-clockwise &optional window
@ -2368,7 +2375,7 @@ windows on the top and vice-versa as in the example below.
@deffn Command window-layout-flip-leftright &optional window
This command rearranges window in a way that the windows on the right
become the window on the left, and vice-versa.
become the windows on the left, and vice-versa.
@smallexample
@group
@ -2409,13 +2416,19 @@ becomes a vertical split and vice versa.
The final two commands can be used to 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.
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-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.
@cindex rotating windows
@deffn Command rotate-windows &optional window reverse
This rotate windows under @var{window} in cyclic ordering. The optional
argument @var{reverse} means to rotate windows backward, in reverse
cyclic order.
This command rotates @var{window}'c child windows in cyclic ordering.
The optional argument @var{reverse} means to rotate windows backwards,
in reverse cyclic order.
@smallexample
@group
@ -2432,7 +2445,7 @@ cyclic order.
@end deffn
@deffn Command rotate-windows-back &optional window
This command rotates windows under @var{window} backward in cyclic
This command rotates @var{window}'s child windows backwards in cyclic
ordering.
@smallexample
@ -2455,7 +2468,7 @@ The last two commands are subject to the following option.
If this is @code{nil}, the selected window will remain unaffected by
@code{rotate-windows} and @code{rotate-windows-back}. Otherwise, the
selected window will change to the window that appears at the location
of the selected window before any of these commands were invoked.
of the window selected before any of these commands were invoked.
@end defopt

View file

@ -357,14 +357,15 @@ increments as with Emacs 24 through Emacs 30 you now have to set
** Windows
+++
*** New commands to modify window layouts.
- 'C-x w t' and 'C-x w r <left>/<right>' rotate the window layout.
- 'C-x w o <left>/<right>' rotate the windows within the current layout.
- 'C-x w f <left>/<right>/<up>/<down>' flip window layouts.
By default, these commands operate on the selected frame's root window.
With a prefix argument, they operate on the selected window's parent.
*** New commands to modify window layouts of frames.
'window-layout-rotate-clockwise' and its counterpart
'window-layout-rotate-anticlockwise' rotate an entire window layout.
'window-layout-flip-topdown' and 'window-layout-flip-leftright' flip the
window layout vertically and horizontally.
'window-layout-transpose' reorganizes windows such that every horizontal
split becomes a vertical split and vice versa.
'rotate-windows' and its counterpart 'rotate-windows-back' rotate
windows in cylic ordering.
+++
*** Windmove commands now move to skipped windows if invoked twice in a row.

View file

@ -29,17 +29,8 @@
;;; Code:
(defcustom rotate-windows-change-selected t
"If nil the selected window will not change with `rotate-windows'.
The selected window before and after the function call will stay
unchanged if nil. `rotate-windows-back' is also affected."
:type 'boolean
:group 'windows)
(defun window-tree-normal-sizes (window &optional next)
"Return normal sizes of all windows rooted at WINDOW.
The return value is a list of the form (SPLIT-TYPE PARENT-WIN
PARENT-WIN-HEIGHT PARENT-WIN-WIDTH . WS), where SPLIT-TYPE is non-nil if
PARENT-WIN is split horizontally; PARENT-WIN is the internal window;
@ -47,7 +38,7 @@ PARENT-WIN-HEIGHT and PARENT-WIN-WIDTH are the normal heights of
PARENT-WIN; and WS is a list of lists the form (WINDOW HEIGHT WIDTH)
where HEIGHT and WIDTH are the normal height and width of the window.
(fn WINDOW)"
\(fn WINDOW)"
(let (list)
(while window
(setq list
@ -77,97 +68,112 @@ where HEIGHT and WIDTH are the normal height and width of the window.
(if current-prefix-arg (window-parent) (window-main-window)))
;;;###autoload
(defun window-layout-rotate-anticlockwise (&optional window)
"Rotate window layout of WINDOW counterclockwise by 90 degrees.
(defun window-layout-rotate-clockwise (&optional window)
"Clockwise rotate layout of WINDOW's child windows.
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
If WINDOW is nil, it defaults to the root window of the selected frame.
Interactively, a prefix argument says to rotate the parent window of the
selected window."
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."
(interactive (list (window--rotate-interactive-arg)))
(window--transpose window '(right . above) nil))
(window--transpose (or window (window-main-window)) '(left . below) nil))
;;;###autoload
(defun window-layout-rotate-clockwise (&optional window)
"Rotate window layout under WINDOW clockwise by 90 degrees.
(defun window-layout-rotate-anticlockwise (&optional window)
"Rotate layout of WINDOW's child windows counterclockwise 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
counterclockwise the layout of the child windows of the selected
window's parent. Signal an error if WINDOW is not a parent window
If WINDOW is nil, it defaults to the root window of the selected frame.
Interactively, a prefix argument says to rotate the parent window of the
selected window."
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."
(interactive (list (window--rotate-interactive-arg)))
(window--transpose window '(left . below) nil))
(window--transpose (or window (window-main-window)) '(right . above) nil))
;;;###autoload
(defun window-layout-flip-leftright (&optional window)
"Horizontally flip windows under WINDOW.
"Horizontally flip WINDOW's child windows.
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
Flip the window layout so that the window on the right becomes the
window on the left, and vice-versa.
If WINDOW is nil, it defaults to the root window of the selected frame.
Interactively, a prefix argument says to flip the parent window of the
selected window."
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."
(interactive (list (window--rotate-interactive-arg)))
(window--transpose window '(below . left) t))
(window--transpose (or window (window-main-window)) '(below . left) t))
;;;###autoload
(defun window-layout-flip-topdown (&optional window)
"Vertically flip windows under WINDOW.
"Vertically flip WINDOW's child windows.
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
Flip the window layout so that the top window becomes the bottom window,
and vice-versa.
If WINDOW is nil, it defaults to the root window of the selected frame.
Interactively, a prefix argument says to flip the parent window of the
selected window."
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."
(interactive (list (window--rotate-interactive-arg)))
(window--transpose window '(above . right) t))
(window--transpose (or window (window-main-window)) '(above . right) t))
;;;###autoload
(defun window-layout-transpose (&optional window)
"Transpose windows under WINDOW.
"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
Reorganize the windows under WINDOW so that every horizontal split
becomes a vertical split, and vice versa. This is equivalent to
diagonally flipping.
If WINDOW is nil, it defaults to the root window of the selected frame.
Interactively, a prefix argument says to transpose the parent window of
the selected window."
Recursively reorganize WINDOW's child windows so that each horizontal
split becomes a vertical split and vice versa."
(interactive (list (window--rotate-interactive-arg)))
(window--transpose window '(right . below) nil))
(window--transpose (or window (window-main-window)) '(right . below) nil))
;;;###autoload
(defun rotate-windows-back (&optional window)
"Rotate windows under WINDOW backward in cyclic ordering.
If WINDOW is nil, it defaults to the root window of the selected frame.
Interactively, a prefix argument says to rotate the parent window of the
selected window."
(interactive (list (window--rotate-interactive-arg)))
(rotate-windows window t))
(defcustom rotate-windows-change-selected t
"Whether rotating windows will change the selected window.
If this is nil, `rotate-windows' and `rotate-windows-back' do not change
a frame's selected window. Otherwise, the new selected window will be
the window that appears at the location of the previously selected
window."
:type 'boolean
:group 'windows)
;;;###autoload
(defun rotate-windows (&optional window reverse)
"Rotate windows under WINDOW in cyclic ordering.
"Rotate child windows of WINDOW 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 the child
windows of the selected window's parent.
Optional argument REVERSE says to rotate windows backward, in reverse
cyclic order.
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.
If WINDOW is nil, it defaults to the root window of the selected frame.
Rotating windows leaves the way a frame layout has been produced via
splitting and deleting windows unaltered. It only \"moves\" windows
within that layout such that the space formerly occupied by any window
is now occupied by the window preceding (following if REVERSE is
non-nil) it in that order.
Interactively, a prefix argument says to rotate the parent window of the
selected window."
If you want to rotate the entire layout of windows consider using the
function `window-layout-rotate-clockwise' instead."
(interactive (list (window--rotate-interactive-arg)))
(when (or (not window) (window-live-p window))
(setq window (or window (window-main-window)))
(when (window-live-p window)
(user-error "No windows to rotate"))
(let* ((frame (window-frame window))
(selected-window (frame-selected-window window))
(x-y (and rotate-windows-change-selected
(cons (1+ (window-pixel-left selected-window))
(1+ (window-pixel-top selected-window)))))
(win-tree (car (window-tree-normal-sizes window)))
(winls (or
(seq-filter
@ -175,11 +181,12 @@ selected window."
(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")))
(rotated-ls (if reverse
(append (cdr winls) (list (car winls)))
(append (last winls) winls)))
(other-window-arg (if reverse 1 -1))
(first-window (car rotated-ls))
(new-win-tree
;; Recursively process `win-tree' and construct a new tree
@ -193,14 +200,31 @@ selected window."
(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 atom windows"))
(user-error "Cannot rotate windows due to fixed size or atomic windows"))
(delete-other-windows-internal first-window window)
(window--transpose-1 new-win-tree first-window '(below . right) t nil)
(set-frame-selected-window frame selected-window)
(when rotate-windows-change-selected
(other-window other-window-arg)
(while (not (memq (selected-window) winls))
(other-window other-window-arg)))))
(when x-y
;; Try to select the window at the position previously occupied by
;; the selected window.
(let ((window-at-x-y (window-at-x-y (car x-y) (cdr x-y) frame t)))
(when window-at-x-y
(setq selected-window window-at-x-y))))
(set-frame-selected-window frame selected-window)))
;;;###autoload
(defun rotate-windows-back (&optional window)
"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.
If you want to rotate the entire layout of windows, consider using the
function `window-layout-rotate-anticlockwise' instead."
(interactive (list (window--rotate-interactive-arg)))
(rotate-windows window t))
(defun window--transpose (window conf no-resize)
"Rearrange windows under WINDOW recursively.
@ -424,8 +448,7 @@ absolute value of ARG. Return the new frame."
(let ((comb (window-get-split-combination main arg)))
(window-state-put (window-state-get comb)
(window-main-window (make-frame)))
(delete-window comb))
))))
(delete-window comb))))))
(provide 'window-x)
;;; window-x.el ends here

View file

@ -9454,7 +9454,10 @@ windows in the same combination.
Other values are reserved for future use.
A specific split operation may ignore the value of this variable if it
is affected by a non-nil value of `window-combination-limit'. */);
is affected by a non-nil value of `window-combination-limit'. If you
want to use a sequence of `split-window' calls to produce a specific,
predefined layout of windows on a frame, bind this variable temporarily
to nil. */);
Vwindow_combination_resize = Qnil;
DEFVAR_LISP ("window-combination-limit", Vwindow_combination_limit,