diff options
| -rw-r--r-- | modules/ui-navigation.el | 38 | ||||
| -rw-r--r-- | tests/test-ui-navigation--window-resize.el | 39 |
2 files changed, 69 insertions, 8 deletions
diff --git a/modules/ui-navigation.el b/modules/ui-navigation.el index d8d7162e2..00fa841e1 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 3be0313b8..986ce1580 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) |
