From 1df6bd12fafbf97062f7844a23de208add75a843 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Mon, 28 Sep 2026 19:36:58 -0300 Subject: A pane tag wraps onto rows and can collapse to one, as acme's does acme's frame wraps a tag at its right edge and the window grows to show every row (wind.c wintaglines); Tagup/Tagdown shrink it to one row and back. Pane tags used to be one row, scrolled sideways to the caret. Now a tag's lines wrap at its width (one cell kept for the caret past a full row) and it starts expanded; insert-mode Up on the first row collapses it, Down on the last row expands it, and Alt-Up/Alt-Down do so in either mode. A word the wrap breaks across rows is still one word to a click and a selection. Column and workspace tags keep scrolling sideways. Tests read body rows relative to the body's first row; the 41 snapshot goldens that moved were re-recorded by name after a programmatic check that each differs only by body rows shifting under the taller tags, and twelve scripts whose clicks targeted fixed rows now click the same text. Co-Authored-By: Claude Opus 5.5 --- src/edit.zig | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) (limited to 'src/edit.zig') diff --git a/src/edit.zig b/src/edit.zig index 1e480785..a194ef48 100644 --- a/src/edit.zig +++ b/src/edit.zig @@ -202,7 +202,7 @@ pub fn clipYank(p: *Pardes, pane: *Pane, main_only: bool) void { /// (the tag's lines first, the ones it shows) pub fn paneText(p: *Pardes, pane: *Pane) ![]u8 { const arena = p.scratch.allocator(); - const tag = try tagline.tagText(p, arena, pane); + const tag = (try tagline.shownTag(p, arena, pane)).text; const from = modal.lineStartOffset(tag, pane.tag_top); const cut = modal.lineStartOffset(tag, pane.tag_top + pane.tag_rows); const shown = if (cut < tag.len) tag[from .. cut - 1] else tag[from..]; @@ -299,13 +299,20 @@ pub fn selectionText(p: *Pardes, pane: *Pane, sl: Pane.Sel) ![]const u8 { return result; } const text = try paneText(p, pane); + const shown = try tagline.shownTag(p, arena, pane); var out: std.ArrayList(u8) = .empty; var it = std.mem.splitAny(u8, text, "\n"); var v: i32 = 0; var first = true; while (it.next()) |line| : (v += 1) { const cols = sweepCols(sl, v) orelse continue; - if (!first) try out.append(arena, '\n'); + // A row the tag's wrapping began carries on its text: no newline. + const soft = v > 0 and v < pane.tag_rows and std.mem.indexOfScalar( + usize, + shown.soft, + modal.lineStartOffset(shown.text, @as(usize, @intCast(v)) + pane.tag_top) -| 1, + ) != null; + if (!first and !soft) try out.append(arena, '\n'); first = false; // Past the first row a body row starts after its line numbers. const gutter: i32 = if (pane.file != null and v >= pane.tag_rows) panes.File.gutterWidth(pane) else 0; @@ -750,6 +757,19 @@ fn insertEdit(p: *Pardes, t: *Text, key: Key) void { Key.left, Key.right, Key.up, Key.down => { const lines = paneCursorLines(p, t) catch return; const cur0 = t.toModalCursor(); + // In a pane's tag, Up on its first line collapses it and Down + // on its last expands it, where they would move nothing; acme's + // arrows in a tag always do (plan9port text.c:776, Tagdown/Tagup). + if (t.what == .tag) if (t.pane()) |pane| { + if (key.cp == Key.up and cur0.row == 0) { + pane.tag_expanded = false; + return; + } + if (key.cp == Key.down and cur0.row + 1 >= lines.len) { + pane.tag_expanded = true; + return; + } + }; const nc = switch (key.cp) { Key.left => modal.charLeft(lines, cur0), Key.right => modal.charRight(lines, cur0), -- cgit v1.3