diff options
| author | Craig Jennings <c@cjennings.net> | 2026-06-28 12:02:50 -0400 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-06-28 12:02:50 -0400 |
| commit | d5fba0cc6294a9fc17993f7389e0df9f984165fb (patch) | |
| tree | ca16505431c0529d7950e1d6ce39990584f40199 | |
| parent | 16ad2dde87be8d119c85dcb1a70856000603a189 (diff) | |
| download | dotemacs-d5fba0cc6294a9fc17993f7389e0df9f984165fb.tar.gz dotemacs-d5fba0cc6294a9fc17993f7389e0df9f984165fb.zip | |
feat(windows): bind M-arrow to the window pull-away and resize
M-<arrow> now mirrors C-; b <arrow>. From a sole window it pulls a sliver split open on the opposite edge, revealing the previous buffer. In a multi-window layout it nudges the divider via windsize.
cj/window-resize-sticky derives the arrow with event-basic-type, so one command serves both chords. M-up/down were unbound, and M-left/right shed word-motion, which stays on C-left/right. org and other modes that own M-arrow still shadow it, so C-; b remains the universal binding.
| -rw-r--r-- | modules/ui-navigation.el | 14 | ||||
| -rw-r--r-- | tests/test-ui-navigation--window-resize.el | 45 |
2 files changed, 57 insertions, 2 deletions
diff --git a/modules/ui-navigation.el b/modules/ui-navigation.el index cb0fc5697..76dd686a6 100644 --- a/modules/ui-navigation.el +++ b/modules/ui-navigation.el @@ -110,7 +110,9 @@ existing split does. No-op when SIDE is nil." (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>'. +nudging until any other key. Bound to `C-; b <arrow>' and to the global +`M-<arrow>' keys (each direction); the arrow is read with `event-basic-type', +so the Meta modifier on the M-<arrow> path is stripped and both behave alike. When the selected window is the sole window in the frame there is no divider to move, so the first arrow instead splits a sliver away on the @@ -119,13 +121,21 @@ buffer; the current window keeps almost the whole frame and the following arrows shrink it via `windsize', so it reads the same as resizing an existing split." (interactive) - (let ((key (key-description (vector last-command-event)))) + (let ((key (format "<%s>" (event-basic-type last-command-event)))) (if (one-window-p) (cj/window--pull-away (cj/window-pull-side key)) (let ((cmd (keymap-lookup cj/window-resize-map key))) (when cmd (call-interactively cmd))))) (set-transient-map cj/window-resize-map t)) +;; M-<arrow> mirrors `C-; b <arrow>': one chord to pull a split from a sole +;; window or nudge a divider. M-<up>/<down> are otherwise unbound; M-<left>/ +;; <right> shed their word-motion, which stays on `C-<left>'/`C-<right>'. +(keymap-global-set "M-<left>" #'cj/window-resize-sticky) +(keymap-global-set "M-<right>" #'cj/window-resize-sticky) +(keymap-global-set "M-<up>" #'cj/window-resize-sticky) +(keymap-global-set "M-<down>" #'cj/window-resize-sticky) + ;; ------------------------------ Window Splitting ----------------------------- (defun cj/split-and-follow-right () diff --git a/tests/test-ui-navigation--window-resize.el b/tests/test-ui-navigation--window-resize.el index 553219755..b011fb063 100644 --- a/tests/test-ui-navigation--window-resize.el +++ b/tests/test-ui-navigation--window-resize.el @@ -82,5 +82,50 @@ real window split happens under `--batch'." (should (eq (keymap-lookup cj/buffer-and-file-map arrow) #'cj/window-resize-sticky)))) +(ert-deftest test-ui-navigation-window-resize-sticky-meta-arrow-pulls-away () + "Normal: M-<arrow> reaches the same pull-away as the bare arrow. The +direction is derived with `event-basic-type', so the Meta modifier is stripped +and a sole window pulls a sliver to the side opposite the arrow, exactly as the +bare-arrow path does." + (dolist (case '((M-down . above) + (M-up . below) + (M-left . right) + (M-right . left))) + (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))) ; meta stripped, pulled to opposite side + (should overriding-terminal-local-map)))) ; loop armed + +(ert-deftest test-ui-navigation-window-resize-sticky-meta-arrow-resizes () + "Normal: with more than one window, M-<arrow> dispatches the matching +`windsize' command, same as the bare arrow -- the Meta modifier is stripped +before the resize-map lookup." + (dolist (case '((M-left . windsize-left) + (M-right . windsize-right) + (M-up . windsize-up) + (M-down . windsize-down))) + (let ((ran nil) + (overriding-terminal-local-map nil) + (pre-command-hook nil)) + (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) + (should overriding-terminal-local-map)))) + +(ert-deftest test-ui-navigation-window-resize-bound-under-meta-arrow () + "Normal: each global `M-<arrow>' reaches the sticky-resize command." + (dolist (arrow '("M-<left>" "M-<right>" "M-<up>" "M-<down>")) + (should (eq (keymap-lookup (current-global-map) arrow) + #'cj/window-resize-sticky)))) + (provide 'test-ui-navigation--window-resize) ;;; test-ui-navigation--window-resize.el ends here |
