diff options
| author | Gabriel Schneider <[email protected]> | 2026-07-26 23:50:25 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-08-01 15:02:07 -0300 |
| commit | a1f26fc4977b185cbd603749157360c011452e5e (patch) | |
| tree | 5702b7316227e1714ae29885f445b2a2f3b00929 /src | |
| parent | d9da3ce9e87cf4c43bf34664031f090e6dde2410 (diff) | |
| download | pardes-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.
Diffstat (limited to 'src')
| -rw-r--r-- | src/pardes.zig | 86 | ||||
| -rw-r--r-- | src/tutor.txt | 10 |
2 files changed, 84 insertions, 12 deletions
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. |
