From 42748f92b77367db415c8b4d0f2e537a9e1ddde7 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Mon, 28 Sep 2026 00:13:11 -0300 Subject: Make the column and workspace tags Texts with the pane tag's keys The headers had their own one-line editor with its own keys: Enter always executed, h/l and J/K moved between headers and panes, Ctrl-c/x/v cut and pasted, and TagLine refused a newline that 9P accepted. They are now Texts like a pane's tag, with no prefix and a default until edited, so normal and insert mode, undo and the look and execute keys are the body's. Moving between them is the window keys' job: Up from a pane with nothing above it reaches its column's tag and then the workspace's, Down comes back, and Left and Right walk the column tags. Co-Authored-By: Claude Opus 5.5 --- src/normal.zig | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) (limited to 'src/normal.zig') diff --git a/src/normal.zig b/src/normal.zig index e4fa075d..630f7689 100644 --- a/src/normal.zig +++ b/src/normal.zig @@ -173,7 +173,9 @@ const Replay = union(enum) { pub fn replaySels(p: *Pardes, t: *Text, what: Replay) void { const id = p.active; - const serial = t.pane().serial; + // a header belongs to no pane and cannot go away under the replay + const owner: ?*Pane = if (t.what == .column or t.what == .workspace) null else t.pane(); + const serial = if (owner) |pane| pane.serial else 0; // the whole selection in text coordinates, document order. t.sels // is already ordered, so this only slots the primary into place. var list: [Text.max_selections]Text.SelRange = undefined; @@ -256,8 +258,11 @@ pub fn replaySels(p: *Pardes, t: *Text, what: Replay) void { p.multi_first = false; if (p.multi_stop) { p.multi_stop = false; - const pn = p.panes[id] orelse return; - if (pn.serial == serial) t.nsel = 0; + if (owner != null) { + const pn = p.panes[id] orelse return; + if (pn.serial != serial) return; + } + t.nsel = 0; return; } const text = edit.flatSurface(p, t) catch return; @@ -284,7 +289,9 @@ pub fn handleNormal(p: *Pardes, t: *Text, key: Key) void { } fn executeNormalAction(p: *Pardes, t: *Text, semantic: modal.Normal.Action) void { - const pane = t.pane(); + // What reaches past the text -- a builtin, a search, a language query -- + // acts for the pane the text belongs to; a header's is the active one. + const pane = if (t.what == .column or t.what == .workspace) p.panes[p.active] orelse return else t.pane(); const raw_selection = t.what == .body and pane.rawPointerText(pane.sel[sel_slot]) != null; const before = .{ t.cur_row, t.cur_col, t.vsel, t.msel, t.select, t.nsel, t.mode }; defer if (raw_selection and !std.meta.eql(before, .{ t.cur_row, t.cur_col, t.vsel, t.msel, t.select, t.nsel, t.mode })) { @@ -547,9 +554,11 @@ fn executeNormalAction(p: *Pardes, t: *Text, semantic: modal.Normal.Action) void }, // `:` is the one key a tag and a body do not share: it moves the // keyboard between them. - .command_line => if (t.what == .tag) { - pane.focus = .body; - } else p.enterTag(pane), + .command_line => switch (t.what) { + .tag => pane.focus = .body, + .column, .workspace => p.exitHeader(), + .body, .input => p.enterTag(pane), + }, .pipe_selection => |how| return p.startPrompt(pane, .{ .pipe = how }), .search => return p.startPrompt(pane, .{ .search = config.search_marker }), .search_step => |direction| return look.lookWalk( -- cgit v1.3