summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-07-26 23:50:25 -0300
committerGabriel Schneider <[email protected]>2026-08-01 15:02:07 -0300
commita1f26fc4977b185cbd603749157360c011452e5e (patch)
tree5702b7316227e1714ae29885f445b2a2f3b00929
parentd9da3ce9e87cf4c43bf34664031f090e6dde2410 (diff)
downloadpardes-a1f26fc4977b185cbd603749157360c011452e5e.tar.gz
pardes-a1f26fc4977b185cbd603749157360c011452e5e.zip
window navigation builtins: SPC w h/j/k/l focus, SPC w t file<->terminal
Left/Down/Up/Right run the existing focusDir (now taking the pane the builtin ran on, so an executed name acts on ITS pane); the Ctrl-w prefix routes through the same four builtins instead of calling focusDir itself. Toggleterm hops between the most recently focused document and terminal off focus_hist, the MRU the core already rebuilds each sync. Ctrl-w stays: a tty-mode pane never sees SPC, so it is the only keyboard way out of one. Its snapshot step now moves focus somewhere (c-w l), which the old c-w h in that layout did not.
-rw-r--r--docs/helix-keys.md4
-rw-r--r--src/pardes.zig86
-rw-r--r--src/tutor.txt10
-rw-r--r--test/snapshots/leader.golden45
-rw-r--r--test/snapshots/leader.snap8
-rw-r--r--test/snapshots/windownav.golden310
-rw-r--r--test/snapshots/windownav.snap54
-rw-r--r--test/snapshots/windowops.golden14
-rw-r--r--test/snapshots/windowops.snap10
9 files changed, 514 insertions, 27 deletions
diff --git a/docs/helix-keys.md b/docs/helix-keys.md
index c109fc48..735dd4d4 100644
--- a/docs/helix-keys.md
+++ b/docs/helix-keys.md
@@ -87,10 +87,10 @@ with a reason in `test/hxcases/waivers.jsonl`.
| `Esc` (insert) | back to normal mode, cursor right after the insertion (no vim left-step) | | helix-verified |
| `Enter` (normal) | acme **look** chord: EXPLICIT selection, else file-ish word under cursor | pardes-specific, keep (helix normal-mode Enter unbound). Covers helix `gf`. Implicit motion residue falls back to the cursor word | pardes-specific |
| `Tab` (normal) | acme **execute** chord | pardes-specific, keep; explicit-selection rule as Enter | pardes-specific |
-| `Ctrl-w` + `h/j/k/l`/arrows | directional pane focus prefix — normal/tty modes only | pardes' own window handling (helix window mode skipped, section C). Insert mode owns `Ctrl-w` = delete-word-back; `Ctrl-w` while editing a tag (tags are always insert) is a no-op | pardes-specific |
+| `Ctrl-w` + `h/j/k/l`/arrows | directional pane focus prefix — normal/tty modes only | pardes' own window handling (helix window mode skipped, section C). Runs the SAME `Left`/`Down`/`Up`/`Right` builtins `SPC w h/j/k/l` runs; kept alongside the leader because a pane in raw **tty** mode never sees `SPC` (the shell owns it), so this is the only keyboard way out of one. Insert mode owns `Ctrl-w` = delete-word-back; `Ctrl-w` while editing a tag (tags are always insert) is a no-op | pardes-specific |
| `Alt-n` | new terminal below (any mode) | shadows helix `Alt-n` TS sibling-select — skipped anyway (tree-sitter) | pardes-specific |
| `Alt-c` | move active terminal to a fresh column (any mode) | helix `Alt-c` is change-noyank; the pardes window op wins (do-not-touch contract). `Alt-d` + `i` covers the behavior | waived (`alt-c-window-op`) |
-| `Space` (normal, body) | the pardes LEADER: a key path from here runs a BUILTIN with no arguments — the same builtins the topbar and the tags hold. `SPC k` Kill, `SPC d` Del, `SPC f s` Save, `SPC f f` Find (fd over the pane's directory, results into `+Search`), `SPC h t` Tutor, `SPC c n`/`SPC c d` Newcol/Delcol, `SPC t d/c/n/r` Debug/Colors/NextColor/Crt, `SPC s d`/`SPC s r` Dump/Restore. `?` at ANY depth opens the Help builtin listing what the prefix can still reach (`SPC ?` = all, `SPC h ?` = the docs group), into a `+Help` output buffer. The pending path shows at the right edge of the active pane's tag; Esc — or any unmapped key — abandons it | helix spends Space on pickers/LSP (section C); pardes has neither, and acme's builtins are what a leader is for. Enum-derived: the paths, the Help lines and the execute dispatch all fold out of one `Builtin` enum at comptime. Normal mode on a BODY only — tags are always insert, tty keys belong to the program | pardes-specific |
+| `Space` (normal, body) | the pardes LEADER: a key path from here runs a BUILTIN with no arguments — the same builtins the topbar and the tags hold. `SPC k` Kill, `SPC d` Del, `SPC f s` Save, `SPC f f` Find (fd over the pane's directory, results into `+Search`), `SPC h t` Tutor, `SPC c n`/`SPC c d` Newcol/Delcol, `SPC t d/c/n/r` Debug/Colors/NextColor/Crt, `SPC s d`/`SPC s r` Dump/Restore, `SPC w h/j/k/l` Left/Down/Up/Right (directional pane focus — the `Ctrl-w` prefix's four moves as builtins), `SPC w t` Toggleterm (focus hops between the most recently focused document — file, image or `+Search`/`+Help` buffer — and the most recently focused terminal; the same key hops back). `?` at ANY depth opens the Help builtin listing what the prefix can still reach (`SPC ?` = all, `SPC h ?` = the docs group), into a `+Help` output buffer. The pending path shows at the right edge of the active pane's tag; Esc — or any unmapped key — abandons it | helix spends Space on pickers/LSP (section C); pardes has neither, and acme's builtins are what a leader is for. Enum-derived: the paths, the Help lines and the execute dispatch all fold out of one `Builtin` enum at comptime. Normal mode on a BODY only — tags are always insert, tty keys belong to the program | pardes-specific |
| `/` then `n`/`N` (any pane) | pardes' own plain-substring search into a `+Search` output buffer, n/N walk the result rows and look them (EXPLICIT selections) | KEEP, do not touch; not in the corpus (helix `/` is regex search) | pardes-specific |
| `n`/`N` (terminal panes, no search armed) | lookable-token motion over scrollback (EXPLICIT selections) | KEEP, do not touch | pardes-specific |
| insert: printable text | file: real edit; terminal: typed run splice | | helix-verified (file) |
diff --git a/src/pardes.zig b/src/pardes.zig
index fadaf3cc..0725e8ab 100644
--- a/src/pardes.zig
+++ b/src/pardes.zig
@@ -71,6 +71,17 @@ const Builtin = enum {
Del,
Delcol,
Help,
+ // the window group. The DESTINATION is the name — a word, the way a tag
+ // holds Del or Save — because these names live in the same vocabulary as
+ // everything else here: `Wh` would be a leader key path leaking into the
+ // text you can middle-click. Plain English words are safe for exactly
+ // these five: focus is the cheapest thing to change by accident (nothing
+ // is edited, closed or written) and the way back is the opposite word.
+ Left,
+ Down,
+ Up,
+ Right,
+ Toggleterm,
};
/// SPC leader: ONE key path per builtin, the whole remapping surface. An
@@ -95,6 +106,14 @@ const leader_path = std.EnumArray(Builtin, []const u8).init(.{
.Crt = "tr",
.Dump = "sd",
.Restore = "sr",
+ // the `w` window group `Save` vacated: the four directional focus moves
+ // the Ctrl-w prefix does, spelled h/j/k/l because focus IS a motion, plus
+ // `t` for the file<->terminal hop.
+ .Left = "wh",
+ .Down = "wj",
+ .Up = "wk",
+ .Right = "wl",
+ .Toggleterm = "wt",
});
/// The leader tree, flattened: one row per builtin SORTED BY PATH, so every
@@ -139,7 +158,7 @@ const leader_rows: [@typeInfo(Builtin).@"enum".fields.len]Leader = blk: {
// snapshot golden records the column each word starts at). Comptime-checked
// against the enum so a rename cannot silently rot it.
comptime {
- @setEvalBranchQuota(4000); // the tokenizer walks the string one branch per byte
+ @setEvalBranchQuota(8000); // the tokenizer walks the string one branch per byte, stringToEnum builds a map per name
var it = std.mem.tokenizeScalar(u8, topbar_str, ' ');
while (it.next()) |w| {
if (std.meta.stringToEnum(Builtin, w) == null) @compileError("topbar word is not a builtin: " ++ w);
@@ -1377,11 +1396,14 @@ pub const Pardes = struct {
// before Ctrl-w, so a modified key abandons the sequence instead of
// arming a second prefix on top of it
if (p.leader_on) return p.leaderKey(key);
- // Ctrl-w prefix: helix-style directional pane focus (h/j/k/l)
+ // Ctrl-w prefix: helix-style directional pane focus (h/j/k/l), running
+ // the SAME builtins `SPC w h/j/k/l` runs. It stays despite the leader
+ // covering it because it reaches one place the leader cannot: a pane
+ // in raw tty mode never sees SPC (the shell owns every printable key),
+ // so this is the only keyboard way out of one.
if (p.ctrl_w_pending) {
p.ctrl_w_pending = false;
- if (is(key, 'h') or is(key, Key.left)) p.focusDir(.left) else if (is(key, 'l') or is(key, Key.right)) p.focusDir(.right) else if (is(key, 'k') or is(key, Key.up)) p.focusDir(.up) else if (is(key, 'j') or is(key, Key.down)) p.focusDir(.down);
- if (p.panes[p.active]) |nt| nt.pending = 0;
+ if (is(key, 'h') or is(key, Key.left)) p.runBuiltin(.Left, p.active, "") else if (is(key, 'l') or is(key, Key.right)) p.runBuiltin(.Right, p.active, "") else if (is(key, 'k') or is(key, Key.up)) p.runBuiltin(.Up, p.active, "") else if (is(key, 'j') or is(key, Key.down)) p.runBuiltin(.Down, p.active, "");
return;
}
// insert mode owns Ctrl-w (delete word backward, helix); the focus
@@ -1571,13 +1593,15 @@ pub const Pardes = struct {
pane.pending = 0;
}
- /// move focus to the nearest pane in `dir` (overlap-aware nearest edge)
- fn focusDir(p: *Pardes, dir: enum { left, right, up, down }) void {
- const a = p.rects[p.active];
+ /// move focus to the nearest pane in `dir` of `from` (overlap-aware
+ /// nearest edge). `from` is the pane the builtin ran on, which is the
+ /// active one for a key but the CLICKED one for a name executed in a tag.
+ fn focusDir(p: *Pardes, from: usize, dir: enum { left, right, up, down }) void {
+ const a = p.rects[from];
var best: ?usize = null;
var best_d: i32 = 0;
for (p.panes, 0..) |slot, i| {
- if (slot == null or i == p.active) continue;
+ if (slot == null or i == from) continue;
const r = p.rects[i];
const vov = a.y < r.y + r.h and r.y < a.y + a.h;
const hov = a.x < r.x + r.w and r.x < a.x + a.w;
@@ -1599,7 +1623,11 @@ pub const Pardes = struct {
best_d = d;
}
}
- if (best) |b| p.active = b;
+ if (best) |b| {
+ p.active = b;
+ // a count typed before the hop was meant for the pane you left
+ p.panes[b].?.pending = 0;
+ }
}
// ---- move-drag placement ----
@@ -4681,7 +4709,8 @@ pub const Pardes = struct {
/// Run a builtin on pane `id`. `txt` is the executed text — only Restore
/// reads it (for its optional path argument), so the leader passes "" and
/// every builtin runs with zero arguments. The topbar ones are global; the
- /// pane-scoped ones (Save, Del, Delcol) act on `id`.
+ /// pane-scoped ones (Save, Del, Delcol, and the window group, which moves
+ /// focus relative to `id`) act on `id`.
fn runBuiltin(p: *Pardes, b: Builtin, id: usize, txt: []const u8) void {
const pane = p.panes[id] orelse return;
switch (b) {
@@ -4752,6 +4781,43 @@ pub const Pardes = struct {
const nt = p.openTutorView(free) catch return;
p.placeDoc(id, free, nt); // a doc like any other
},
+ .Left => p.focusDir(id, .left),
+ .Down => p.focusDir(id, .down),
+ .Up => p.focusDir(id, .up),
+ .Right => p.focusDir(id, .right),
+ // The file<->terminal hop. "Latest" is already recorded: focus_hist
+ // is the MRU sync() rebuilds every update (most recent last), the
+ // same list a closing pane hands focus back through — so this walks
+ // it instead of keeping a second one. Which side is which: only a
+ // shell is a terminal; a file, an image and an output buffer
+ // (+Search/+Help) are all DOCS you read, so isTerminal is the whole
+ // test. Landing pushes this pane to the top of that same history,
+ // which is why the hop back is the same key.
+ .Toggleterm => {
+ const want_term = !pane.isTerminal();
+ var t: ?usize = null;
+ // any live pane of the other kind: a pane you have never
+ // focused (the file you started with) is in no history at all
+ for (p.panes, 0..) |slot, k| {
+ const op = slot orelse continue;
+ if (k != id and op.isTerminal() == want_term) t = k;
+ }
+ // ...but the most recently focused one wins
+ var i = p.nfocus;
+ while (i > 0) {
+ i -= 1;
+ const hid = p.focus_hist[i];
+ const hp = p.panes[hid] orelse continue;
+ if (hid != id and hp.isTerminal() == want_term) {
+ t = hid;
+ break;
+ }
+ }
+ if (t) |target| {
+ p.active = target;
+ p.panes[target].?.pending = 0;
+ }
+ },
}
}
diff --git a/src/tutor.txt b/src/tutor.txt
index f2954667..8124cd42 100644
--- a/src/tutor.txt
+++ b/src/tutor.txt
@@ -450,7 +450,9 @@
Ctrl-f full page down. (Ctrl-b pages up on a file pane;
on a terminal it is the default tty toggle. If you
start with --tty-toggle=g, Ctrl-g takes that role.)
- Ctrl-w h/j/k/l focus the pane left/down/up/right
+ Ctrl-w h/j/k/l focus the pane left/down/up/right (SPC w h/j/k/l
+ does the same; Ctrl-w also reaches a tty pane,
+ where SPC belongs to the shell)
Alt-n new terminal below the active one
Alt-c move the active terminal into a fresh column
@@ -473,6 +475,9 @@
SPC c n / c d Newcol / Delcol
SPC t d/c/n/r Debug / Colors / NextColor / Crt toggles
SPC s d / s r Dump / Restore the session
+ SPC w h/j/k/l Left/Down/Up/Right: focus the pane that way
+ SPC w t Toggleterm: hop between the last document you
+ looked at and the last terminal, and back
`?` works at ANY depth: SPC ? lists everything, SPC h ? lists only
what the "h" group holds. Help writes into a "+Help" OUTPUT BUFFER,
@@ -504,7 +509,8 @@
: = the tag as a command line (motions, Enter, back)
zt zz zb Ctrl-d/u Ctrl-f Ctrl-w hjkl Alt-n/c
SPC = the leader: a key path runs a builtin (SPC ? lists
- them; SPC k Kill, SPC d Del, SPC f s Save, SPC f f Find)
+ them; SPC k Kill, SPC d Del, SPC f s Save, SPC f f Find,
+ SPC w hjkl focus, SPC w t file <-> terminal)
vs Helix: no multi-cursor; selection is LINE-first (x), plus v chars.
vs Vim: no verb+noun (dw); motions only move; Esc never reaches tty.
diff --git a/test/snapshots/leader.golden b/test/snapshots/leader.golden
index 201888ec..47b1cb4e 100644
--- a/test/snapshots/leader.golden
+++ b/test/snapshots/leader.golden
@@ -202,7 +202,7 @@
| 15 SPC t d Debug
| 16 SPC t n NextColor
| 17 SPC t r Crt
-| 18
+| 18 SPC w h Left
== snap help-group grid=100x40 cursor=7,22
|Kill Newcol Tutor Debug Colors NextColor Crt Dump
| NOR /tmp/pardes-snap/leader/cwd/cmds.txt Save De NOR /tmp/pardes-snap/leader/cwd Del
@@ -366,7 +366,48 @@
| 15 SPC t d Debug
| 16 SPC t n NextColor
| 17 SPC t r Crt
-| 18
+| 18 SPC w h Left
+== snap help-window grid=100x40 cursor=7,22
+|Kill Newcol Tutor Debug Colors NextColor Crt Dump
+| NOR /tmp/pardes-snap/leader/cwd/cmds.txt Save De NOR /tmp/pardes-snap/leader/cwd Del
+| 1 Help
+| cmds.txt
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+| NOR /tmp/pardes-snap/leader/cwd/+Help Del
+| 1 pardes builtins under SPC w
+| 2
+| 3 SPC w h Left
+| 4 SPC w j Down
+| 5 SPC w k Up
+| 6 SPC w l Right
+| 7 SPC w t Toggleterm
+| 8
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
== snap esc-del grid=100x40 cursor=7,2
|Kill Newcol Tutor Debug Colors NextColor Crt Dump
| NOR /tmp/pardes-snap/leader/cwd/cmds.txt Save De NOR /tmp/pardes-snap/leader/cwd Del
diff --git a/test/snapshots/leader.snap b/test/snapshots/leader.snap
index 26d11cd7..0d2dc423 100644
--- a/test/snapshots/leader.snap
+++ b/test/snapshots/leader.snap
@@ -62,6 +62,14 @@ key ?
wait 10000 SPC s r
stable 700 15000
snap help-all
+# `SPC w ?`: the window group, whose five builtins are the tail of the full
+# listing above and fall off the bottom of this pane there
+key space
+key w
+key ?
+wait 10000 Toggleterm
+stable 700 15000
+snap help-window
# Esc in the BODY of an output buffer IS Del: +Help has no file behind it and
# nothing worth keeping, so the key that elsewhere only drops a selection
# dismisses the whole pane and hands its height back to the pane above.
diff --git a/test/snapshots/windownav.golden b/test/snapshots/windownav.golden
new file mode 100644
index 00000000..3017fa29
--- /dev/null
+++ b/test/snapshots/windownav.golden
@@ -0,0 +1,310 @@
+== snap start grid=100x30 cursor=7,2
+|Kill Newcol Tutor Debug Colors NextColor Crt Dump
+| NOR /tmp/pardes-snap/windownav/cwd/w.txt Save De NOR /tmp/pardes-snap/windownav/cwd Del
+| 1 alpha
+| 2 beta w.txt
+| 3 gamma
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+== snap to-term grid=100x30 cursor=54,4
+|Kill Newcol Tutor Debug Colors NextColor Crt Dump
+| NOR /tmp/pardes-snap/windownav/cwd/w.txt Save De NOR /tmp/pardes-snap/windownav/cwd Del
+| 1 alpha
+| 2 beta w.txt
+| 3 gamma
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+== snap back-to-file grid=100x30 cursor=7,2
+|Kill Newcol Tutor Debug Colors NextColor Crt Dump
+| NOR /tmp/pardes-snap/windownav/cwd/w.txt Save De NOR /tmp/pardes-snap/windownav/cwd Del
+| 1 alpha
+| 2 beta w.txt
+| 3 gamma
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+== snap altn grid=100x30 cursor=4,19
+|Kill Newcol Tutor Debug Colors NextColor Crt Dump
+| NOR /tmp/pardes-snap/windownav/cwd/w.txt Save De NOR /tmp/pardes-snap/windownav/cwd Del
+| 1 alpha
+| 2 beta w.txt
+| 3 gamma
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+| NOR /tmp/pardes-snap/windownav/cwd Del
+|
+| w.txt
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+== snap up grid=100x30 cursor=7,2
+|Kill Newcol Tutor Debug Colors NextColor Crt Dump
+| NOR /tmp/pardes-snap/windownav/cwd/w.txt Save De NOR /tmp/pardes-snap/windownav/cwd Del
+| 1 alpha
+| 2 beta w.txt
+| 3 gamma
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+| NOR /tmp/pardes-snap/windownav/cwd Del
+|
+| w.txt
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+== snap right grid=100x30 cursor=54,4
+|Kill Newcol Tutor Debug Colors NextColor Crt Dump
+| NOR /tmp/pardes-snap/windownav/cwd/w.txt Save De NOR /tmp/pardes-snap/windownav/cwd Del
+| 1 alpha
+| 2 beta w.txt
+| 3 gamma
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+| NOR /tmp/pardes-snap/windownav/cwd Del
+|
+| w.txt
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+== snap left grid=100x30 cursor=7,2
+|Kill Newcol Tutor Debug Colors NextColor Crt Dump
+| NOR /tmp/pardes-snap/windownav/cwd/w.txt Save De NOR /tmp/pardes-snap/windownav/cwd Del
+| 1 alpha
+| 2 beta w.txt
+| 3 gamma
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+| NOR /tmp/pardes-snap/windownav/cwd Del
+|
+| w.txt
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+== snap down grid=100x30 cursor=4,19
+|Kill Newcol Tutor Debug Colors NextColor Crt Dump
+| NOR /tmp/pardes-snap/windownav/cwd/w.txt Save De NOR /tmp/pardes-snap/windownav/cwd Del
+| 1 alpha
+| 2 beta w.txt
+| 3 gamma
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+| NOR /tmp/pardes-snap/windownav/cwd Del
+|
+| w.txt
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+== snap toggle-doc grid=100x30 cursor=7,2
+|Kill Newcol Tutor Debug Colors NextColor Crt Dump
+| NOR /tmp/pardes-snap/windownav/cwd/w.txt Save De NOR /tmp/pardes-snap/windownav/cwd Del
+| 1 alpha
+| 2 beta w.txt
+| 3 gamma
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+| NOR /tmp/pardes-snap/windownav/cwd Del
+|
+| w.txt
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+== snap toggle-back grid=100x30 cursor=4,19
+|Kill Newcol Tutor Debug Colors NextColor Crt Dump
+| NOR /tmp/pardes-snap/windownav/cwd/w.txt Save De NOR /tmp/pardes-snap/windownav/cwd Del
+| 1 alpha
+| 2 beta w.txt
+| 3 gamma
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+| NOR /tmp/pardes-snap/windownav/cwd Del
+|
+| w.txt
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
diff --git a/test/snapshots/windownav.snap b/test/snapshots/windownav.snap
new file mode 100644
index 00000000..0e57fc1e
--- /dev/null
+++ b/test/snapshots/windownav.snap
@@ -0,0 +1,54 @@
+# RULE: moving focus is a BUILTIN like any other, so there is ONE
+# implementation and every route reaches it. SPC w h/j/k/l run Left/Down/Up/
+# Right — the geometric focusDir the Ctrl-w prefix also runs — and SPC w t
+# runs Toggleterm, the hop between the most recently focused DOCUMENT (a file,
+# an image, a +Search/+Help output buffer: things you READ) and the most
+# recently focused TERMINAL. "Most recent" is the focus history the core
+# already keeps to hand focus back when a pane closes, so the hop back is the
+# same key. Focus shows up in the golden's `cursor=` header: that is the
+# ACTIVE pane's cursor, so each snap is named by the pane it should land in.
+file w.txt alpha\nbeta\ngamma
+start 30 100 w.txt
+wait 8000 Kill Newcol
+wait 8000 w.txt
+stable 700 20000
+# left column = the file, right column = the launch shell; the file has focus
+snap start
+# SPC w t with a shell that has NEVER been focused: nothing to find in the
+# history, so it falls back to a scan and still lands in the right column
+key space w t
+stable 400 5000
+snap to-term
+# ...and back, because the file is now the latest document
+key space w t
+stable 400 5000
+snap back-to-file
+# Alt-n: a second shell, below the file — the left column now has two rows
+key a-n
+stable 700 15000
+snap altn
+# SPC w k: up out of that new shell into the file above it
+key space w k
+stable 400 5000
+snap up
+# SPC w l: right, into the launch shell
+key space w l
+stable 400 5000
+snap right
+# SPC w h: left, back into the file column
+key space w h
+stable 400 5000
+snap left
+# SPC w j: down, into the shell under the file
+key space w j
+stable 400 5000
+snap down
+# SPC w t from a terminal: the file, which is the only document here
+key space w t
+stable 400 5000
+snap toggle-doc
+# ...and back to the LATEST terminal — the one just left, not the launch shell
+# in the right column, which was focused earlier
+key space w t
+stable 400 5000
+snap toggle-back
diff --git a/test/snapshots/windowops.golden b/test/snapshots/windowops.golden
index c6282d58..381638c8 100644
--- a/test/snapshots/windowops.golden
+++ b/test/snapshots/windowops.golden
@@ -246,7 +246,7 @@
|
|
|
-== snap focus-left grid=100x30 cursor=4,19
+== snap focus-right grid=100x30 cursor=44,4
|Kill Newcol Tutor Debug Colors NextColor Crt Dump
| NOR /tmp/pardes-snap/windowops/cwd Del NOR /tmp/pardes-snap/windowops/cwd Del
|
@@ -277,13 +277,13 @@
|
|
|
-== snap debug-on grid=100x30 cursor=4,19
+== snap debug-on grid=100x30 cursor=44,4
|Kill Newcol Tutor Debug Colors NextColor Crt Dump
| NOR /tmp/pardes-snap/windowops/cwd Del NOR /tmp/pardes-snap/windowo╭────────────────────────────╮
| │pardes resizes=1 │
| w.txt w.txt │terms 4 cols 2 │
-| │active #3 38x13 │
-| │scroll 0/13 │
+| │active #2 58x28 │
+| │scroll 0/28 │
| │pinch 1.0000 │
| │tscroll 0.0000 │
| ╰────────────────────────────╯
@@ -310,7 +310,7 @@
|
== style debug-on
|0: 0-48 #969696,#2c2a3e, 49-99 d,#2c2a3e,
-|1: 0-1 d,#383c54, 2-39 #969696,#2c2a3e, 40-41 d,#383c54, 42-69 #969696,#2c2a3e, 70-99 d,d,
+|1: 0-1 d,#383c54, 2-39 #969696,#2c2a3e, 40-41 d,#6e4fd0, 42-69 #969696,#2c2a3e, 70-99 d,d,
|2: 0-1 d,#525252, 2-39 d,d, 40-41 d,#525252, 42-70 d,d, 71-87 #969696,d, 88-98 d,#2a2a2a, 99-99 d,d,
|3: 0-1 d,#525252, 2-39 d,d, 40-41 d,#525252, 42-70 d,d, 71-85 #969696,d, 86-98 d,#2a2a2a, 99-99 d,d,
|4: 0-1 d,#525252, 2-39 d,d, 40-41 d,#525252, 42-70 d,d, 71-86 #969696,d, 87-98 d,#2a2a2a, 99-99 d,d,
@@ -325,7 +325,7 @@
|13: 0-1 d,#525252, 2-39 d,d, 40-41 d,#525252, 42-99 d,d,
|14: 0-1 d,#525252, 2-39 d,d, 40-41 d,#525252, 42-99 d,d,
|15: 0-1 d,#525252, 2-39 d,d, 40-41 d,#525252, 42-99 d,d,
-|16: 0-1 d,#6e4fd0, 2-39 #969696,#2c2a3e, 40-41 d,#525252, 42-99 d,d,
+|16: 0-1 d,#383c54, 2-39 #969696,#2c2a3e, 40-41 d,#525252, 42-99 d,d,
|17: 0-1 d,#525252, 2-39 d,d, 40-41 d,#525252, 42-99 d,d,
|18: 0-1 d,#525252, 2-39 d,d, 40-41 d,#525252, 42-99 d,d,
|19: 0-1 d,#525252, 2-39 d,d, 40-41 d,#525252, 42-99 d,d,
@@ -339,7 +339,7 @@
|27: 0-1 d,#525252, 2-39 d,d, 40-41 d,#525252, 42-99 d,d,
|28: 0-1 d,#525252, 2-39 d,d, 40-41 d,#525252, 42-99 d,d,
|29: 0-1 d,#525252, 2-39 d,d, 40-41 d,#525252, 42-99 d,d,
-== snap debug-off grid=100x30 cursor=4,19
+== snap debug-off grid=100x30 cursor=44,4
|Kill Newcol Tutor Debug Colors NextColor Crt Dump
| NOR /tmp/pardes-snap/windowops/cwd Del NOR /tmp/pardes-snap/windowops/cwd Del
|
diff --git a/test/snapshots/windowops.snap b/test/snapshots/windowops.snap
index 13e35fc5..8e9ba070 100644
--- a/test/snapshots/windowops.snap
+++ b/test/snapshots/windowops.snap
@@ -1,4 +1,4 @@
-# hover resize hints, border drags, move drag, Alt-n, Ctrl-w focus, Debug box
+# hover resize hints, border drags, move drag, Alt-n, Ctrl-w focus (routed through the Right builtin), Debug box
file w.txt x
start 30 100 -n 3
wait 8000 w.txt
@@ -37,10 +37,12 @@ snap moved
key a-n
stable 700 15000
snap altn
-# Ctrl-w h: focus left
-key c-w h
+# Ctrl-w l: focus right, into the pane just moved to the right column. The
+# prefix runs the SAME Right builtin `SPC w l` runs (windownav covers the
+# leader side); `h` from here would have nothing to its left to prove.
+key c-w l
stable 400 5000
-snap focus-left
+snap focus-right
# Debug overlay on (topbar middle-click on Debug), then off
press middle 19 1
release middle 19 1