diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-28 09:16:36 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:14 -0300 |
| commit | a9de2b51ab76a43250a4d6c41a0ddd54e6970049 (patch) | |
| tree | a2138ead27f434026c224fd8b926f66b7fd7fe0c /test | |
| parent | 0d04bc9214cccce42d74862b31376d49bdda3956 (diff) | |
| download | pardes-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')
| -rw-r--r-- | test/panes.zig | 75 | ||||
| -rw-r--r-- | test/snapshots/tagpath.golden | 23 | ||||
| -rw-r--r-- | test/snapshots/tagpath.snap | 33 |
3 files changed, 91 insertions, 40 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); } diff --git a/test/snapshots/tagpath.golden b/test/snapshots/tagpath.golden index 2ba1a2e6..a2ea32fc 100644 --- a/test/snapshots/tagpath.golden +++ b/test/snapshots/tagpath.golden @@ -8,12 +8,12 @@ | 4 line 4 | 5 == snap colon grid=100x31 cursor=38,2 -== snap col0 grid=100x31 cursor=37,2 -== snap drag-cross grid=100x31 cursor=41,2 -== style drag-cross grid=100x31 +== snap col0 grid=100x31 cursor=3,2 +== snap sel-path grid=100x31 cursor=36,2 +== style sel-path grid=100x31 |0: 0-70 #b2c3b8,#1d2824, 71-99 d,#1d2824, |1: 0-1 #ffffff,#887455, 2-2 d,#26362d, 3-34 #becfc4,#26362d, 35-99 d,#26362d, -|2: 0-1 #000000,#add38a, 2-2 d,#26362d, 3-10 #becfc4,#26362d, 11-41 #f5f7ee,#3c614a, 42-58 #becfc4,#26362d, 59-99 d,#26362d, +|2: 0-1 #000000,#add38a, 2-2 d,#26362d, 3-36 #f5f7ee,#3c614a, 37-58 #becfc4,#26362d, 59-99 d,#26362d, |3: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-12 #e5eee5,#0d1410, 13-99 d,#0d1410, |4: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-12 #e5eee5,#0d1410, 13-99 d,#0d1410, |5: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-12 #e5eee5,#0d1410, 13-99 d,#0d1410, @@ -42,9 +42,16 @@ |28: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-99 d,#0d1410, |29: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-99 d,#0d1410, |30: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-99 d,#0d1410, +== snap yanked-path grid=100x31 cursor=41,3 +|2: /tmp/pardes-snap/tagpath/cwd/c.txt * Save Tty Collapse Del +|3: 1 l/tmp/pardes-snap/tagpath/cwd/c.txtine 1 +== snap drag-cross grid=100x31 cursor=41,2 +== style drag-cross grid=100x31 +|2: 0-1 #000000,#add38a, 2-2 d,#26362d, 3-10 #becfc4,#26362d, 11-41 #f5f7ee,#3c614a, 42-60 #becfc4,#26362d, 61-99 d,#26362d, +|3: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-7 #e5eee5,#0d1410, 8-41 #f5f7ee,#3c614a, 42-46 #e5eee5,#0d1410, 47-99 d,#0d1410, == snap name-draft grid=100x31 cursor=11,2 -|2: /renamed/tmp/pardes-snap/tagpath/cwd/c.txt Save Tty Collapse Del -== snap name-cancelled grid=100x31 cursor=7,3 -|2: /tmp/pardes-snap/tagpath/cwd/c.txt Save Tty Collapse Del -== snap name-committed grid=100x31 cursor=7,3 +|2: /renamed/tmp/pardes-snap/tagpath/cwd/c.txt * Save Tty Collapse Del +== snap name-cancelled grid=100x31 cursor=41,3 +|2: /tmp/pardes-snap/tagpath/cwd/c.txt * Save Tty Collapse Del +== snap name-committed grid=100x31 cursor=41,3 |2: /renamed/tmp/pardes-snap/tagpath/cwd/c.txt * Save Tty Collapse Del diff --git a/test/snapshots/tagpath.snap b/test/snapshots/tagpath.snap index b9ef80e6..f02769d9 100644 --- a/test/snapshots/tagpath.snap +++ b/test/snapshots/tagpath.snap @@ -1,8 +1,7 @@ -# The path is part of the tag's text for the mouse -- a sweep selects across -# the path and the commands after it -- but never for the keyboard: the tag's -# cursor lives in its own text, so `0` goes to the start of that, not of the -# path. A click on the path drafts a new name there instead. Escape cancels; -# Enter changes buffer identity, never renames a disk file. +# The path is part of the tag's text for the keyboard as for the mouse: `0` +# goes to its start, `E` selects it and `y` yanks it. It is read-only, but +# typing into it drafts a new name there, as a click on it does. Escape +# cancels; Enter changes buffer identity, never renames a disk file. lines c.txt 4 start 31 100 c.txt wait 8000 Newcol Joincol @@ -15,6 +14,16 @@ snap colon key 0 stable 400 5000 snap col0 +key E +stable 400 5000 +snap sel-path +snapstyle sel-path +key y +# `:` gives the keyboard back to the body, and `p` puts the path there +key : +key p +stable 400 5000 +snap yanked-path # Sweep across the path / command boundary without injected padding. press left 12 3 drag left 30 3 @@ -23,21 +32,21 @@ release left 42 3 stable 400 5000 snap drag-cross snapstyle drag-cross +# Esc is the tag's normal mode; typed at the path's start, `/renamed` drafts +# a name key esc settle 100 -key : -stable 400 5000 -# a click on the path is a name draft, the caret where it was clicked -press left 4 3 -release left 4 3 +key 0 +key i text /renamed stable 400 5000 snap name-draft key esc stable 400 5000 snap name-cancelled -press left 4 3 -release left 4 3 +key : +key 0 +key i text /renamed key enter stable 400 5000 |
