aboutsummaryrefslogtreecommitdiff
path: root/modules/cj-window-geometry.el
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-05-09 14:48:01 -0500
committerCraig Jennings <c@cjennings.net>2026-05-09 14:48:01 -0500
commit7ad613f96380319c037f367a1b6b1beda03846ca (patch)
treeb442ddc79de803e042505df84ff1d8acb8c812cb /modules/cj-window-geometry.el
parenta29ac8d9f31443279ba5897b13cf5cda49519975 (diff)
downloaddotemacs-7ad613f96380319c037f367a1b6b1beda03846ca.tar.gz
dotemacs-7ad613f96380319c037f367a1b6b1beda03846ca.zip
refactor: extract window-geometry helpers shared by F9 and F12
`ai-vterm.el` (F9) and `eshell-vterm-config.el` (F12) both grew the same geometry-preservation pattern: classify a window's position, capture its body size, map cardinal direction to its frame-edge variant. The shared helpers were sitting as near-duplicates in both modules. With two real consumers established, the abstraction has the right shape. I pulled them into `cj-window-geometry.el`. The new module exposes three pure helpers: - `cj/window-direction` returns right/below/left/above based on edges relative to `frame-root-window`. Takes an optional DEFAULT for the single-window-frame fallback so each consumer picks its own (ai-vterm wants 'right, vterm-toggle wants 'below). - `cj/window-body-size` returns body-cols (right/left) or body-lines (below/above). Same body-vs-total reasoning as before: divider-independent, matches what the user sees. - `cj/cardinal-to-edge-direction` maps right/left/below/above to rightmost/leftmost/bottom/top, used by each consumer's `display-saved` action. `ai-vterm.el` and `eshell-vterm-config.el` now `(require 'cj-window-geometry)` and call the shared helpers directly. The consumer-specific `capture-state` and `display-saved` bodies stay in each module because they bind to consumer-specific state vars. Extracting those would either need parameter-passing-via-symbol or a macro, both heavier than the duplication they would remove. Tests: 15 in `test-cj-window-geometry.el` covering all four directions, body-size on both axes, cardinal-to-edge mapping, default-arg fallback, and the unknown-direction nil case. Deleted `test-ai-vterm--window-geometry.el` (now redundant) and trimmed four duplicate window-direction/size tests from `test-vterm-toggle--display.el`. Net LOC: each consumer ~40-50 lines lighter, with the new module + tests paying roughly half that back. Full make test green. make validate-modules green.
Diffstat (limited to 'modules/cj-window-geometry.el')
-rw-r--r--modules/cj-window-geometry.el81
1 files changed, 81 insertions, 0 deletions
diff --git a/modules/cj-window-geometry.el b/modules/cj-window-geometry.el
new file mode 100644
index 00000000..88fa83d4
--- /dev/null
+++ b/modules/cj-window-geometry.el
@@ -0,0 +1,81 @@
+;;; cj-window-geometry.el --- Pure window-geometry helpers -*- lexical-binding: t; -*-
+
+;; Author: Craig Jennings <c@cjennings.net>
+
+;;; Commentary:
+
+;; Pure helpers for classifying a window's position in its frame and
+;; computing body sizes. Shared between `ai-vterm.el' (F9 dispatch)
+;; and `eshell-vterm-config.el' (F12 dispatch); the geometry-
+;; preservation pattern in both modules captures direction + body
+;; size at toggle-off and replays them on the next toggle-on.
+;;
+;; All functions are pure: they read window/frame edges and return
+;; classifications. No side effects, no state. Consumers wrap them
+;; with consumer-specific state vars and display logic.
+
+;;; Code:
+
+(defun cj/window-direction (window &optional default)
+ "Return the side WINDOW occupies in its frame.
+
+Returns one of right, below, left, above. Falls back to DEFAULT
+(or right when DEFAULT is nil) when WINDOW fills its frame's
+root area. Comparison uses `frame-root-window' edges so the
+minibuffer doesn't make every full-area window look like it
+fails to span the full height."
+ (let* ((root (frame-root-window (window-frame window)))
+ (edges (window-edges window))
+ (root-edges (window-edges root))
+ (left (nth 0 edges))
+ (top (nth 1 edges))
+ (right (nth 2 edges))
+ (bottom (nth 3 edges))
+ (root-left (nth 0 root-edges))
+ (root-top (nth 1 root-edges))
+ (root-right (nth 2 root-edges))
+ (root-bottom (nth 3 root-edges))
+ (spans-full-width (and (= left root-left) (= right root-right)))
+ (spans-full-height (and (= top root-top) (= bottom root-bottom))))
+ (cond
+ ((not spans-full-width) (if (= left root-left) 'left 'right))
+ ((not spans-full-height) (if (= top root-top) 'above 'below))
+ (t (or default 'right)))))
+
+(defun cj/window-body-size (window direction)
+ "Return WINDOW's body size on the axis matching DIRECTION.
+
+Returns body-width (columns) when DIRECTION is right or left.
+Returns body-height (lines) when DIRECTION is below or above.
+
+Body size, not total size, is the right thing to capture for
+geometry replay: total-width includes the right-side divider when
+the window has a right sibling but excludes it at the frame edge,
+so a captured rightmost window replayed into a middle position
+would leave the body 1 col short. Body size is divider-
+independent and matches what the user actually sees."
+ (if (memq direction '(right left))
+ (window-body-width window)
+ (window-body-height window)))
+
+(defun cj/cardinal-to-edge-direction (direction)
+ "Map cardinal DIRECTION to its `display-buffer-in-direction' edge variant.
+
+Returns rightmost/leftmost/bottom/top for right/left/below/above
+respectively. Returns nil for any other input.
+
+The edge variants route splits relative to the frame's main
+window rather than the selected window, so a toggle-on lands at
+the same frame edge regardless of which window is selected. The
+cardinal variants would split the selected window's tree branch
+instead, putting the new window mid-frame in multi-window
+layouts."
+ (pcase direction
+ ('right 'rightmost)
+ ('left 'leftmost)
+ ('below 'bottom)
+ ('above 'top)
+ (_ nil)))
+
+(provide 'cj-window-geometry)
+;;; cj-window-geometry.el ends here