summaryrefslogtreecommitdiff
path: root/test/panes.zig
diff options
context:
space:
mode:
Diffstat (limited to 'test/panes.zig')
-rw-r--r--test/panes.zig207
1 files changed, 136 insertions, 71 deletions
diff --git a/test/panes.zig b/test/panes.zig
index a6a95124..836323a0 100644
--- a/test/panes.zig
+++ b/test/panes.zig
@@ -165,6 +165,23 @@ const TagLayerTests = struct {
}
};
+
+// Rows relative to where the pane's body and tag are, whatever height its
+// tag has (a tag wraps onto more rows when it is long).
+fn paneAt(p: *Pardes, r: anytype) *pardes.Pane {
+ for (p.panes, 0..) |slot, id| if (slot) |pane| if (std.meta.eql(p.rects[id], r)) return pane;
+ unreachable;
+}
+fn bodyY(p: *Pardes, r: anytype) u16 {
+ return p.bodyTop(paneAt(p, r), r);
+}
+fn bodyH(p: *Pardes, r: anytype) u16 {
+ return r.h -| paneAt(p, r).tag_rows;
+}
+fn tagY(p: *Pardes, r: anytype) u16 {
+ return p.tagTop(paneAt(p, r), r);
+}
+
test "a tag with a newline in it is as tall as its lines, and each line is typed into and pointed at" {
for ([_]bool{ false, true }) |bottom| {
const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 80, .rows = 14 });
@@ -407,7 +424,7 @@ const TagNavigationTests = struct {
defer frame.deinit();
_ = try p.render(frame.allocator());
const rect = p.rects[0];
- const y = if (p.settings.tag_bottom) rect.y + rect.h - pardes.BOX_H else rect.y;
+ const y = tagY(p, rect);
const prefix: u16 = @intCast(panes.File.displayWidth(try pardes.tagline.tagPrefix(p, first)));
ClickCursorTests.click(p, .left, rect.x + pardes.TAG_TEXT_INSET + prefix + 6, y);
try std.testing.expectEqual(@as(usize, 0), p.active);
@@ -774,6 +791,8 @@ const TreeContextTests = struct {
const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 48, .rows = 14 });
defer p.deinit();
const pane = try setZigFile(p, "pub fn @\"界name\"(\n\tvalue: usize,\n) void {\n" ++ " _ = value;\n" ** 12 ++ "}\n");
+ pane.tag_expanded = false; // a one-row tag keeps this small screen's rows
+ p.sync();
p.settings.wrap = false;
p.settings.colors = false;
try std.testing.expect(p.executeBuiltinLine(0, "TreeContext"));
@@ -811,6 +830,8 @@ const TreeContextTests = struct {
defer p.deinit();
const body = " // 012345678901234567890123456789界tail";
const pane = try setZigFile(p, "pub fn aLongDeclarationNameThatCannotFit() void {\n // before\n" ++ body ++ "\n}\n");
+ pane.tag_expanded = false; // a one-row tag keeps this small screen's rows
+ p.sync();
p.settings.wrap = true;
try std.testing.expect(p.executeBuiltinLine(0, "TreeContext"));
pane.scrollBy(2);
@@ -953,14 +974,14 @@ const TtySelectionTests = struct {
_ = try p.render(frame.allocator());
const source = p.rects[0];
const sx = source.x + config.GUTTER;
- const sy = if (p.settings.tag_bottom) source.y else source.y + pardes.BOX_H;
+ const sy = bodyY(p, source);
JumpSelectionTests.drag(p, sx + 1, sy, sx + 3, sy + 1);
try std.testing.expectEqualStrings("lpha\nbrav", p.registers.text(p.gpa, '"') orelse return error.MissingAutomaticTtyYank);
try noChildInput(p);
const target = p.rects[1];
const tx = target.x + config.GUTTER + 2;
- const ty = if (p.settings.tag_bottom) target.y else target.y + pardes.BOX_H;
+ const ty = bodyY(p, target);
p.update(.{ .mouse = .{ .button = .left, .kind = .press, .col = tx, .row = ty } });
p.update(.{ .mouse = .{ .button = .right, .kind = .press, .col = tx, .row = ty } });
p.update(.{ .mouse = .{ .button = .right, .kind = .release, .col = tx, .row = ty } });
@@ -1180,7 +1201,7 @@ const ClickCursorTests = struct {
_ = try p.render(frame.allocator());
const rect = p.rects[0];
const x = rect.x + config.GUTTER + panes.File.gutterWidth(pane);
- const y = if (bottom_tag) rect.y else rect.y + pardes.BOX_H;
+ const y = bodyY(p, rect);
click(p, .left, x + 15, y + case.click_row);
try std.testing.expectEqual(case.row, pane.body.cur_row);
try std.testing.expectEqual(case.col, pane.body.cur_col);
@@ -1249,7 +1270,7 @@ const ClickCursorTests = struct {
_ = try p.render(frame.allocator());
const rect = p.rects[0];
const x = rect.x + config.GUTTER + panes.File.gutterWidth(pane);
- const y = if (p.settings.tag_bottom) rect.y else rect.y + pardes.BOX_H;
+ const y = bodyY(p, rect);
JumpSelectionTests.drag(p, x + 1, y + 4, x + 3, y + 4);
try std.testing.expectEqualStrings("lph", pardes.test_api.heldSelection(p, 0).?);
for (0..2) |_| p.update(.{ .mouse = .{ .button = .wheel_down, .kind = .press, .col = x + 2, .row = y + 4 } });
@@ -1458,22 +1479,29 @@ const TagNameTintTests = struct {
var arena = std.heap.ArenaAllocator.init(p.gpa);
defer arena.deinit();
const surface = try p.render(arena.allocator());
- const text = try pardes.tagline.tagText(p, arena.allocator(), pane);
+ // The tag as shown: its rows wrapped, each offset mapped back to the
+ // tag's own to say whether it is in the name.
+ const shown = try pardes.tagline.shownTag(p, arena.allocator(), pane);
const base = std.mem.lastIndexOfScalar(u8, name, '/') orelse return error.MissingDirectory;
const rect = p.rects[id];
- const y = if (p.settings.tag_bottom) rect.y + rect.h - pardes.BOX_H else rect.y;
const left = rect.x + pardes.TAG_TEXT_INSET;
var visible_name: usize = 0;
- var at: usize = 0;
- while (at < text.len) : (at = modal.nextGrapheme(text, at)) {
- const start: i32 = @as(i32, @intCast(panes.File.rawDisplayCol(text, at))) - pane.tag_scroll;
- const end = start + @as(i32, @intCast(panes.File.displayWidth(text[at..modal.nextGrapheme(text, at)])));
- var col = @max(0, start);
- while (col < end and col < rect.w - pardes.TAG_TEXT_INSET) : (col += 1) {
- const in_name = at > base and at < name.len;
- const fg = if (in_name) name_fg else tag_fg;
- try std.testing.expectEqual(pardes.Color{ .rgb = fg }, surface.at(left + @as(u16, @intCast(col)), y).style.fg);
- if (in_name) visible_name += 1;
+ for (0..pane.tag_rows) |row| {
+ const line_start = modal.lineStartOffset(shown.text, row + pane.tag_top);
+ const line = modal.lineSlice(shown.text, row + pane.tag_top);
+ const y = tagY(p, rect) + @as(u16, @intCast(row));
+ var at: usize = 0;
+ while (at < line.len) : (at = modal.nextGrapheme(line, at)) {
+ const real = shown.toReal(line_start + at);
+ const start: i32 = @intCast(panes.File.rawDisplayCol(line, at));
+ const end = start + @as(i32, @intCast(panes.File.displayWidth(line[at..modal.nextGrapheme(line, at)])));
+ var col = start;
+ while (col < end and col < rect.w - pardes.TAG_TEXT_INSET) : (col += 1) {
+ const in_name = real > base and real < name.len;
+ const fg = if (in_name) name_fg else tag_fg;
+ try std.testing.expectEqual(pardes.Color{ .rgb = fg }, surface.at(left + @as(u16, @intCast(col)), y).style.fg);
+ if (in_name) visible_name += 1;
+ }
}
}
return visible_name;
@@ -1499,7 +1527,7 @@ const TagNameTintTests = struct {
}
}
- test "tag filename tint follows scrolled Unicode drafted names and yields to selection" {
+ test "tag filename tint follows wrapped Unicode drafted names and yields to selection" {
const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 38, .rows = 14 });
defer p.deinit();
const pane = try p.setTestFile("body\n");
@@ -1509,17 +1537,20 @@ const TagNameTintTests = struct {
pardes.tagline.draftName(p, pane, 0);
redraft(pane, name);
pane.input.cur_col = @intCast(std.mem.indexOf(u8, name, "界").?);
+ p.sync(); // the drafted name's rows
try std.testing.expect((try check(p, 0, name)) > 0);
- try std.testing.expect(pane.tag_scroll > 0);
- // A sweep over the wide glyph wears the selection over the tint.
- const col: i32 = @intCast(panes.File.rawDisplayCol(name, @intCast(pane.input.cur_col)));
- pane.sel[0] = .{ .state = .done, .c0 = col, .c1 = col + 1, .r0 = 0, .r1 = 0 };
- pane.tag_scroll += 4; // bring both cells of the selected wide glyph into view
+ try std.testing.expect(pane.tag_rows > 1); // the long name wraps
+ // A sweep over the wide glyph, on the row it wrapped to, wears the
+ // selection over the tint.
var arena = std.heap.ArenaAllocator.init(p.gpa);
defer arena.deinit();
+ const shown = try pardes.tagline.shownTag(p, arena.allocator(), pane);
+ const pos = modal.positionAt(shown.text, shown.fromReal(@intCast(pane.input.cur_col)));
+ const col: i32 = @intCast(panes.File.rawDisplayCol(modal.lineSlice(shown.text, pos.row), pos.col));
+ pane.sel[0] = .{ .state = .done, .c0 = col, .c1 = col + 1, .r0 = @intCast(pos.row), .r1 = @intCast(pos.row) };
const surface = try p.render(arena.allocator());
- const x = p.rects[0].x + pardes.TAG_TEXT_INSET + @as(u16, @intCast(col - pane.tag_scroll));
- const y = if (p.settings.tag_bottom) p.rects[0].y + p.rects[0].h - pardes.BOX_H else p.rects[0].y;
+ const x = p.rects[0].x + pardes.TAG_TEXT_INSET + @as(u16, @intCast(col));
+ const y = tagY(p, p.rects[0]) + @as(u16, @intCast(pos.row));
try std.testing.expectEqual(pardes.Color{ .rgb = p.theme().sel_fg }, surface.at(x, y).style.fg);
try std.testing.expectEqual(pardes.Color{ .rgb = p.theme().sel_fg }, surface.at(x + 1, y).style.fg);
}
@@ -1542,7 +1573,7 @@ const TagNameTintTests = struct {
p.settings.focus_tint = active;
const surface = try p.render(arena.allocator());
const x = p.rects[0].x + pardes.TAG_TEXT_INSET;
- const y = if (p.settings.tag_bottom) p.rects[0].y + p.rects[0].h - pardes.BOX_H else p.rects[0].y;
+ const y = tagY(p, p.rects[0]);
const fg = if (active) active_fg else tag_fg;
try std.testing.expectEqual(pardes.Color{ .rgb = fg }, surface.at(x + 1, y).style.fg);
try std.testing.expectEqual(pardes.Color{ .rgb = fg }, surface.at(x + 5, y).style.fg);
@@ -1561,7 +1592,7 @@ fn redraft(pane: *Pane, name: []const u8) void {
/// Click the tagline at the end of the path: a name draft, caret there.
fn clickPathEnd(p: *Pardes, pane: *Pane) void {
const rect = p.rects[p.active];
- const y = if (p.settings.tag_bottom) rect.y + rect.h - pardes.BOX_H else rect.y;
+ const y = tagY(p, rect);
const x = rect.x + pardes.TAG_TEXT_INSET + @as(u16, @intCast(panes.File.displayWidth(pane.file.?.path) - 1));
ClickCursorTests.click(p, .left, x, y);
std.debug.assert(pane.prompt == .name);
@@ -1703,7 +1734,7 @@ test "paste into pane commands and prompts never inserts into the body" {
try std.testing.expect(std.mem.indexOf(u8, pardes.tagline.curTail(pane), " Custom\nCommand") != null);
}
-test "long pane tags reveal commands and map visible clicks to whole tag addresses" {
+test "a long pane tag wraps onto rows, and a click on a wrapped row maps to the whole tag's address" {
const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 50, .rows = 12 });
defer p.deinit();
const pane = try p.setTestFile("body\n");
@@ -1715,13 +1746,15 @@ test "long pane tags reveal commands and map visible clicks to whole tag address
var arena = std.heap.ArenaAllocator.init(p.gpa);
defer arena.deinit();
const surface = try p.render(arena.allocator());
- try std.testing.expect(pane.tag_scroll > 0);
+ try std.testing.expect(pane.tag_rows > 1);
try std.testing.expect(surface.cursor.?.x < p.rects[0].x + p.rects[0].w);
const text = try pardes.tagline.tagText(p, p.gpa, pane);
defer p.gpa.free(text);
- const offset = std.mem.indexOf(u8, text, "SyntaxBold").?;
- const x: u16 = @intCast(p.rects[0].x + pardes.TAG_TEXT_INSET + panes.File.rawDisplayCol(text, offset) - pane.tag_scroll);
- const y = p.rects[0].y;
+ const shown = try pardes.tagline.shownTag(p, arena.allocator(), pane);
+ const at = shown.fromReal(std.mem.indexOf(u8, text, "SyntaxBold").?);
+ const pos = modal.positionAt(shown.text, at);
+ const x: u16 = @intCast(p.rects[0].x + pardes.TAG_TEXT_INSET + panes.File.rawDisplayCol(modal.lineSlice(shown.text, pos.row), pos.col));
+ const y = p.tagTop(pane, p.rects[0]) + @as(u16, @intCast(pos.row));
const before = p.settings.syntax_bold;
p.update(.{ .mouse = .{ .col = x, .row = y, .button = config.exec_button, .kind = .press } });
p.update(.{ .mouse = .{ .col = x, .row = y, .button = config.exec_button, .kind = .release } });
@@ -1729,6 +1762,32 @@ test "long pane tags reveal commands and map visible clicks to whole tag address
try std.testing.expectEqualStrings(text, try pardes.tagline.tagText(p, p.scratch.allocator(), pane));
}
+test "a word the tag's wrapping breaks across rows is still one word to a click" {
+ const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 50, .rows = 12 });
+ defer p.deinit();
+ const pane = try p.setTestFile("body\n");
+ try pane.appendTag(" SyntaxBold");
+ var arena = std.heap.ArenaAllocator.init(p.gpa);
+ defer arena.deinit();
+ // Lengthen the path until the wrap splits the word after "Synt".
+ var pad: usize = 1;
+ const shown = while (pad < 80) : (pad += 1) {
+ p.gpa.free(pane.file.?.path);
+ const path = try std.fmt.allocPrint(arena.allocator(), "/{s}/f.zig", .{("a" ** 80)[0..pad]});
+ pane.file.?.path = try p.gpa.dupe(u8, path);
+ p.sync();
+ const s = try pardes.tagline.shownTag(p, arena.allocator(), pane);
+ if (std.mem.indexOf(u8, s.text, "Synt\n") != null) break s;
+ } else return error.NoSplit;
+ const pos = modal.positionAt(shown.text, std.mem.indexOf(u8, shown.text, "Synt").?);
+ const x: u16 = @intCast(p.rects[0].x + pardes.TAG_TEXT_INSET + pos.col + 1);
+ const y = p.tagTop(pane, p.rects[0]) + @as(u16, @intCast(pos.row));
+ const before = p.settings.syntax_bold;
+ p.update(.{ .mouse = .{ .col = x, .row = y, .button = config.exec_button, .kind = .press } });
+ p.update(.{ .mouse = .{ .col = x, .row = y, .button = config.exec_button, .kind = .release } });
+ try std.testing.expectEqual(!before, p.settings.syntax_bold);
+}
+
test "terminal overlay recoloring never materializes unrelated history" {
const testing = std.testing;
const p = try Pardes.init(testing.allocator, .{ .tty_only = true, .cols = 32, .rows = 8 });
@@ -1751,7 +1810,7 @@ test "terminal overlay recoloring never materializes unrelated history" {
const surface = try p.render(frame.allocator());
try testing.expect(p.shell_rows.pane == null);
const rect = p.rects[0];
- const body_y = if (p.settings.tag_bottom) rect.y else rect.y + pardes.BOX_H;
+ const body_y = bodyY(p, rect);
const cell = surface.at(rect.x + config.GUTTER, body_y);
try testing.expectEqualStrings("c", cell.grapheme());
try testing.expectEqual(pardes.Color{ .rgb = p.theme().palette.?[2] }, cell.style.fg);
@@ -1799,7 +1858,7 @@ test "terminal output keeps following new rows after scrollback eviction" {
const surface = try p.render(frame.allocator());
const rect = p.rects[0];
const x = rect.x + config.GUTTER;
- const y = (if (p.settings.tag_bottom) rect.y else rect.y + pardes.BOX_H) + pane.rows - 2;
+ const y = (bodyY(p, rect)) + pane.rows - 2;
for (last, 0..) |byte, col| {
const cell = surface.at(x + @as(u16, @intCast(col)), y);
try std.testing.expectEqualStrings(&.{byte}, cell.grapheme());
@@ -2452,7 +2511,7 @@ test "unplaced terminal panes keep their allocated grid until placement" {
p.update(.tick);
try std.testing.expect(p.rects[id].w > 0 and p.rects[id].h > pardes.BOX_H);
try std.testing.expectEqual(p.rects[id].w - config.GUTTER, pane.cols);
- try std.testing.expectEqual(p.rects[id].h - pardes.BOX_H, pane.rows);
+ try std.testing.expectEqual(bodyH(p, p.rects[id]), pane.rows);
}
test "document panes ignore terminal output without allocating terminal state" {
@@ -2805,7 +2864,7 @@ const OutputTests = struct {
layout.compute(p);
const surface = try p.render(frame.allocator());
const rect = p.rects[pane.search_pane.?];
- const body_y = if (p.settings.tag_bottom) rect.y else rect.y + pardes.BOX_H;
+ const body_y = bodyY(p, rect);
for (file.location_rows, 0..) |row, index| {
if (!row.declaration) continue;
const line = modal.lineSlice(file.content, index);
@@ -2853,7 +2912,7 @@ const OutputTests = struct {
_ = try p.render(frame.allocator());
const rect = p.rects[id];
const x = rect.x + config.GUTTER + panes.File.gutterWidth(help) + @as(u16, @intCast(at - line_start + 2));
- const y = (if (p.settings.tag_bottom) rect.y else rect.y + pardes.BOX_H) + @as(u16, @intCast(row));
+ const y = (bodyY(p, rect)) + @as(u16, @intCast(row));
p.update(.{ .mouse = .{ .button = config.exec_button, .kind = .press, .col = x, .row = y } });
p.update(.{ .mouse = .{ .button = config.exec_button, .kind = .release, .col = x, .row = y } });
try std.testing.expect(p.active != id);
@@ -2923,7 +2982,7 @@ const OutputTests = struct {
p.acknowledgePanelPresentation(&.{});
const rect = p.rects[id];
const x = rect.x + config.GUTTER + panes.File.gutterWidth(result);
- const y = if (p.settings.tag_bottom) rect.y else rect.y + pardes.BOX_H;
+ const y = bodyY(p, rect);
p.update(.{ .mouse = .{ .button = config.select_button, .kind = .press, .col = x, .row = y } });
p.update(.{ .mouse = .{ .button = config.select_button, .kind = .drag, .col = x + 2, .row = y + 1 } });
try std.testing.expect(p.drag == .select);
@@ -3238,8 +3297,8 @@ const TerminalTests = struct {
defer frame.deinit();
const r = p.rects[0];
const tx = r.x + config.GUTTER;
- const body_y = if (p.settings.tag_bottom) r.y else r.y + pardes.BOX_H;
- const rows: usize = r.h - pardes.BOX_H;
+ const body_y = bodyY(p, r);
+ const rows: usize = bodyH(p, r);
const cols: usize = r.w -| config.GUTTER;
const Snap = struct { text: [][7]u8, len: []u8, style: []pardes.CellStyle };
@@ -3479,7 +3538,7 @@ const TerminalTests = struct {
defer frame.deinit();
const r = p.rects[0];
const tx = r.x + config.GUTTER;
- const body_y = if (p.settings.tag_bottom) r.y else r.y + pardes.BOX_H;
+ const body_y = bodyY(p, r);
const red: pardes.Color = .{ .index = 1 };
const green: pardes.Color = .{ .index = 2 };
const blue: pardes.Color = .{ .index = 4 };
@@ -3534,7 +3593,7 @@ const TerminalTests = struct {
defer frame.deinit();
const r = p.rects[0];
const tx = r.x + config.GUTTER;
- const body_y = if (p.settings.tag_bottom) r.y else r.y + pardes.BOX_H;
+ const body_y = bodyY(p, r);
p.shell_rows.stale = true;
const s = try p.render(frame.allocator());
@@ -3582,8 +3641,8 @@ const TerminalTests = struct {
defer frame.deinit();
const r = p.rects[0];
const tx = r.x + config.GUTTER;
- const body_y = if (p.settings.tag_bottom) r.y else r.y + pardes.BOX_H;
- const body_h = r.h - pardes.BOX_H;
+ const body_y = bodyY(p, r);
+ const body_h = bodyH(p, r);
p.shell_rows.stale = true;
const s = try p.render(frame.allocator());
@@ -3621,7 +3680,7 @@ const TerminalTests = struct {
defer frame.deinit();
const r = p.rects[0];
const tx = r.x + config.GUTTER;
- const body_y = if (p.settings.tag_bottom) r.y else r.y + pardes.BOX_H;
+ const body_y = bodyY(p, r);
const red: pardes.Color = .{ .index = 1 };
const green: pardes.Color = .{ .index = 2 };
const blue: pardes.Color = .{ .index = 4 };
@@ -3658,7 +3717,7 @@ const TerminalTests = struct {
defer frame.deinit();
const r = p.rects[0];
const tx = r.x + config.GUTTER;
- const body_y = if (p.settings.tag_bottom) r.y else r.y + pardes.BOX_H;
+ const body_y = bodyY(p, r);
const red: pardes.Color = .{ .index = 1 };
// Covers far more rows than the grid was ever dumped for, so the anchor
@@ -3740,7 +3799,7 @@ const TerminalTests = struct {
defer frame.deinit();
const r = p.rects[0];
const tx = r.x + config.GUTTER;
- const body_y = if (p.settings.tag_bottom) r.y else r.y + pardes.BOX_H;
+ const body_y = bodyY(p, r);
p.shell_rows.stale = true;
const s = try p.render(frame.allocator());
@@ -3818,8 +3877,8 @@ const TerminalTests = struct {
defer frame.deinit();
const r = p.rects[0];
const tx = r.x + config.GUTTER;
- const body_y = if (p.settings.tag_bottom) r.y else r.y + pardes.BOX_H;
- const rows: usize = r.h - pardes.BOX_H;
+ const body_y = bodyY(p, r);
+ const rows: usize = bodyH(p, r);
const cols: usize = r.w -| config.GUTTER;
// What tty mode shows with nothing left behind: the reference.
@@ -3870,6 +3929,8 @@ const TerminalTests = struct {
defer p.deinit();
while (p.nextEffect()) |_| {}
const pane = p.panes[0].?;
+ pane.tag_expanded = false; // a one-row tag keeps this small screen's rows
+ p.sync();
pane.tty_filter = filter;
pane.body.mode = mode;
p.update(.{ .output = .{ .pane = 0, .bytes = "\x1b[32m\u{754C}\x1b[41m\x1b[K\x1b[7G\x1b[44m\x1b[K\r\n" } });
@@ -3878,7 +3939,7 @@ const TerminalTests = struct {
defer frame.deinit();
const r = p.rects[0];
const tx = r.x + config.GUTTER;
- const body_y = if (p.settings.tag_bottom) r.y else r.y + pardes.BOX_H;
+ const body_y = bodyY(p, r);
p.shell_rows.stale = true;
const s = try p.render(frame.allocator());
@@ -3914,7 +3975,7 @@ const TerminalTests = struct {
defer frame.deinit();
const r = p.rects[0];
const tx = r.x + config.GUTTER;
- const body_y = if (p.settings.tag_bottom) r.y else r.y + pardes.BOX_H;
+ const body_y = bodyY(p, r);
p.shell_rows.stale = true;
const s = try p.render(frame.allocator());
@@ -3947,10 +4008,10 @@ const TerminalTests = struct {
const s = try p.render(frame.allocator());
const r = p.rects[0];
const tx = r.x + config.GUTTER;
- const body_y = if (p.settings.tag_bottom) r.y else r.y + pardes.BOX_H;
+ const body_y = bodyY(p, r);
var bad: usize = 0;
- for (0..r.h -| pardes.BOX_H) |vr| {
+ for (0..bodyH(p, r)) |vr| {
var buf: [16]u8 = undefined;
var n: usize = 0;
for (0..10) |c| {
@@ -3990,7 +4051,7 @@ const TerminalTests = struct {
defer frame.deinit();
const r = p.rects[0];
const tx = r.x + config.GUTTER;
- const body_y = if (p.settings.tag_bottom) r.y else r.y + pardes.BOX_H;
+ const body_y = bodyY(p, r);
p.update(.{ .output = .{ .pane = 0, .bytes = "plain text\r\n" } });
p.shell_rows.stale = true;
@@ -4028,7 +4089,7 @@ const TerminalTests = struct {
defer frame.deinit();
const r = p.rects[0];
const tx = r.x + config.GUTTER;
- const body_y = if (p.settings.tag_bottom) r.y else r.y + pardes.BOX_H;
+ const body_y = bodyY(p, r);
// The state two ordinary edits reach: one at the bottom, one that split a
// line further up. The buffer now spans rows 2..6 and diverges at BOTH
@@ -4066,7 +4127,7 @@ const TerminalTests = struct {
defer frame.deinit();
const r = p.rects[0];
const tx = r.x + config.GUTTER;
- const body_y = if (p.settings.tag_bottom) r.y else r.y + pardes.BOX_H;
+ const body_y = bodyY(p, r);
// The state three keystrokes reach on any blank shell row: type a character
// and delete it, and the buffer holds NO text while still standing in for
@@ -4091,6 +4152,8 @@ const TerminalTests = struct {
defer p.deinit();
while (p.nextEffect()) |_| {}
const pane = p.panes[0].?;
+ pane.tag_expanded = false; // a one-row tag keeps this small screen's rows
+ p.sync();
pane.tty_filter = false;
pane.body.mode = .tty;
@@ -4100,7 +4163,7 @@ const TerminalTests = struct {
defer frame.deinit();
const r = p.rects[0];
const tx = r.x + config.GUTTER;
- const body_y = if (p.settings.tag_bottom) r.y else r.y + pardes.BOX_H;
+ const body_y = bodyY(p, r);
const blue: pardes.Color = .{ .index = 4 };
p.shell_rows.stale = true;
@@ -4143,12 +4206,12 @@ const TerminalTests = struct {
defer frame.deinit();
const r = p.rects[0];
const tx = r.x + config.GUTTER;
- const body_y = if (p.settings.tag_bottom) r.y else r.y + pardes.BOX_H;
+ const body_y = bodyY(p, r);
const s = try p.render(frame.allocator());
const want = pardes.Color{ .index = 33 };
var seen = false;
- for (0..@as(usize, r.h -| pardes.BOX_H)) |vr| {
+ for (0..@as(usize, bodyH(p, r))) |vr| {
var buf: [15]u8 = undefined;
for (0..15) |c| {
const g = s.at(tx + @as(u16, @intCast(c)), body_y + @as(u16, @intCast(vr))).grapheme();
@@ -4196,11 +4259,11 @@ const TerminalTests = struct {
defer frame.deinit();
const r = p.rects[0];
const tx = r.x + config.GUTTER;
- const body_y = if (p.settings.tag_bottom) r.y else r.y + pardes.BOX_H;
+ const body_y = bodyY(p, r);
const s = try p.render(frame.allocator());
var seen = false;
- for (0..@as(usize, r.h -| pardes.BOX_H)) |vr| {
+ for (0..@as(usize, bodyH(p, r))) |vr| {
var buf: [12]u8 = undefined;
for (0..12) |c| {
const g = s.at(tx + @as(u16, @intCast(c)), body_y + @as(u16, @intCast(vr))).grapheme();
@@ -4250,8 +4313,8 @@ const TerminalTests = struct {
defer frame.deinit();
const r = p.rects[0];
const tx = r.x + config.GUTTER;
- const body_y = if (p.settings.tag_bottom) r.y else r.y + pardes.BOX_H;
- const body_h = r.h -| pardes.BOX_H;
+ const body_y = bodyY(p, r);
+ const body_h = bodyH(p, r);
var step: usize = 0;
while (step < 12) : (step += 1) {
@@ -4345,11 +4408,11 @@ const TerminalTests = struct {
defer frame.deinit();
const r = p.rects[0];
const tx = r.x + config.GUTTER;
- const body_y = if (p.settings.tag_bottom) r.y else r.y + pardes.BOX_H;
+ const body_y = bodyY(p, r);
const s = try p.render(frame.allocator());
var seen: usize = 0;
- for (0..@as(usize, r.h -| pardes.BOX_H)) |vr| {
+ for (0..@as(usize, bodyH(p, r))) |vr| {
var buf: [12]u8 = undefined;
for (0..12) |c| {
const g = s.at(tx + @as(u16, @intCast(c)), body_y + @as(u16, @intCast(vr))).grapheme();
@@ -4427,7 +4490,7 @@ test "cancelled normal gestures leave no count or subprefix after mouse and focu
switch (gesture) {
.body, .tag => {
const rect = p.rects[0];
- const tag_y = if (p.settings.tag_bottom) rect.y + rect.h - pardes.BOX_H else rect.y;
+ const tag_y = tagY(p, rect);
const body_y = p.bodyTop(pane, rect);
const x = if (gesture == .tag)
rect.x + pardes.TAG_TEXT_INSET + @as(u16, @intCast(panes.File.displayWidth(try pardes.tagline.tagPrefix(p, pane)))) + 3
@@ -4557,9 +4620,9 @@ test "growing a file pane colors every newly visible Zig keyword" {
_ = frame.reset(.retain_capacity);
const surface = try p.render(frame.allocator());
const rect = p.rects[0];
- const body_y = if (p.settings.tag_bottom) rect.y else rect.y + pardes.BOX_H;
+ const body_y = bodyY(p, rect);
var seen: usize = 0;
- for (body_y..body_y + rect.h - pardes.BOX_H) |y| {
+ for (body_y..body_y + bodyH(p, rect)) |y| {
for (rect.x + config.GUTTER..rect.x + rect.w - 1) |x| {
const first = surface.at(@intCast(x), @intCast(y));
const second = surface.at(@intCast(x + 1), @intCast(y));
@@ -4600,6 +4663,8 @@ test "Zig keywords keep every rendered byte colored across line-number widths" {
const p = try Pardes.init(gpa, .{ .tty_only = true, .cols = view.cols, .rows = 10 });
defer p.deinit();
const pane = try p.setTestFile(source);
+ pane.tag_expanded = false; // a one-row tag keeps this small screen's rows
+ p.sync();
const file = &pane.file.?;
gpa.free(file.path);
file.path = try gpa.dupe(u8, "/highlight.zig");
@@ -4616,8 +4681,8 @@ test "Zig keywords keep every rendered byte colored across line-number widths" {
var seen: usize = 0;
var if_position: ?struct { x: u16, y: u16 } = null;
const rect = p.rects[0];
- const body_y = if (p.settings.tag_bottom) rect.y else rect.y + pardes.BOX_H;
- for (body_y..body_y + rect.h - pardes.BOX_H) |y| {
+ const body_y = bodyY(p, rect);
+ for (body_y..body_y + bodyH(p, rect)) |y| {
for (rect.x + config.GUTTER..rect.x + rect.w - 1) |x| {
const first = surface.at(@intCast(x), @intCast(y));
const second = surface.at(@intCast(x + 1), @intCast(y));
@@ -4666,7 +4731,7 @@ test "terminal overlays preserve trailing blank styles without coloring inserted
defer frame.deinit();
const rect = p.rects[0];
const x = rect.x + config.GUTTER;
- const y = if (p.settings.tag_bottom) rect.y else rect.y + pardes.BOX_H;
+ const y = bodyY(p, rect);
const palette = p.theme().palette.?;
const colors = [_]pardes.Color{ .{ .rgb = palette[1] }, .{ .rgb = palette[2] }, .{ .rgb = palette[4] } };
const original = try p.render(frame.allocator());