summaryrefslogtreecommitdiff
path: root/src/edit.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-28 00:30:46 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:14 -0300
commit232bfbbc4371a1fe9df210874118b4450dce7f9d (patch)
tree6af9cfcc6e741082b15a270a6d80d3d182301ce5 /src/edit.zig
parent42748f92b77367db415c8b4d0f2e537a9e1ddde7 (diff)
downloadpardes-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/edit.zig')
-rw-r--r--src/edit.zig34
1 files changed, 19 insertions, 15 deletions
diff --git a/src/edit.zig b/src/edit.zig
index 1822db31..36544d6b 100644
--- a/src/edit.zig
+++ b/src/edit.zig
@@ -128,10 +128,14 @@ pub fn clipYank(p: *Pardes, pane: *Pane, main_only: bool) void {
p.emit(.{ .set_clipboard = {} });
}
-/// tag + '\n' + body: the full selectable pane text (row 0 = the tag)
+/// tag + '\n' + body: the full selectable pane text, a row to a screen row
+/// (the tag's lines first, as many as it shows)
pub fn paneText(p: *Pardes, pane: *Pane) ![]u8 {
const arena = p.scratch.allocator();
- return std.fmt.allocPrint(arena, "{s}\n{s}", .{ try p.tagText(arena, pane), try body_layer.bodyText(p, arena, pane) });
+ const tag = try p.tagText(arena, pane);
+ const cut = modal.lineStartOffset(tag, pane.tag_rows);
+ const shown = if (cut < tag.len) tag[0 .. cut - 1] else tag;
+ return std.fmt.allocPrint(arena, "{s}\n{s}", .{ shown, try body_layer.bodyText(p, arena, pane) });
}
pub fn pointerSourceLine(pane: *Pane, terminal_lines: []const []const u8, row: i32) []const u8 {
@@ -144,16 +148,16 @@ pub fn pointerSourceLine(pane: *Pane, terminal_lines: []const []const u8, row: i
pub fn capturePointerSelection(p: *Pardes, pane: *Pane, slot: usize) !void {
var gesture = pane.sel[slot];
if (pane.hasPdf()) return;
- if (gesture.state != .done or @min(gesture.r0, gesture.r1) < BOX_H) return;
+ if (gesture.state != .done or @min(gesture.r0, gesture.r1) < pane.tag_rows) return;
if (pane.pointerSelection(gesture) != null) return;
pane.clearPointerSelection(slot);
// Refresh the source map before translating the rendered rectangle.
const raw = pane.isTerminal() and pane.body.mode == .tty;
const body = try body_layer.bodyText(p, p.scratch.allocator(), pane);
const terminal_lines = if (pane.file == null and !raw) try panes.Terminal.cursorLines(p, pane) else &.{};
- const first = @max(0, @min(gesture.r0, gesture.r1) - BOX_H);
+ const first = @max(0, @min(gesture.r0, gesture.r1) - pane.tag_rows);
const logical_rows = if (pane.body_rows > 0) pane.body_rows else pane.rows;
- const last = @min(@as(i32, logical_rows) - 1, @max(gesture.r0, gesture.r1) - BOX_H);
+ const last = @min(@as(i32, logical_rows) - 1, @max(gesture.r0, gesture.r1) - pane.tag_rows);
if (first > last) return;
const rows = try p.gpa.alloc(Pane.PointerRow, @intCast(last - first + 1));
errdefer p.gpa.free(rows);
@@ -1595,28 +1599,28 @@ const PointerTextSelection = struct {
};
pub fn pointerTextSelection(p: *const Pardes, id: usize, col: u16, row: u16, body_hit: ?Mouse.BodyHit, tag_hit: ?TagHit) ?PointerTextSelection {
- if (p.panes[id] == null) return null;
+ const pane = p.panes[id] orelse return null;
const r = p.rects[id];
if (col < r.x + config.GUTTER or col >= r.x + r.w or row < r.y or row >= r.y + r.h) return null;
- const tag_y = if (p.settings.tag_bottom) r.y + r.h -| BOX_H else r.y;
- const body_y = p.bodyTop(r);
- const on_tag = row >= tag_y and row < tag_y + BOX_H;
+ const tag_y = p.tagTop(pane, r);
+ const body_y = p.bodyTop(pane, r);
+ const on_tag = row >= tag_y and row < tag_y + pane.tag_rows;
if (on_tag and col < r.x + TAG_TEXT_INSET) return null;
// A notice chip is chrome: it sits OVER the body's top rows, so a
// click inside one belongs to nobody. The rest of those rows is
// ordinary body text and still answers normally.
- if (!on_tag and row >= body_y and row - body_y < p.panes[id].?.notices.span() and
- p.panes[id].?.notices.covers(row - body_y, col)) return null;
+ if (!on_tag and row >= body_y and row - body_y < pane.notices.span() and
+ pane.notices.covers(row - body_y, col)) return null;
if (!on_tag) if (body_hit) |supplied| {
const mapped_hit = mouse.bodyHitForPane(p, id, supplied, false) orelse return null;
const c = @as(i32, mapped_hit.col) - config.GUTTER;
- const v = @as(i32, mapped_hit.row) + BOX_H;
+ const v = @as(i32, mapped_hit.row) + pane.tag_rows;
return .{ .sel = .{ .state = .dragging, .c0 = c, .c1 = c, .r0 = v, .r1 = v }, .on_tag = false };
};
- const tag_col = if (on_tag) mouse.tagColumn(p, .pane, id, tag_hit, false) else null;
+ const tag_col = if (on_tag) mouse.tagColumn(p, .pane, id, tag_hit, false, row - tag_y) else null;
const c: i32 = (if (tag_col) |value| @as(i32, value) else @as(i32, col) - @as(i32, r.x + (if (on_tag) TAG_TEXT_INSET else config.GUTTER))) +
- (if (on_tag) @as(i32, p.panes[id].?.tag_scroll) else 0);
- const v: i32 = if (on_tag) 0 else @as(i32, row) - @as(i32, body_y) + @as(i32, BOX_H);
+ (if (on_tag) @as(i32, pane.tag_scroll) else 0);
+ const v: i32 = if (on_tag) @as(i32, row) - @as(i32, tag_y) else @as(i32, row) - @as(i32, body_y) + @as(i32, pane.tag_rows);
return .{
.sel = .{ .state = .dragging, .c0 = c, .c1 = c, .r0 = v, .r1 = v },
.on_tag = on_tag,