; Touch-ups for new window-x.el

Discussion:
https://lists.gnu.org/archive/html/emacs-devel/2025-01/msg00322.html

* lisp/window-x.el: Autoload commands, provide feature.
(window-tree-normal-sizes): Improve docstring.
(window--window-to-transpose): Remove.
(window--rotate-interactive-arg): New function.
(rotate-window-layout-anticlockwise): Rename to...
(rotate-window-layout-counterclockwise): ...this.
(rotate-window-layout-clockwise)
(flip-window-layout-horizontally)
(flip-window-layout-vertically, transpose-window-layout)
(rotate-windows-back, rotate-windows, window--transpose)
(window--transpose-1): Cosmetics.
This commit is contained in:
Eshel Yaron 2025-01-11 13:57:26 +01:00
parent d00de6f166
commit 7dcc7605d5
No known key found for this signature in database
GPG key ID: EF3EE9CA35D78618

View file

@ -1,4 +1,4 @@
;;; window-x.el --- extended window commands -*- lexical-binding: t; -*-
;;; window-x.el --- Extra window organization commands -*- lexical-binding: t; -*-
;; Copyright (C) 2025 Free Software Foundation, Inc.
@ -25,19 +25,21 @@
;;; Commentary:
;; This file defines additional infrequently used window commands that
;; should not be in window.el to not make the dumped image bigger.
;; This file defines less frequently used window organization commands.
;;; Code:
(defun window-tree-normal-sizes (window &optional next)
"Return normal sizes of all windows rooted at WINDOW.
A list of the form (SPLIT-TYPE PARENT-WIN PARENT-WIN-HEIGHT
PARENT-WIN-WIDTH W1 W2 ...) is returned. SPLIT-TYPE is non-nil if
PARENT-WIN is split horizontally. PARENT-WIN is the internal 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;
PARENT-WIN-HEIGHT and PARENT-WIN-WIDTH are the normal heights of
PARENT-WIN. Wn is a list of the form (WINDOW HEIGHT WIDTH) where HEIGHT
and WIDTH are the normal height and width of the window."
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)"
(let (list)
(while window
(setq list
@ -62,192 +64,159 @@ and WIDTH are the normal height and width of the window."
(setq window (when next (window-next-sibling window))))
(nreverse list)))
(defun window--window-to-transpose (frame-or-window)
"Return the window to be acted upon by `window--transpose'.
If FRAME-OR-WINDOW is a window return FRAME-OR-WINDOW. If
FRAME-OR-WINDOW is a frame, return FRAME-OR-WINDOW's main window. If
FRAME-OR-WINDOW is nil, than the frames main window wil be returned. If
FRAME-OR-WINDOW is non-nil, and not a frame or a window or a number,
than the return value will be the parent window of the selected window."
(cond
((windowp frame-or-window)
frame-or-window)
((or (framep frame-or-window) (not frame-or-window))
(window-main-window frame-or-window))
(frame-or-window
(window-parent))))
(defsubst window--rotate-interactive-arg ()
"Return interative window argument for window rotation commands."
(if current-prefix-arg (window-parent) (window-main-window)))
(defun rotate-window-layout-anticlockwise (&optional frame-or-window)
"Rotate windows of FRAME-OR-WINDOW anticlockwise by 90 degrees.
Transform the layout of windows such that a window on top becomes a
window on the right, a window on the right moves to the bottom, a window
on the bottom moves to the left and a window on the left becomes one on
the top.
;;;###autoload
(defun rotate-window-layout-counterclockwise (&optional window)
"Rotate windows under WINDOW counterclockwise by 90 degrees.
If FRAME-OR-WINDOW is nil, rotate the main window of the selected
frame. If FRAME-OR-WINDOW specifies a live frame, rotate the main
window of that frame. If FRAME-OR-WINDOW specifies a parent window,
rotate that window. In any other case and interactively with a prefix
argument rotate the parent window of the selected window."
(interactive "P")
(let ((window (window--window-to-transpose frame-or-window)))
(window--transpose window '(right . above) nil)))
If WINDOW is nil, it defaults to the root window of the selected frame.
(defun rotate-window-layout-clockwise (&optional frame-or-window)
"Rotate windows of FRAME-OR-WINDOW clockwise by 90 degrees.
Transform the layout of windows such that a window on top becomes a
window on the right, a window on the right moves to the bottom, a
window on the bottom moves to the left and a window on the left becomes
one on the top.
Interactively, a prefix argument says to rotate the parent window of the
selected window."
(interactive (list (window--rotate-interactive-arg)))
(window--transpose window '(right . above) nil))
If FRAME-OR-WINDOW is nil, rotate the main window of the selected frame.
If FRAME-OR-WINDOW specifies a live frame, rotate the main window of
that frame. If FRAME-OR-WINDOW specifies a parent window, rotate that
window. In any other case and interactively with a prefix argument
rotate the parent window of the selected window."
(interactive "P")
(let ((window (window--window-to-transpose frame-or-window)))
(window--transpose window '(left . below) nil)))
;;;###autoload
(defun rotate-window-layout-clockwise (&optional window)
"Rotate windows under WINDOW clockwise by 90 degrees.
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)))
(window--transpose window '(left . below) nil))
;;;###autoload
(defun flip-window-layout-horizontally (&optional window)
"Horizontally flip windows under WINDOW.
(defun flip-window-layout-horizontally (&optional frame-or-window)
"Horizontally flip windows of FRAME-OR-WINDOW.
Flip the window layout so that the window on the right becomes the
window on the left, and vice-versa.
If FRAME-OR-WINDOW is nil, flip the main window of the selected frame.
If FRAME-OR-WINDOW specifies a live frame, rotate the main window of
that frame. If FRAME-OR-WINDOW specifies a parent window, rotate that
window. In any other case and interactively with a prefix argument
rotate the parent window of the selected window."
(interactive "P")
(let ((window (window--window-to-transpose frame-or-window)))
(window--transpose window '(below . left) t)))
If WINDOW is nil, it defaults to the root window of the selected frame.
(defun flip-window-layout-vertically (&optional frame-or-window)
"Verticlly flip windows of FRAME-OR-WINDOW.
Flip the window layout so that the top window becomes the bottom window
Interactively, a prefix argument says to flip the parent window of the
selected window."
(interactive (list (window--rotate-interactive-arg)))
(window--transpose window '(below . left) t))
;;;###autoload
(defun flip-window-layout-vertically (&optional window)
"Vertically flip windows under WINDOW.
Flip the window layout so that the top window becomes the bottom window,
and vice-versa.
If FRAME-OR-WINDOW is nil, flip the main window of the selected frame.
If FRAME-OR-WINDOW specifies a live frame, rotate the main window of
that frame. If FRAME-OR-WINDOW specifies a parent window, rotate that
window. In any other case and interactively with a prefix argument
rotate the parent window of the selected window."
(interactive "P")
(let ((window (window--window-to-transpose frame-or-window)))
(window--transpose window '(above . right) t)))
If WINDOW is nil, it defaults to the root window of the selected frame.
(defun transpose-window-layout (&optional frame-or-window)
"Transpose windows of FRAME-OR-WINDOW.
Make the windows on FRAME-OR-WINDOW so that every horizontal split
Interactively, a prefix argument says to flip the parent window of the
selected window."
(interactive (list (window--rotate-interactive-arg)))
(window--transpose window '(above . right) t))
;;;###autoload
(defun transpose-window-layout (&optional window)
"Transpose windows under 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 FRAME-OR-WINDOW is nil, transpose the main window of the selected frame.
If FRAME-OR-WINDOW specifies a live frame, rotate the main window of
that frame. If FRAME-OR-WINDOW specifies a parent window, rotate that
window. In any other case and interactively with a prefix argument
rotate the parent window of the selected window."
(interactive "P")
(let ((window (window--window-to-transpose frame-or-window)))
(window--transpose window '(right . below) nil)))
If WINDOW is nil, it defaults to the root window of the selected frame.
(defun window--depmap(fun ls)
"Map FUN across all nodes of list LS."
(if (consp ls)
(cons
(if (consp (car ls))
(window--depmap fun (car ls))
(funcall fun (car ls)))
(window--depmap fun (cdr ls)))
(funcall fun ls)))
Interactively, a prefix argument says to transpose the parent window of
the selected window."
(interactive (list (window--rotate-interactive-arg)))
(window--transpose window '(right . below) nil))
(defun rotate-windows-back(&optional frame-or-window)
"Move windows into locations of their predecessors in cyclic ordering.
;;;###autoload
(defun rotate-windows-back (&optional window)
"Rotate windows under WINDOW backward in cyclic ordering.
If FRAME-OR-WINDOW is nil, rotate the main window of the selected frame.
If FRAME-OR-WINDOW specifies a live frame, rotate the main window of
that frame. If FRAME-OR-WINDOW specifies a parent window, rotate that
window. In any other case and interactively with a prefix argument
rotate the parent window of the selected window."
(interactive "P")
(rotate-windows frame-or-window t))
If WINDOW is nil, it defaults to the root window of the selected frame.
(defun rotate-windows (&optional frame-or-window reverse)
"Move windows into locations of their forerunners in cyclic ordering.
Interactively, a prefix argument says to rotate the parent window of the
selected window."
(interactive (list (window--rotate-interactive-arg)))
(rotate-windows window t))
Else if FRAME-OR-WINDOW is nil, rotate the main window of the
selected frame. If FRAME-OR-WINDOW specifies a live frame, rotate the
main window of that frame. If FRAME-OR-WINDOW specifies a parent
window, rotate that window. In any other case and interactively with a
prefix argument rotate the parent window of the selected window."
(interactive "P")
(let ((window (window--window-to-transpose frame-or-window)))
(if (or (not window)
(window-live-p window))
(message "No windows to transpose")
(let* ((frame (window-frame window))
(selected-window (frame-selected-window window))
(win-tree (car (window-tree-normal-sizes window)))
(winls (seq-filter 'window-live-p (flatten-list win-tree)))
(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 (window--depmap
(lambda (x)
(if (window-live-p x)
(pop rotated-ls)
x))
win-tree)))
(if (or (seq-some 'window-atom-root winls)
(seq-some 'window-fixed-size-p winls))
(message "This does not work with fixed size or atom windows.")
(progn
;; All child windows need to be recursively deleted.
(delete-other-windows-internal first-window window)
;; (delete-dups atom-windows)
(window--transpose-1 new-win-tree first-window '(below . right) t nil)
(set-frame-selected-window frame selected-window)
(other-window other-window-arg)
(while (not (memq (selected-window) winls))
(other-window other-window-arg))))))))
;;;###autoload
(defun rotate-windows (&optional window reverse)
"Rotate windows under WINDOW in cyclic ordering.
Optional argument REVERSE says to rotate windows backward, in reverse
cyclic order.
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)))
(when (or (not window) (window-live-p window))
(user-error "No windows to transpose"))
(let* ((frame (window-frame window))
(selected-window (frame-selected-window window))
(win-tree (car (window-tree-normal-sizes window)))
(winls (seq-filter #'window-live-p (flatten-list win-tree)))
(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
;; with the same shape and rotated windows at the leaves.
(named-let rec ((tree win-tree))
(cond
((consp tree) (cons (rec (car tree)) (rec (cdr 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 atom windows"))
;; All child windows need to be recursively deleted.
(delete-other-windows-internal first-window window)
;; (delete-dups atom-windows)
(window--transpose-1 new-win-tree first-window '(below . right) t nil)
(set-frame-selected-window frame selected-window)
(other-window other-window-arg)
(while (not (memq (selected-window) winls))
(other-window other-window-arg))))
(defun window--transpose (window conf no-resize)
"Rearrange windows of WINDOW recursively.
CONF should be a cons cell: (HORIZONTAL-SPLIT . VERTICAL-SPLIT) where
"Rearrange windows under WINDOW recursively.
CONF should be a cons cell (HORIZONTAL-SPLIT . VERTICAL-SPLIT) where
HORIZONTAL-SPLIT will be used as the third argument of `split-window'
when splitting a window that was previously horizontally split, and
VERTICAL-SPLIT as third argument of `split-window' for a window that was
previously vertically split. If NO-RESIZE is nil, the SIDE argument of
the window-split is converted from vertical to horizontal or vice versa,
with the same proportion of the total split."
(if (or (not window)
(window-live-p window))
(message "No windows to transpose")
(let* ((frame (window-frame window))
(first-window window)
(selected-window (frame-selected-window window))
(win-tree (car (window-tree-normal-sizes window)))
(win-list (seq-filter 'window-live-p (flatten-list win-tree)))
(atom-windows
(remq nil (mapcar 'window-atom-root
win-list))))
(if (and (not (eq (car atom-windows) window))
(or no-resize
(and (not atom-windows)
(not (seq-some 'window-fixed-size-p win-list)))))
(progn
(delete-dups atom-windows)
(while (not (window-live-p first-window))
(setq first-window (window-child first-window)))
(delete-other-windows-internal first-window window)
(window--transpose-1 win-tree first-window conf no-resize atom-windows)
;; Go back to previously selected window.
(set-frame-selected-window frame selected-window)
(mapc 'window-make-atom atom-windows))
(message "This does not work with fixed size or atom windows.")))))
(when (or (not window) (window-live-p window))
(user-error "No windows to transpose"))
(let* ((frame (window-frame window))
(first-window window)
(selected-window (frame-selected-window window))
(win-tree (car (window-tree-normal-sizes window)))
(win-list (seq-filter #'window-live-p (flatten-list win-tree)))
(atom-windows (seq-keep #'window-atom-root win-list)))
(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"))
(delete-dups atom-windows)
(while (not (window-live-p first-window))
(setq first-window (window-child first-window)))
(delete-other-windows-internal first-window window)
(window--transpose-1 win-tree first-window conf no-resize atom-windows)
;; Go back to previously selected window.
(set-frame-selected-window frame selected-window)
(mapc #'window-make-atom atom-windows)))
(defun window--transpose-1 (subtree cwin conf no-resize atom-windows)
"Subroutine of `window--transpose'.
@ -259,8 +228,7 @@ ones in `window--transpose'."
;; `flen' is max size the window could be converted to the opposite
;; of the given split type.
(let ((parent-window-is-set t)
(flen (if (funcall (if no-resize 'not 'identity)
(car subtree))
(flen (if (xor no-resize (car subtree))
(float (window-pixel-width cwin))
(float (window-pixel-height cwin)))))
(mapc
@ -268,7 +236,7 @@ ones in `window--transpose'."
(prog1
(let* ((split-size (- (round (* flen size))))
(split-type
(funcall (if (car subtree) 'car 'cdr) conf))
(funcall (if (car subtree) #'car #'cdr) conf))
(return-win
(if (listp window)
;; `window' is a window subtree.
@ -293,9 +261,7 @@ ones in `window--transpose'."
(if window-combination-limit
(cons (caar (cddddr first-child)) (cadr subtree))
(caar (cddddr first-child)))))
(if is-atom
'(nil . t)
conf)
(if is-atom '(nil . t) conf)
no-resize
atom-windows))
;; `window' is a window.
@ -323,14 +289,16 @@ ones in `window--transpose'."
(if (car subtree)
(cadr window-size-info)
(caddr window-size-info)))))
;; We need to ingore first 5 elements of window list, we ignore
;; We need to ignore first 5 elements of window list, we ignore
;; window split type, sizes and the first window (it's
;; implicitly created). We just have a list of windows.
(nreverse (cdr (cddddr subtree)))))
;; (caar (cddddr subtree)) is the first child window of subtree.
(unless (windowp (caar (cddddr subtree)))
(let ((is-atom (memq (cadr (cadr (cddddr subtree))) atom-windows)))
(window--transpose-1 (car (cddddr subtree)) cwin (if is-atom '(nil . t) conf)
(window--transpose-1 (car (cddddr subtree)) cwin
(if is-atom '(nil . t) conf)
no-resize atom-windows)))))
(provide 'window-x)
;;; window-x.el ends here