aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-06-20 10:34:45 -0400
committerCraig Jennings <c@cjennings.net>2026-06-20 10:34:45 -0400
commitc152aa1fe95845bb2fe8dc59098e1925c30eb6e5 (patch)
tree74413fbcc59ea23afdb973a9195c40815aeda81f
parent5fd29d8dc004dcbd0942e1ac990ddc9ce67d8ea7 (diff)
downloaddotemacs-c152aa1fe95845bb2fe8dc59098e1925c30eb6e5.tar.gz
dotemacs-c152aa1fe95845bb2fe8dc59098e1925c30eb6e5.zip
feat(windows): pull a window away from a sole window with C-; b + arrow
When the selected window fills the frame there is no divider to resize, so the arrow now splits toward its direction with the previous buffer and the original window shrinks from that edge. Multi-window resize is unchanged.
-rw-r--r--modules/ui-navigation.el38
-rw-r--r--tests/test-ui-navigation--window-resize.el39
2 files changed, 69 insertions, 8 deletions
diff --git a/modules/ui-navigation.el b/modules/ui-navigation.el
index d8d7162e..00fa841e 100644
--- a/modules/ui-navigation.el
+++ b/modules/ui-navigation.el
@@ -75,14 +75,42 @@ resize -- each moves the active window's divider in the arrow's direction
"<up>" #'windsize-up
"<down>" #'windsize-down)
+(defun cj/window-arrow-direction (key)
+ "Map a windsize arrow KEY description to a split direction.
+KEY is one of \"<left>\" \"<right>\" \"<up>\" \"<down>\"; returns
+left/right/above/below respectively, or nil for anything else."
+ (pcase key
+ ("<left>" 'left)
+ ("<right>" 'right)
+ ("<up>" 'above)
+ ("<down>" 'below)
+ (_ nil)))
+
+(defun cj/window--pull-away (direction)
+ "Split the sole window toward DIRECTION and reveal the previous buffer.
+DIRECTION is one of left/right/above/below. A new window opens on that
+side showing `other-buffer'; focus stays on the original window so it
+shrinks from that edge, letting a fullscreen window (e.g. a terminal)
+share the frame. No-op when DIRECTION is nil."
+ (when direction
+ (let ((new (split-window (selected-window) nil direction)))
+ (set-window-buffer new (other-buffer (current-buffer) t))
+ new)))
+
(defun cj/window-resize-sticky ()
"Resize the active window's divider in the just-pressed arrow's direction
-(via `windsize'), then keep `cj/window-resize-map' active so bare arrows keep
-nudging until any other key. Bound to `C-; b <left>/<right>/<up>/<down>'."
+\(via `windsize'), then keep `cj/window-resize-map' active so bare arrows keep
+nudging until any other key. Bound to `C-; b <left>/<right>/<up>/<down>'.
+
+When the selected window is the sole window in the frame there is no
+divider to move, so the arrow instead pulls a new window away toward that
+edge (`cj/window--pull-away'), revealing the previous buffer."
(interactive)
- (let ((cmd (keymap-lookup cj/window-resize-map
- (key-description (vector last-command-event)))))
- (when cmd (call-interactively cmd)))
+ (let ((key (key-description (vector last-command-event))))
+ (if (one-window-p)
+ (cj/window--pull-away (cj/window-arrow-direction key))
+ (let ((cmd (keymap-lookup cj/window-resize-map key)))
+ (when cmd (call-interactively cmd)))))
(set-transient-map cj/window-resize-map t))
;; ------------------------------ Window Splitting -----------------------------
diff --git a/tests/test-ui-navigation--window-resize.el b/tests/test-ui-navigation--window-resize.el
index 3be0313b..986ce158 100644
--- a/tests/test-ui-navigation--window-resize.el
+++ b/tests/test-ui-navigation--window-resize.el
@@ -24,8 +24,11 @@
(should (eq (keymap-lookup cj/window-resize-map "<down>") #'windsize-down)))
(ert-deftest test-ui-navigation-window-resize-sticky-dispatches-and-arms ()
- "Normal: `cj/window-resize-sticky' runs the `windsize' command matching the
-arrow key that triggered it, then arms the sticky-repeat map."
+ "Normal: with more than one window, `cj/window-resize-sticky' runs the
+`windsize' command matching the arrow key that triggered it, then arms the
+sticky-repeat map. `one-window-p' is forced nil so the resize path is taken
+deterministically -- in `--batch' the sole frame is one-window-p, which would
+otherwise route to the pull-away path."
(dolist (case '((left . windsize-left)
(right . windsize-right)
(up . windsize-up)
@@ -33,13 +36,43 @@ arrow key that triggered it, then arms the sticky-repeat map."
(let ((ran nil)
(overriding-terminal-local-map nil)
(pre-command-hook nil))
- (cl-letf (((symbol-function (cdr case))
+ (cl-letf (((symbol-function 'one-window-p) (lambda (&rest _) nil))
+ ((symbol-function (cdr case))
(lambda (&rest _) (interactive) (setq ran t))))
(let ((last-command-event (car case)))
(cj/window-resize-sticky)))
(should ran) ; dispatched to the right command
(should overriding-terminal-local-map)))) ; loop armed
+(ert-deftest test-ui-navigation-window-arrow-direction ()
+ "Normal/Error: each arrow maps to its split direction; anything else is nil."
+ (should (eq (cj/window-arrow-direction "<left>") 'left))
+ (should (eq (cj/window-arrow-direction "<right>") 'right))
+ (should (eq (cj/window-arrow-direction "<up>") 'above))
+ (should (eq (cj/window-arrow-direction "<down>") 'below))
+ (should (null (cj/window-arrow-direction "<prior>")))
+ (should (null (cj/window-arrow-direction "x"))))
+
+(ert-deftest test-ui-navigation-window-resize-sticky-sole-window-pulls-away ()
+ "Normal: with a single window, the arrow pulls a window away toward its
+direction (via `cj/window--pull-away') rather than resizing, then arms the
+loop. `cj/window--pull-away' is stubbed to capture the direction so no real
+window split happens under `--batch'."
+ (dolist (case '((left . left)
+ (right . right)
+ (up . above)
+ (down . below)))
+ (let ((pulled nil)
+ (overriding-terminal-local-map nil)
+ (pre-command-hook nil))
+ (cl-letf (((symbol-function 'one-window-p) (lambda (&rest _) t))
+ ((symbol-function 'cj/window--pull-away)
+ (lambda (dir) (setq pulled dir))))
+ (let ((last-command-event (car case)))
+ (cj/window-resize-sticky)))
+ (should (eq pulled (cdr case))) ; pulled toward the arrow
+ (should overriding-terminal-local-map)))) ; loop armed
+
(ert-deftest test-ui-navigation-window-resize-bound-under-c-semicolon-b ()
"Normal: `C-; b <arrow>' (each direction) reaches the sticky-resize command."
(require 'custom-buffer-file)