From 9d2e07e997de155c7dbb894a79e5c11839eab39e Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Mon, 28 Sep 2026 01:01:35 -0300 Subject: Scroll a tag taller than its rows to keep its caret in view A tag shows at most eight rows, so a longer one (a 9P write of many lines, or `o` in the tag) put its caret on a line that was never drawn. Pane, column and workspace tags now keep a first line on screen that follows the caret while they have the keyboard, and the pointer and the pane text count their rows from it. Co-Authored-By: Claude Opus 5.5 --- src/mouse.zig | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) (limited to 'src/mouse.zig') diff --git a/src/mouse.zig b/src/mouse.zig index df657ead..69feea29 100644 --- a/src/mouse.zig +++ b/src/mouse.zig @@ -276,10 +276,13 @@ pub fn handleMouse(p: *Pardes, m: Mouse) void { }; const x = if (p.header_column) |c| p.col_x[c] + TAG_TEXT_INSET else 0; const text = edit.flatSurface(p, t) catch return; - const top = if (p.header_column != null) p.topBarHeight() else 0; - const line: u16 = @intCast(@min(mrow -| top, modal.lineCount(text) -| 1, (if (p.header_column != null) p.columnBarHeight() else p.topBarHeight()) -| 1)); + // `row` is the header's row on screen, `line` the line of its text + // shown there. + const base = if (p.header_column != null) p.topBarHeight() else 0; + const row: u16 = @intCast(@min(mrow -| base, (if (p.header_column != null) p.columnBarHeight() else p.topBarHeight()) -| 1)); + const line: u16 = @intCast(@min(row + p.header_top, modal.lineCount(text) -| 1)); const bar = modal.lineSlice(text, line); - const at = @min(bar.len, panes.File.rawAtDisplay(bar, (tag_layer.columnAt(p, if (p.header_column != null) .column else .workspace, p.header_column orelse 0, p.pointer_tag_hit, p.header_drag, line) orelse (mcol -| x)) + p.header_scroll)); + const at = @min(bar.len, panes.File.rawAtDisplay(bar, (tag_layer.columnAt(p, if (p.header_column != null) .column else .workspace, p.header_column orelse 0, p.pointer_tag_hit, p.header_drag, row) orelse (mcol -| x)) + p.header_scroll)); // the anchor stays where the press put it t.cur_row = line; t.cur_col = @intCast(at); @@ -310,11 +313,12 @@ pub fn handleMouse(p: *Pardes, m: Mouse) void { const t = if (column) |c| &p.col_tags[c] else &p.global_tag; const focused = p.header_focus and p.header_column == column; const text = edit.flatSurface(p, t) catch return; - const line: u16 = @intCast(mrow - if (column != null) p.topBarHeight() else 0); + const row: u16 = @intCast(mrow - if (column != null) p.topBarHeight() else 0); + const line: u16 = @intCast(row + if (focused) p.header_top else 0); if (line > 0 and line >= modal.lineCount(text)) return; const bar = modal.lineSlice(text, line); const scroll = if (focused) p.header_scroll else 0; - const at = @min(bar.len, panes.File.rawAtDisplay(bar, (tag_layer.columnAt(p, if (column != null) .column else .workspace, column orelse 0, p.pointer_tag_hit, p.header_drag, line) orelse (mcol -| x)) + scroll)); + const at = @min(bar.len, panes.File.rawAtDisplay(bar, (tag_layer.columnAt(p, if (column != null) .column else .workspace, column orelse 0, p.pointer_tag_hit, p.header_drag, row) orelse (mcol -| x)) + scroll)); if (m.button == config.select_button) { // A clicked header is typed straight into, as a clicked tag is. if (!focused) tagline.enterHeader(p, column); @@ -662,7 +666,7 @@ fn gestureRange(p: *Pardes, id: usize, text: []const u8, on_tag: bool, operand: const tag = tagline.tagText(p, p.scratch.allocator(), pane) catch return .{}; const sel = operand.expanded orelse operand.preview orelse return .{}; const lead = if (sel.r0 < sel.r1 or (sel.r0 == sel.r1 and sel.c0 <= sel.c1)) sel else Pane.Sel{ .r0 = sel.r1, .c0 = sel.c1 }; - const row: usize = @intCast(@max(0, lead.r0)); + const row: usize = @as(usize, @intCast(@max(0, lead.r0))) + pane.tag_top; const start = modal.lineStartOffset(tag, row); const at = start + panes.File.rawAtDisplay(modal.lineSlice(tag, row), @intCast(@max(0, lead.c0))); const q0: u32 = @intCast(@min(at, tag.len)); -- cgit v1.3