summaryrefslogtreecommitdiff
path: root/test/panes.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 /test/panes.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 'test/panes.zig')
-rw-r--r--test/panes.zig80
1 files changed, 70 insertions, 10 deletions
diff --git a/test/panes.zig b/test/panes.zig
index b9fa9a88..4804c497 100644
--- a/test/panes.zig
+++ b/test/panes.zig
@@ -168,6 +168,66 @@ const TagLayerTests = struct {
}
};
+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 });
+ defer p.deinit();
+ const pane = try p.setTestFile("body\n");
+ p.presentation.enabled = false;
+ p.settings.tag_bottom = bottom;
+ p.update(.tick);
+ try std.testing.expectEqual(@as(u16, 1), pane.tag_rows);
+ const height = p.rects[0].h;
+ const body_rows = pane.rows;
+ // Insert mode in a tag is a body's: Enter is a new line.
+ p.update(.{ .key = .{ .cp = ':' } });
+ p.update(.{ .key = .{ .cp = 'A' } });
+ p.update(.{ .key = .{ .cp = Key.enter } });
+ p.update(.{ .key = .{ .cp = 'S', .text = "Second" } });
+ try std.testing.expect(std.mem.endsWith(u8, Pardes.curTail(pane), "\nSecond"));
+ try std.testing.expectEqual(@as(u16, 2), pane.tag_rows);
+ // The pane keeps its height; the body gives the tag its row.
+ try std.testing.expectEqual(height, p.rects[0].h);
+ try std.testing.expectEqual(body_rows - 1, pane.rows);
+ var frame = std.heap.ArenaAllocator.init(std.testing.allocator);
+ defer frame.deinit();
+ const surface = try p.render(frame.allocator());
+ const r = p.rects[0];
+ const tag_y = p.tagTop(pane, r);
+ const x = r.x + pardes.TAG_TEXT_INSET;
+ try std.testing.expectEqualStrings("/", surface.at(x, tag_y).grapheme());
+ for ("Second", 0..) |byte, i| try std.testing.expectEqual(byte, surface.at(x + @as(u16, @intCast(i)), tag_y + 1).text[0]);
+ try std.testing.expectEqual(pardes.FontRole.tagline, surface.at(x, tag_y + 1).style.font_role);
+ // the caret is on the second line, after what was typed
+ try std.testing.expectEqual(tag_y + 1, surface.cursor.?.y);
+ try std.testing.expectEqual(x + 6, surface.cursor.?.x);
+ // the body starts under (or over) the whole tag
+ const body_y = p.bodyTop(pane, r);
+ try std.testing.expectEqual(if (bottom) r.y else r.y + 2, body_y);
+ try std.testing.expectEqualStrings("b", surface.at(r.x + config.GUTTER + panes.File.gutterWidth(pane), body_y).grapheme());
+ // a layer to a line, for a pixel shell
+ var lines: usize = 0;
+ for (p.surface.tagLayers()) |*layer| {
+ if (layer.cols == 0 or layer.kind != .pane or layer.id != 0) continue;
+ try std.testing.expectEqual(tag_y + layer.line, layer.viewport.y);
+ lines += 1;
+ }
+ try std.testing.expectEqual(@as(usize, 2), lines);
+ // a click on the second line puts the tag's cursor on it
+ p.update(.{ .key = .{ .cp = Key.escape } });
+ p.update(.{ .key = .{ .cp = ':' } });
+ ClickCursorTests.click(p, .left, x + 2, tag_y + 1);
+ try std.testing.expect(pane.focus == .tag);
+ try std.testing.expectEqual(@as(i32, 1), pane.tag.cur_row);
+ try std.testing.expectEqual(@as(i32, 2), pane.tag.cur_col);
+ // and the pane's text, a row to a screen row, has both of its lines
+ const text = try pardes.edit.paneText(p, pane);
+ try std.testing.expect(std.mem.startsWith(u8, modal.lineSlice(text, 0), "/test.txt"));
+ try std.testing.expectEqualStrings("Second", modal.lineSlice(text, 1));
+ try std.testing.expectEqualStrings("body\n", pane.file.?.content);
+ }
+}
+
const TagNavigationTests = struct {
fn key(p: *Pardes, cp: u21) void {
p.update(.{ .key = .{ .cp = cp } });
@@ -521,7 +581,7 @@ const TreeContextTests = struct {
try std.testing.expectEqual(@as(i32, 59), pane.wrapAt(visible.row).line);
const cursor = surface.cursor orelse return error.MissingLookCursor;
const rect = p.rects[0];
- const y = p.bodyTop(rect);
+ const y = p.bodyTop(pane, rect);
try std.testing.expectEqual(y + @as(u16, @intCast(visible.row)), cursor.y);
}
}
@@ -543,7 +603,7 @@ const TreeContextTests = struct {
try std.testing.expectEqual(@as(i32, 7), pane.wrapAt(pane.context_rows).line);
const rect = p.rects[0];
const x = rect.x + config.GUTTER + panes.File.gutterWidth(pane);
- const y = p.bodyTop(rect);
+ const y = p.bodyTop(pane, rect);
ClickCursorTests.click(p, .left, x + 6, y);
try std.testing.expectEqual(@as(i32, 0), pane.body.cur_row);
try std.testing.expectEqual(@as(i32, 6), pane.body.cur_col);
@@ -552,7 +612,7 @@ const TreeContextTests = struct {
try std.testing.expectEqual(x + 6, cursor.x);
// Read the body top again: the click cleared the announcement band the
// builtin above put up, and the body moved back up with it.
- try std.testing.expectEqual(p.bodyTop(p.rects[0]), cursor.y);
+ try std.testing.expectEqual(p.bodyTop(pane, p.rects[0]), cursor.y);
p.update(.{ .key = .{ .cp = 'i' } });
p.update(.{ .key = .{ .cp = 'Z', .text = "Z" } });
try std.testing.expect(std.mem.startsWith(u8, pane.file.?.content, "const ZBox = struct {"));
@@ -591,7 +651,7 @@ const TreeContextTests = struct {
const surface = try p.render(frame.allocator());
try std.testing.expectEqual(@as(u16, 3), pane.context_rows);
const rect = p.rects[0];
- const y = p.bodyTop(rect);
+ const y = p.bodyTop(pane, rect);
var glyph_x: ?u16 = null;
for (0..surface.cols) |col| {
if (std.mem.eql(u8, surface.at(@intCast(col), y).grapheme(), "界")) glyph_x = @intCast(col);
@@ -605,7 +665,7 @@ const TreeContextTests = struct {
try std.testing.expectEqual(x, cursor.x);
// Read the body top again: the click cleared the announcement band the
// builtin above put up, so the body moved with it.
- try std.testing.expectEqual(p.bodyTop(p.rects[0]), cursor.y);
+ try std.testing.expectEqual(p.bodyTop(pane, p.rects[0]), cursor.y);
panes.File.setContent(p, &pane.file.?, try p.gpa.dupe(u8, "// no enclosing declaration\n" ** 18));
_ = try p.render(frame.allocator());
try std.testing.expectEqual(@as(u16, 0), pane.context_rows);
@@ -664,7 +724,7 @@ const TtySelectionTests = struct {
_ = try p.render(frame.allocator());
const rect = p.rects[0];
const x = rect.x + config.GUTTER;
- const y = p.bodyTop(rect);
+ const y = p.bodyTop(pane, rect);
JumpSelectionTests.drag(p, x + 1, y, x + 3, y);
try std.testing.expectEqualStrings("lph", p.yank orelse return error.MissingAutomaticTtyYank);
p.update(.{ .key = .{ .cp = p.opts.tty_toggle, .ctrl = true } });
@@ -694,7 +754,7 @@ const TtySelectionTests = struct {
_ = try p.render(frame.allocator());
const rect = p.rects[0];
const x = rect.x + config.GUTTER;
- const y = p.bodyTop(rect);
+ const y = p.bodyTop(pane, rect);
JumpSelectionTests.drag(p, x + @as(u16, if (prompt) 0 else 1), y, x + 3, y + @as(u16, if (prompt) 0 else 1));
const expected = if (prompt) "$ ab" else "lph\nrav";
try std.testing.expectEqual(panes.Text.Mode.tty, pane.body.mode);
@@ -1009,7 +1069,7 @@ const JumpSelectionTests = struct {
if (wrap) try std.testing.expectEqual(pane.wrap_line[0], pane.wrap_line[1]);
const rect = p.rects[0];
const x = rect.x + config.GUTTER + panes.File.gutterWidth(pane);
- const y = p.bodyTop(rect);
+ const y = p.bodyTop(pane, rect);
if (reverse)
drag(p, x + last_col, y + 1, x + first_col, y)
else
@@ -1040,7 +1100,7 @@ const JumpSelectionTests = struct {
// A selection created after Back must also disappear on Forward.
// Back announces itself, so the body sits under a notice band now.
- const y2 = p.bodyTop(p.rects[0]);
+ const y2 = p.bodyTop(pane, p.rects[0]);
drag(p, x + 1, y2, x + 3, y2 + 1);
try std.testing.expect(pardes.test_api.heldSelection(p, 0) != null);
p.update(.{ .key = .{ .cp = 'i', .ctrl = true } });
@@ -4093,7 +4153,7 @@ test "cancelled normal gestures leave no count or subprefix after mouse and focu
.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 body_y = p.bodyTop(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 p.tagPrefix(pane)))) + 3
else