summaryrefslogtreecommitdiff
path: root/test/panes.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-28 09:16:36 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:14 -0300
commita9de2b51ab76a43250a4d6c41a0ddd54e6970049 (patch)
treea2138ead27f434026c224fd8b926f66b7fd7fe0c /test/panes.zig
parent0d04bc9214cccce42d74862b31376d49bdda3956 (diff)
downloadpardes-a9de2b51ab76a43250a4d6c41a0ddd54e6970049.tar.gz
pardes-a9de2b51ab76a43250a4d6c41a0ddd54e6970049.zip
Let the keyboard reach a tag's path, read-only
The tag's computed path, dirty marker and page were out of the keyboard's reach: its cursor lived in the text after them. It now lives in the tag as shown, as in acme where the name is ordinary text at the start of the tag, so `0` goes to the line's start and motions select and yank the path. The path stays read-only: an edit that would change it is refused and leaves the cursor where it was, and typing into a file's path drafts a new name as a click on it does. Sync moves the tag's positions when the prefix changes length, so a rename or the dirty marker still never moves the cursor off the text after it. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'test/panes.zig')
-rw-r--r--test/panes.zig75
1 files changed, 55 insertions, 20 deletions
diff --git a/test/panes.zig b/test/panes.zig
index d8ed517f..d0551cfc 100644
--- a/test/panes.zig
+++ b/test/panes.zig
@@ -12,12 +12,6 @@ const image = pardes.image;
const syntax = pardes.syntax;
const filesystem = pardes.filesystem;
-/// The tag's caret as a byte offset into the tag as it is shown, prefix
-/// included.
-fn tagCaret(p: *Pardes, pane: *Pane) !usize {
- return (try pardes.tagline.tagPrefix(p, pane)).len + @as(usize, @intCast(pane.tag.cur_col));
-}
-
fn setTag(pane: *Pane, text: []const u8) !void {
if (pane.tag.own) |own| pane.gpa.free(own);
pane.tag.own = try pane.gpa.dupe(u8, text);
@@ -67,7 +61,7 @@ const TagLayerTests = struct {
p.update(.{ .mouse = mouse(layer, x, .press) });
p.update(.{ .mouse = mouse(layer, x, .release) });
try std.testing.expect(pane.focus == .tag);
- try std.testing.expectEqual(at, try tagCaret(p, pane));
+ try std.testing.expectEqual(at, @as(usize, @intCast(pane.tag.cur_col)));
p.update(.{ .key = .{ .cp = 'X', .text = "X" } });
_ = try p.render(frame.allocator());
const edited = try pardes.tagline.tagText(p, frame.allocator(), pane);
@@ -99,7 +93,7 @@ const TagLayerTests = struct {
event.row = @intFromFloat(painted_y);
p.update(.{ .mouse = event });
}
- try std.testing.expectEqual(at, try tagCaret(p, pane));
+ try std.testing.expectEqual(at, @as(usize, @intCast(pane.tag.cur_col)));
}
}
@@ -148,7 +142,7 @@ const TagLayerTests = struct {
p.update(.{ .mouse = mouse(layer, beyond_x, .release) });
try std.testing.expectEqual(@as(usize, 0), p.active);
const end_col = left_layer.capacity(p.row_metrics) - 1;
- try std.testing.expectEqual(panes.File.rawAtDisplay(left_text, end_col), try tagCaret(p, left));
+ try std.testing.expectEqual(panes.File.rawAtDisplay(left_text, end_col), @as(usize, @intCast(left.tag.cur_col)));
try std.testing.expect(left.sel[0].state == .done and left.sel[0].r0 == 0);
p.update(.{ .key = .{ .cp = Key.escape } });
const long = "a" ** 60 ++ "界END";
@@ -320,6 +314,44 @@ const TagNavigationTests = struct {
try std.testing.expect(pane.prompt == .none and pane.focus == .body);
}
+ test "the keyboard reaches a tag's path, which it can select and yank but not edit" {
+ const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 100, .rows = 20 });
+ defer p.deinit();
+ const pane = try p.setTestFile("body\n");
+ p.update(.tick);
+ key(p, ':');
+ // `0` is the line's start, as in acme, where the name is ordinary text
+ key(p, '0');
+ try std.testing.expectEqual(@as(i32, 0), pane.tag.cur_col);
+ key(p, 'E');
+ key(p, 'y');
+ try std.testing.expectEqualStrings("/test.txt", p.yank.?);
+ // an edit that reaches into it is refused, the cursor with it
+ const before = try std.testing.allocator.dupe(u8, try pardes.edit.flatSurface(p, &pane.tag));
+ defer std.testing.allocator.free(before);
+ const cursor = pane.tag.cur_col;
+ key(p, 'd');
+ try std.testing.expectEqualStrings(before, try pardes.edit.flatSurface(p, &pane.tag));
+ try std.testing.expectEqual(cursor, pane.tag.cur_col);
+ try std.testing.expect(pane.tag.vsel.active);
+ // typed into, the path drafts a name there, as a click on it does
+ key(p, ';');
+ key(p, '0');
+ key(p, 'i');
+ p.update(.{ .key = .{ .cp = 'x', .text = "x" } });
+ try std.testing.expect(pane.prompt == .name);
+ try std.testing.expectEqualStrings("x/test.txt", pane.input.own.?);
+ key(p, Key.escape);
+ try std.testing.expect(pane.prompt == .none);
+ try std.testing.expectEqualStrings("/test.txt", pane.file.?.path);
+ // after the path, the text is the tag's own to edit
+ key(p, ':');
+ key(p, 'A');
+ p.update(.{ .key = .{ .cp = '!', .text = "!" } });
+ try std.testing.expect(std.mem.endsWith(u8, pardes.tagline.curTail(pane), "!"));
+ try std.testing.expectEqualStrings("body\n", pane.file.?.content);
+ }
+
fn window(p: *Pardes, letter: u21) void {
p.update(.{ .key = .{ .cp = 'w', .ctrl = true } });
key(p, letter);
@@ -339,8 +371,9 @@ const TagNavigationTests = struct {
// Nothing to remember yet: `:` lands on Save, in normal mode.
key(p, ':');
try std.testing.expect(first.focus == .tag and first.tag.mode == .normal);
- const tail = pardes.tagline.curTail(first);
- try std.testing.expectEqualStrings("Save", tail[@intCast(first.tag.cur_col)..][0..4]);
+ const shown = try pardes.edit.flatSurface(p, &first.tag);
+ try std.testing.expectEqualStrings("Save", shown[@intCast(first.tag.cur_col)..][0..4]);
+ // Home is the line's start, the path's: the keyboard reaches it.
key(p, Key.home);
for (0..3) |_| key(p, Key.right);
try std.testing.expectEqual(@as(i32, 3), first.tag.cur_col);
@@ -369,13 +402,13 @@ const TagNavigationTests = struct {
ClickCursorTests.click(p, .left, rect.x + pardes.TAG_TEXT_INSET + prefix + 6, y);
try std.testing.expectEqual(@as(usize, 0), p.active);
try std.testing.expect(first.focus == .tag and first.tag.mode == .insert);
- try std.testing.expectEqual(@as(i32, 6), first.tag.cur_col);
+ try std.testing.expectEqual(@as(i32, prefix + 6), first.tag.cur_col);
key(p, Key.escape);
key(p, ':');
p.startPrompt(first, &first.body, .{ .search = config.search_marker });
key(p, Key.escape);
key(p, ':');
- try std.testing.expectEqual(@as(i32, 6), first.tag.cur_col);
+ try std.testing.expectEqual(@as(i32, prefix + 6), first.tag.cur_col);
key(p, ':');
try std.testing.expectEqualStrings("body\n", first.file.?.content);
}
@@ -403,26 +436,28 @@ const TagNavigationTests = struct {
try std.testing.expectEqual(chord == Key.tab, saved);
// ...and the tag still knows where it was.
key(p, ':');
- try std.testing.expectEqualStrings("Save", pardes.tagline.curTail(pane)[@intCast(pane.tag.cur_col)..][0..4]);
+ try std.testing.expectEqualStrings("Save", (try pardes.edit.flatSurface(p, &pane.tag))[@intCast(pane.tag.cur_col)..][0..4]);
}
}
- test "a renamed path never moves the tag's cursor and a shorter tag pulls it back" {
+ test "a renamed path moves the tag's cursor with the text after it and a shorter tag pulls it back" {
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");
- key(p, ':');
- key(p, Key.home);
- for (0..3) |_| key(p, Key.right);
+ key(p, ':'); // on Save
key(p, ':');
p.gpa.free(pane.file.?.path);
pane.file.?.path = try p.gpa.dupe(u8, "/界.zig");
+ p.update(.tick);
key(p, ':');
- try std.testing.expectEqual(@as(i32, 3), pane.tag.cur_col);
+ const shown = try pardes.edit.flatSurface(p, &pane.tag);
+ try std.testing.expectEqualStrings("Save", shown[@intCast(pane.tag.cur_col)..][0..4]);
key(p, ':');
try setTag(pane, "é");
+ p.update(.tick);
key(p, ':');
- try std.testing.expectEqual(@as(i32, 2), pane.tag.cur_col);
+ // `/界.zig é`: the cursor is pulled back onto the last character
+ try std.testing.expectEqual(@as(i32, "/界.zig ".len), pane.tag.cur_col);
try std.testing.expectEqualStrings("body\n", pane.file.?.content);
}