diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-28 00:30:46 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:14 -0300 |
| commit | 232bfbbc4371a1fe9df210874118b4450dce7f9d (patch) | |
| tree | 6af9cfcc6e741082b15a270a6d80d3d182301ce5 /src/body_layer.zig | |
| parent | 42748f92b77367db415c8b4d0f2e537a9e1ddde7 (diff) | |
| download | pardes-232bfbbc4371a1fe9df210874118b4450dce7f9d.tar.gz pardes-232bfbbc4371a1fe9df210874118b4450dce7f9d.zip | |
Let a pane's tag take a row for each line it holds
A tag that is a text can hold a newline, and the tagline, the body under it
and the pointer all assumed one row. The pane's tag now takes a row per line
(up to eight, and never the body's last row); the body starts below it, the
rows a pointer counts start the body after the tag's lines, a click on any
line puts the tag's cursor there, and a pixel shell gets a tag layer per
line. A tag of one line draws exactly as before.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src/body_layer.zig')
| -rw-r--r-- | src/body_layer.zig | 69 |
1 files changed, 34 insertions, 35 deletions
diff --git a/src/body_layer.zig b/src/body_layer.zig index e85e10f1..2b2c8a74 100644 --- a/src/body_layer.zig +++ b/src/body_layer.zig @@ -135,11 +135,11 @@ fn paintTerminalSelection(p: *Pardes, pane: *Pane, r: Rect, rows: []const Pane.P const raw = pane.body.mode == .tty; const tx = r.x + config.GUTTER; const width = r.w -| config.GUTTER; - const body_y = p.bodyTop(r); + const body_y = p.bodyTop(pane, r); var lines = std.mem.splitScalar(u8, body, '\n'); var visible: i32 = 0; while (lines.next()) |line| : (visible += 1) { - if (visible >= r.h -| BOX_H) break; + if (visible >= r.h -| pane.tag_rows) break; const source_row = if (raw) panes.Terminal.gridOffset(pane) + visible else pane.wrapAt(visible).line; for (rows) |row| { const target = if (row.raw_terminal) @@ -177,10 +177,10 @@ fn paintSourceSelection(p: *Pardes, pane: *Pane, r: Rect, rows: []const Pane.Poi const terminal_lines = if (pane.file == null) panes.Terminal.cursorLines(p, pane) catch return else &.{}; const tx = r.x + config.GUTTER; const width = r.w -| config.GUTTER; - const body_y = p.bodyTop(r); + const body_y = p.bodyTop(pane, r); const prefix: i32 = if (pane.file != null) panes.File.gutterWidth(pane) else 0; var visible: i32 = 0; - while (visible < @as(i32, r.h -| BOX_H)) : (visible += 1) { + while (visible < @as(i32, r.h -| pane.tag_rows)) : (visible += 1) { const source = pane.wrapAt(visible); const line = edit.pointerSourceLine(pane, terminal_lines, source.line); const start = @min(line.len, @as(usize, @intCast(@max(0, source.at)))); @@ -224,13 +224,13 @@ pub fn paintPointerSelection( var row: u16 = 0; while (row < r.h) : (row += 1) { if (@as(i32, row) < r0 or @as(i32, row) > r1) continue; - const sy = if (row < BOX_H) tag_y else body_y + row - BOX_H; + const sy = if (row < pane.tag_rows) tag_y + row else body_y + row - pane.tag_rows; // File line numbers occupy a gutter only in the body. The tag is // row zero in Sel space and starts at its real first text cell. - const scroll: i32 = if (row < BOX_H) pane.tag_scroll else 0; - var col: i32 = if (pane.file != null and row >= BOX_H) @max(c0, @as(i32, panes.File.gutterWidth(pane))) else @max(0, c0 - scroll); - const row_x = if (row < BOX_H) r.x + @min(TAG_TEXT_INSET, r.w) else tx; - const row_width = if (row < BOX_H) r.w -| TAG_TEXT_INSET else tw; + const scroll: i32 = if (row < pane.tag_rows) pane.tag_scroll else 0; + var col: i32 = if (pane.file != null and row >= pane.tag_rows) @max(c0, @as(i32, panes.File.gutterWidth(pane))) else @max(0, c0 - scroll); + const row_x = if (row < pane.tag_rows) r.x + @min(TAG_TEXT_INSET, r.w) else tx; + const row_width = if (row < pane.tag_rows) r.w -| TAG_TEXT_INSET else tw; while (col <= c1 - scroll and col < row_width) : (col += 1) { const cell = s.at(row_x + @as(u16, @intCast(col)), sy); cell.default = false; @@ -242,8 +242,8 @@ pub fn paintPointerSelection( pub fn renderBodyLayer(p: *Pardes, arena: std.mem.Allocator, pane: *Pane, r: Rect, id: usize) !void { if (!p.settings.tree_context_tag_style or pane.context_rows == 0 or pane.file == null or pane.collapsed) return; - if (r.w <= config.GUTTER or r.h <= BOX_H) return; - const viewport_rows = r.h - BOX_H; + if (r.w <= config.GUTTER or r.h <= pane.tag_rows) return; + const viewport_rows = r.h - pane.tag_rows; if (viewport_rows == 0) return; // The canonical fixed grid remains available to terminal clients. The // separate body layer reserves enough rows for any compact font size. @@ -260,24 +260,24 @@ pub fn renderBodyLayer(p: *Pardes, arena: std.mem.Allocator, pane: *Pane, r: Rec const layer = &p.surface.body_layers[id]; const count = logical_rows * width; if (layer.cells.len != count) layer.cells = try p.gpa.realloc(layer.cells, count); - const temporary_cells = try arena.alloc(Cell, @as(usize, r.w) * (logical_rows + BOX_H)); + const temporary_cells = try arena.alloc(Cell, @as(usize, r.w) * (logical_rows + pane.tag_rows)); @memset(temporary_cells, .{}); - var temporary: Surface = .{ .cols = r.w, .rows = @intCast(logical_rows + BOX_H), .cells = temporary_cells }; + var temporary: Surface = .{ .cols = r.w, .rows = @intCast(logical_rows + pane.tag_rows), .cells = temporary_cells }; std.mem.swap(Surface, &p.surface, &temporary); - const synthetic: Rect = .{ .x = 0, .y = 0, .w = r.w, .h = @intCast(logical_rows + BOX_H) }; + const synthetic: Rect = .{ .x = 0, .y = 0, .w = r.w, .h = @intCast(logical_rows + pane.tag_rows) }; renderBody(p, arena, pane, synthetic, id, id == p.active, false) catch |err| { std.mem.swap(Surface, &p.surface, &temporary); return err; }; std.mem.swap(Surface, &p.surface, &temporary); - const first_row: usize = if (p.settings.tag_bottom) 0 else BOX_H; + const first_row: usize = if (p.settings.tag_bottom) 0 else pane.tag_rows; for (0..logical_rows) |row| { const start = (row + first_row) * r.w + config.GUTTER; @memcpy(layer.cells[row * width ..][0..width], temporary_cells[start..][0..width]); } layer.pane = @intCast(id); layer.serial = pane.serial; - layer.viewport = .{ .x = r.x + config.GUTTER, .y = p.bodyTop(r), .w = width, .h = viewport_rows }; + layer.viewport = .{ .x = r.x + config.GUTTER, .y = p.bodyTop(pane, r), .w = width, .h = viewport_rows }; layer.cols = width; layer.rows = @intCast(logical_rows); layer.context_rows = pane.context_rows; @@ -295,15 +295,15 @@ pub fn renderBody(p: *Pardes, arena: std.mem.Allocator, pane: *Pane, r: Rect, id const chrome = p.chromeTheme(); const tx = r.x + config.GUTTER; const tw = r.w - config.GUTTER; - const tag_y = if (p.settings.tag_bottom) r.y + r.h -| BOX_H else r.y; + const tag_y = p.tagTop(pane, r); // Below the notice bands: they are stacked at the top of the body the // way tree-sitter context rows are, so the body starts under them // rather than being painted over by them. - const body_y = p.bodyTop(r); + const body_y = p.bodyTop(pane, r); const pane_bg: Color = if (th.bg) |c| .{ .rgb = c } else .default; const page_rgb = th.bg orelse th.tag_bg; // body: emulator rows, prompt rows hidden outside tty mode - const body_h = r.h -| BOX_H; + const body_h = r.h -| pane.tag_rows; const body_bottom = r.h; const body_style: CellStyle = .{ .fg = if (th.fg) |c| .{ .rgb = c } else .default, @@ -365,7 +365,7 @@ pub fn renderBody(p: *Pardes, arena: std.mem.Allocator, pane: *Pane, r: Rect, id if (sl.state == .done and b != 0) continue; // A select sweep over the tag is the tag's selection, painted there // in the tag's own selection colours. - if (b == pardes.sel_slot and @min(sl.r0, sl.r1) < BOX_H) continue; + if (b == pardes.sel_slot and @min(sl.r0, sl.r1) < pane.tag_rows) continue; paintPointerSelection(p, pane, r, tx, tw, tag_y, body_y, sl, sel_btn[b], th.sel_fg); } // modal line selection (helix `x`): whole body rows, normal mode only @@ -377,13 +377,13 @@ pub fn renderBody(p: *Pardes, arena: std.mem.Allocator, pane: *Pane, r: Rect, id th.sel_bg; const lo = @min(pane.body.msel.r0, pane.body.msel.r1); const hi = @max(pane.body.msel.r0, pane.body.msel.r1); - var row: u16 = BOX_H; // never paint the tag row + var row: u16 = pane.tag_rows; // never paint the tag row while (row < body_bottom) : (row += 1) { - const ar = pane.wrapAt(@as(i32, row) - @as(i32, BOX_H)).line; + const ar = pane.wrapAt(@as(i32, row) - @as(i32, pane.tag_rows)).line; if (ar < lo or ar > hi) continue; var col: u16 = if (pane.file != null) panes.File.gutterWidth(pane) else 0; while (col < tw) : (col += 1) { - const cell = s.at(tx + col, body_y + row - BOX_H); + const cell = s.at(tx + col, body_y + row - pane.tag_rows); cell.default = false; cell.style.bg = .{ .rgb = modal_bg }; if (!hover_only) cell.style.fg = .{ .rgb = th.sel_fg }; @@ -415,7 +415,7 @@ pub fn renderBody(p: *Pardes, arena: std.mem.Allocator, pane: *Pane, r: Rect, id else colors.mix(th.sel_bg, page_rgb); var vr: i32 = 0; - while (vr + @as(i32, BOX_H) < @as(i32, body_bottom)) : (vr += 1) { + while (vr + @as(i32, pane.tag_rows) < @as(i32, body_bottom)) : (vr += 1) { const w = pane.wrapAt(vr); if (w.line < bnd.lo_row or w.line > bnd.hi_row) continue; const visible_line = modal.lineSlice(body, @intCast(vr)); @@ -444,7 +444,7 @@ pub fn renderBody(p: *Pardes, arena: std.mem.Allocator, pane: *Pane, r: Rect, id if (primary and !preview and !hover_only) continue; // the hardware cursor IS the primary's if (hover_only) continue; // quiet preview preserves the source ink const cw = pane.wrapRow(sr.row, sr.col); - const crow = cw.row + @as(i32, BOX_H); + const crow = cw.row + @as(i32, pane.tag_rows); const ccol = (if (pane.file != null) panes.File.displayOffset(pane, sr.row, cw.at, sr.col) else @@ -453,8 +453,8 @@ pub fn renderBody(p: *Pardes, arena: std.mem.Allocator, pane: *Pane, r: Rect, id @intCast(@max(0, cw.at)), @intCast(@max(0, sr.col)), )) + vpfx; - if (crow >= BOX_H and crow < @as(i32, body_bottom) and ccol >= vpfx and ccol < tw) { - const cell = s.at(tx + @as(u16, @intCast(ccol)), body_y + @as(u16, @intCast(crow - BOX_H))); + if (crow >= pane.tag_rows and crow < @as(i32, body_bottom) and ccol >= vpfx and ccol < tw) { + const cell = s.at(tx + @as(u16, @intCast(ccol)), body_y + @as(u16, @intCast(crow - pane.tag_rows))); cell.default = false; cell.style.bg = .{ .rgb = th.sel_fg }; cell.style.fg = .{ .rgb = th.sel_bg }; @@ -471,7 +471,7 @@ pub fn renderBody(p: *Pardes, arena: std.mem.Allocator, pane: *Pane, r: Rect, id const crow = if (pane.body.cur_pinned) pane.body.cur_row else pane.surfRow(@as(i32, @intCast(cur.y)) + goff); const ccol = if (pane.body.cur_pinned) pane.body.cur_col else @as(i32, @intCast(cur.x)); const cwp = pane.wrapRow(crow, ccol); - const prow = cwp.row + @as(i32, BOX_H); + const prow = cwp.row + @as(i32, pane.tag_rows); // Files store source-byte columns; the Surface stores display // cells, so account for every expanded tab before the cursor. const cx = if (pane.file != null) @@ -484,10 +484,10 @@ pub fn renderBody(p: *Pardes, arena: std.mem.Allocator, pane: *Pane, r: Rect, id ) else ccol; - if (prow >= BOX_H and cx >= 0 and prow < body_bottom and cx < tw) - s.cursor = .{ .x = tx + @as(u16, @intCast(cx)), .y = body_y + @as(u16, @intCast(prow - BOX_H)), .bar = pane.body.mode == .insert }; + if (prow >= pane.tag_rows and cx >= 0 and prow < body_bottom and cx < tw) + s.cursor = .{ .x = tx + @as(u16, @intCast(cx)), .y = body_y + @as(u16, @intCast(prow - pane.tag_rows)), .bar = pane.body.mode == .insert }; } else if (panes.Terminal.visibleCursor(pane)) |cur| { - if (cur.y + BOX_H < body_bottom and cur.x < tw) + if (cur.y + pane.tag_rows < body_bottom and cur.x < tw) s.cursor = .{ .x = tx + cur.x, .y = body_y + cur.y }; } } @@ -496,10 +496,9 @@ pub fn renderBody(p: *Pardes, arena: std.mem.Allocator, pane: *Pane, r: Rect, id // The rail covers the notice bands too: nothing else paints those // two gutter columns, so a gap there keeps the frame-wide border // fill and reads as a notch cut out of the scrollbar. - const rail_y = if (p.settings.tag_bottom) r.y else r.y + BOX_H; - const rail_h = r.h -| BOX_H; - s.fill(r.x, rail_y, 1, rail_h, .{ .bg = .{ .rgb = chrome.scroll_track } }); - s.fill(r.x + 1, rail_y, 1, rail_h, .{ .bg = pane_bg }); + const rail_h = r.h -| pane.tag_rows; + s.fill(r.x, body_y, 1, rail_h, .{ .bg = .{ .rgb = chrome.scroll_track } }); + s.fill(r.x + 1, body_y, 1, rail_h, .{ .bg = pane_bg }); const sb: struct { total: usize, offset: usize, len: usize } = if (pane.file) |*f| .{ .total = panes.File.nlines(p.gpa, f), .offset = f.scroll, |
