summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/hxcases/parity-waivers.jsonl10
-rw-r--r--test/hxcases/parity.jsonl80
-rw-r--r--test/hxdiff.zig136
-rw-r--r--test/snapshots/chordcut.golden4
-rw-r--r--test/snapshots/edit.golden2
-rw-r--r--test/snapshots/exec-bigdrag.golden2
-rw-r--r--test/snapshots/exec.golden2
-rw-r--r--test/snapshots/fsearch.golden118
-rw-r--r--test/snapshots/fsearch.snap13
-rw-r--r--test/snapshots/images.golden2
-rw-r--r--test/snapshots/layout-open.golden2
-rw-r--r--test/snapshots/leader.golden369
-rw-r--r--test/snapshots/leader.snap64
-rw-r--r--test/snapshots/modal.golden6
-rw-r--r--test/snapshots/psearch.golden310
-rw-r--r--test/snapshots/psearch.snap68
-rw-r--r--test/snapshots/rsearch.golden38
-rw-r--r--test/snapshots/rsearch.snap12
-rw-r--r--test/snapshots/shrinktag.golden10
-rw-r--r--test/snapshots/tty.golden62
-rw-r--r--test/snapshots/tty.snap11
-rw-r--r--test/snapshots/ttyedit.golden155
-rw-r--r--test/snapshots/ttyedit.snap45
-rw-r--r--test/snapshots/ttylook.golden2
24 files changed, 1370 insertions, 153 deletions
diff --git a/test/hxcases/parity-waivers.jsonl b/test/hxcases/parity-waivers.jsonl
new file mode 100644
index 00000000..d48f4670
--- /dev/null
+++ b/test/hxcases/parity-waivers.jsonl
@@ -0,0 +1,10 @@
+{"name":"ctrl-b-page","reason":"not a parity bug: Ctrl-b IS pardes's tty toggle (Options.tty_toggle = 'b'), so on a shell pane it enters raw tty mode instead of paging the view. Deliberate binding, same class as the alt-c-window-op waiver in the helix suite; Ctrl-u/Ctrl-d and PageUp cover half/full page scrolling on terminals."}
+{"name":"ctrl-f-page","reason":"viewport geometry: a file pane's view scrolls anywhere inside its line count, a terminal's is the vt's and cannot move below the live grid bottom, so a full-page move clamps and the cursor snaps into a different scrolloff band. The edit model is not involved - no text differs, only where the view landed."}
+{"name":"ctrl-f-small","reason":"viewport geometry: same as ctrl-f-page with a document shorter than the pane, where the terminal's view cannot scroll at all and the cursor snaps to the top scrolloff margin instead of the last line."}
+{"name":"pgdn-basic","reason":"viewport geometry: PageDown is the same view scroll as Ctrl-f, and clamps at the terminal's live grid bottom. Text identical, view position differs."}
+{"name":"z-pgdn-alias","reason":"viewport geometry: z-prefixed PageDown aliases the Ctrl-f view scroll, so it clamps at the terminal's live grid bottom exactly like pgdn-basic."}
+{"name":"ins-pgdn","reason":"viewport geometry: insert-mode PageDown is bound to the same view scroll as normal mode, so it clamps at the terminal's live grid bottom. Text identical, view position differs."}
+{"name":"zj-snaps-cursor","reason":"viewport geometry: zj scrolls the view one row and snaps the cursor back into the scrolloff band; on a terminal whose view is already at the vt's bottom the scroll is a no-op, so the cursor snaps from a different band."}
+{"name":"zj-noop-in-band","reason":"viewport geometry: same as zj-snaps-cursor - the terminal's view cannot scroll past the live grid bottom, so the cursor's band differs by one row."}
+{"name":"zk-bottom-snap","reason":"viewport geometry: zk scrolls the view up one row and snaps the cursor; the terminal's starting view offset differs because it could not scroll as far down in the first place."}
+{"name":"unindent-tab","reason":"the case text is not representable in a pty pane: a literal TAB fed through the terminal is expanded to the next tab stop by the emulator, so the shell rows hold spaces and never the tab the file pane unindents. Not an editing divergence - unindent-basic, unindent-partial, unindent-realign and unindent-cursor all match; only a byte a terminal cannot hold verbatim."}
diff --git a/test/hxcases/parity.jsonl b/test/hxcases/parity.jsonl
new file mode 100644
index 00000000..ee09dac1
--- /dev/null
+++ b/test/hxcases/parity.jsonl
@@ -0,0 +1,80 @@
+{"name": "type-midline", "text": "abc def\n", "keys": "lliXY<esc>"}
+{"name": "type-linestart", "text": "abc\ndef\n", "keys": "iZZ<esc>"}
+{"name": "type-lineend", "text": "abc\ndef\n", "keys": "AZZ<esc>"}
+{"name": "type-second-line", "text": "abc\ndef\n", "keys": "jlliQ<esc>"}
+{"name": "type-append", "text": "abc\n", "keys": "aX<esc>"}
+{"name": "type-insert-at-first-nonws", "text": " abc\n", "keys": "glIX<esc>"}
+{"name": "enter-splits-line", "text": "abcdef\n", "keys": "lli<ret><esc>"}
+{"name": "enter-at-line-end", "text": "abc\ndef\n", "keys": "A<ret><esc>"}
+{"name": "enter-at-line-start", "text": "abc\ndef\n", "keys": "i<ret><esc>"}
+{"name": "enter-then-type", "text": "abcdef\n", "keys": "lli<ret>XY<esc>"}
+{"name": "enter-twice", "text": "abcdef\n", "keys": "lli<ret><ret><esc>"}
+{"name": "enter-last-line", "text": "abc\ndef\n", "keys": "jA<ret>ZZ<esc>"}
+{"name": "enter-keeps-indent", "text": " abc\n", "keys": "A<ret>X<esc>"}
+{"name": "enter-mid-indented", "text": " abcdef\n", "keys": "6li<ret><esc>"}
+{"name": "multiline-insert", "text": "abc\n", "keys": "llia<ret>b<ret>c<esc>"}
+{"name": "multiline-insert-midline", "text": "abcdef\n", "keys": "lliX<ret>Y<ret>Z<esc>"}
+{"name": "open-below", "text": "abc\ndef\n", "keys": "oX<esc>"}
+{"name": "open-below-last", "text": "abc\ndef\n", "keys": "joX<esc>"}
+{"name": "open-above", "text": "abc\ndef\n", "keys": "OX<esc>"}
+{"name": "open-above-second", "text": "abc\ndef\n", "keys": "jOX<esc>"}
+{"name": "open-below-indent", "text": " abc\n", "keys": "oX<esc>"}
+{"name": "open-below-count", "text": "abc\ndef\n", "keys": "2oX<esc>"}
+{"name": "open-above-count", "text": "abc\ndef\n", "keys": "j2OX<esc>"}
+{"name": "bs-joins-lines", "text": "abc\ndef\n", "keys": "ji<backspace><esc>"}
+{"name": "bs-joins-then-types", "text": "abc\ndef\n", "keys": "ji<backspace>X<esc>"}
+{"name": "bs-midline", "text": "abcdef\n", "keys": "lli<backspace><esc>"}
+{"name": "bs-joins-twice", "text": "ab\ncd\nef\n", "keys": "jji<backspace><backspace><esc>"}
+{"name": "bs-at-doc-start", "text": "abc\n", "keys": "i<backspace><esc>"}
+{"name": "ctrl-h-joins", "text": "abc\ndef\n", "keys": "ji<C-h><esc>"}
+{"name": "del-at-line-end", "text": "abc\ndef\n", "keys": "A<del><esc>"}
+{"name": "del-midline", "text": "abcdef\n", "keys": "lli<del><esc>"}
+{"name": "del-joins-twice", "text": "ab\ncd\nef\n", "keys": "A<del><del><esc>"}
+{"name": "enter-then-bs", "text": "abcdef\n", "keys": "lli<ret><backspace><esc>"}
+{"name": "tab-insert", "text": "abc\n", "keys": "i<tab><esc>"}
+{"name": "tab-midline", "text": "abcdef\n", "keys": "lli<tab><esc>"}
+{"name": "ctrl-w-kills-word", "text": "alpha beta\n", "keys": "A<C-w><esc>"}
+{"name": "ctrl-w-crosses-newline", "text": "alpha\nbeta\n", "keys": "ji<C-w><esc>"}
+{"name": "ctrl-u-kills-to-start", "text": "alpha beta\n", "keys": "A<C-u><esc>"}
+{"name": "ctrl-k-kills-to-end", "text": "alpha beta\n", "keys": "lli<C-k><esc>"}
+{"name": "insert-home-end", "text": "abc def\n", "keys": "lli<end>X<home>Y<esc>"}
+{"name": "insert-arrows", "text": "abc\ndef\n", "keys": "i<down><right>X<esc>"}
+{"name": "d-char", "text": "abc\n", "keys": "d"}
+{"name": "d-line-selection", "text": "aa\nbb\ncc\n", "keys": "jxd"}
+{"name": "d-across-lines", "text": "abc\ndef\nghi\n", "keys": "lvjd"}
+{"name": "d-across-two-lines", "text": "abc\ndef\nghi\n", "keys": "lv2jd"}
+{"name": "d-whole-first-line", "text": "abc\ndef\n", "keys": "xd"}
+{"name": "d-two-lines", "text": "aa\nbb\ncc\ndd\n", "keys": "2xd"}
+{"name": "c-across-lines", "text": "abc\ndef\nghi\n", "keys": "lvjcZ<esc>"}
+{"name": "c-line-selection", "text": "aa\nbb\ncc\n", "keys": "jxcZ<esc>"}
+{"name": "c-char", "text": "abc\n", "keys": "cZ<esc>"}
+{"name": "c-then-enter", "text": "abc\ndef\n", "keys": "lvjcX<ret>Y<esc>"}
+{"name": "d-then-o", "text": "abc\ndef\nghi\n", "keys": "jxdoZ<esc>"}
+{"name": "d-then-type", "text": "abcdef\n", "keys": "lvldiZ<esc>"}
+{"name": "r-replace-char", "text": "abc\n", "keys": "lrZ"}
+{"name": "r-replace-selection", "text": "abc def\n", "keys": "vlrZ"}
+{"name": "tilde-case", "text": "abc\n", "keys": "vll~"}
+{"name": "yank-paste-charwise", "text": "abc def\n", "keys": "vlyp"}
+{"name": "yank-paste-linewise", "text": "aa\nbb\n", "keys": "xyp"}
+{"name": "undo-after-typing", "text": "abc\n", "keys": "liXY<esc>u"}
+{"name": "undo-after-enter", "text": "abcdef\n", "keys": "lli<ret><esc>u"}
+{"name": "undo-redo-after-enter", "text": "abcdef\n", "keys": "lli<ret><esc>uU"}
+{"name": "undo-after-open", "text": "abc\n", "keys": "oX<esc>u"}
+{"name": "undo-after-delete", "text": "abc\ndef\n", "keys": "xdu"}
+{"name": "type-then-motion", "text": "abc\ndef\n", "keys": "liX<esc>jw"}
+{"name": "enter-then-motion", "text": "abcdef\nghi\n", "keys": "lli<ret><esc>ge"}
+{"name": "enter-then-goto-last", "text": "ab\ncd\n", "keys": "A<ret>X<esc>ge"}
+{"name": "open-then-goto-last", "text": "ab\ncd\n", "keys": "ozz<esc>ge"}
+{"name": "md-second-line", "text": "aa\nb(cd)e\n", "keys": "j3lmd("}
+{"name": "md-third-line", "text": "aa\nbb\nx(yz)w\n", "keys": "2j3lmd("}
+{"name": "mr-second-line", "text": "aa\nb(cd)e\n", "keys": "j3lmr(["}
+{"name": "mr-quote-third-line", "text": "aa\nbb\nc 'de' f\n", "keys": "2j4lmr'\""}
+{"name": "ms-second-line", "text": "aa\nbcd\n", "keys": "jvlms("}
+{"name": "indent-second-line", "text": "aa\nbb\ncc\n", "keys": "j<gt>"}
+{"name": "indent-span-later-lines", "text": "aa\nbb\ncc\ndd\n", "keys": "jx2x<gt>"}
+{"name": "unindent-second-line", "text": "aa\n bb\n", "keys": "j<lt>"}
+{"name": "J-second-line", "text": "aa\nbb\ncc\n", "keys": "jJ"}
+{"name": "J-sel-later-lines", "text": "aa\nbb\ncc\ndd\n", "keys": "jxxJ"}
+{"name": "d-on-nl-second-line", "text": "aa\nbb\ncc\n", "keys": "jlld"}
+{"name": "adjust-number-second-line", "text": "aa\nx 41 y\n", "keys": "jw<C-a>"}
+{"name": "R-replace-second-line", "text": "aa\nbb\ncc\n", "keys": "vlyjvlR"}
diff --git a/test/hxdiff.zig b/test/hxdiff.zig
index 2170961e..dc901263 100644
--- a/test/hxdiff.zig
+++ b/test/hxdiff.zig
@@ -10,13 +10,27 @@
//
// zig build hxdiff (diff the checked-in corpus)
// zig build hxdiff -- <cases.jsonl> (results to stdout, no diff)
+// zig build hxparity (file-vs-pty parity, both corpora)
// ./zig-out/bin/pardes-hxdiff <cases.jsonl> [goldens.jsonl [waivers.jsonl]]
+// ./zig-out/bin/pardes-hxdiff --parity [--waivers w.jsonl] <cases.jsonl>...
//
// DIFF MODE (goldens given): every result is compared field-by-field
// (text/mode/cursor/anchor) against the same-named golden line. Waivers
// ({"name":...,"reason":...}) exempt named cases — each must carry a reason.
// Any unwaivered mismatch prints a per-case report and exits 1.
//
+// PARITY MODE (--parity): no goldens at all. Each case is run TWICE, once in
+// a file pane and once in a pty pane, over the same starting text and the
+// same keys, and the two result lines must be identical. That is the whole
+// contract of "editing a shell pane behaves like editing a text file": the
+// oracle is the file pane itself, so it cannot drift. The case's own "pane"
+// field is ignored, and SEVERAL case files may be given — the gate runs the
+// whole helix corpus, not just the editing cases, because a parity suite
+// that only covers what its author remembered cannot catch the next
+// regression. Divergences that are not editing (pardes key bindings, pty
+// viewport geometry, text a terminal cannot hold verbatim) are waived by
+// name in test/hxcases/parity-waivers.jsonl, each with its reason.
+//
// Viewport is fixed at 80x24, matching the helix harness: one pane = 22 body
// rows (topbar + tag take two; helix: statusline + commandline take two).
// pane "file" swaps the boot pane for a file pane holding the case text
@@ -43,21 +57,31 @@ pub fn main(init: std.process.Init) !void {
const arena = arena_state.allocator();
const args = try init.minimal.args.toSlice(init.arena.allocator());
- var cases_path: ?[]const u8 = null;
+ var parity = false;
var goldens_path: ?[]const u8 = null;
var waivers_path: ?[]const u8 = null;
+ var case_paths: std.ArrayList([]const u8) = .empty;
+ var want_waivers = false;
for (args[1..]) |a| {
- if (cases_path == null) {
- cases_path = a;
+ if (want_waivers) {
+ waivers_path = a;
+ want_waivers = false;
+ } else if (std.mem.eql(u8, a, "--parity")) {
+ parity = true;
+ } else if (std.mem.eql(u8, a, "--waivers")) {
+ want_waivers = true;
+ } else if (parity or case_paths.items.len == 0) {
+ // diff mode is positional (cases, goldens, waivers); parity mode
+ // takes any number of case files and nothing else
+ case_paths.append(arena, a) catch fatal("oom", .{});
} else if (goldens_path == null) {
goldens_path = a;
} else if (waivers_path == null) {
waivers_path = a;
} else fatal("usage: pardes-hxdiff <cases.jsonl> [goldens.jsonl [waivers.jsonl]]", .{});
}
- const path = cases_path orelse fatal("usage: pardes-hxdiff <cases.jsonl> [goldens.jsonl [waivers.jsonl]]", .{});
-
- const src = readFileAlloc(arena, path) catch |e| fatal("read {s}: {s}", .{ path, @errorName(e) });
+ if (case_paths.items.len == 0)
+ fatal("usage: pardes-hxdiff [--parity] <cases.jsonl>... [goldens.jsonl [waivers.jsonl]]", .{});
// goldens + waivers, keyed by name (diff mode)
var goldens: std.StringHashMap(Result) = .init(arena);
@@ -94,38 +118,56 @@ pub fn main(init: std.process.Init) !void {
var n_cases: usize = 0;
var n_bad: usize = 0;
var n_waived: usize = 0;
- var lines = std.mem.splitScalar(u8, src, '\n');
- var lineno: usize = 0;
- while (lines.next()) |raw| {
- lineno += 1;
- const line = std.mem.trim(u8, raw, " \t\r");
- if (line.len == 0) continue;
- const case = std.json.parseFromSliceLeaky(Case, arena, line, .{ .ignore_unknown_fields = true }) catch |e|
- fatal("{s}:{d}: bad case line: {s}", .{ path, lineno, @errorName(e) });
- n_cases += 1;
- const res = runCase(arena, case) catch |e| fatal("case {s}: {s}", .{ case.name, @errorName(e) });
- if (goldens_path == null) {
- const jl = std.json.Stringify.valueAlloc(arena, res, .{}) catch fatal("oom", .{});
- out.appendSlice(arena, jl) catch fatal("oom", .{});
- out.append(arena, '\n') catch fatal("oom", .{});
- continue;
- }
- const g = goldens.get(case.name) orelse fatal("no golden for case {s} (regen goldens?)", .{case.name});
- const diff = diffResult(arena, g, res);
- if (diff.len == 0) continue;
- if (waivers.get(case.name)) |reason| {
- n_waived += 1;
- std.debug.print("WAIVED {s}: {s}\n", .{ case.name, reason });
- continue;
+ for (case_paths.items) |path| {
+ const src = readFileAlloc(arena, path) catch |e| fatal("read {s}: {s}", .{ path, @errorName(e) });
+ var lines = std.mem.splitScalar(u8, src, '\n');
+ var lineno: usize = 0;
+ while (lines.next()) |raw| {
+ lineno += 1;
+ const line = std.mem.trim(u8, raw, " \t\r");
+ if (line.len == 0) continue;
+ const case = std.json.parseFromSliceLeaky(Case, arena, line, .{ .ignore_unknown_fields = true }) catch |e|
+ fatal("{s}:{d}: bad case line: {s}", .{ path, lineno, @errorName(e) });
+ n_cases += 1;
+ if (parity) {
+ // the file pane IS the oracle: same text, same keys, both kinds
+ const want = runCase(arena, case, "file") catch |e| fatal("case {s} (file): {s}", .{ case.name, @errorName(e) });
+ const got = runCase(arena, case, "tty") catch |e| fatal("case {s} (tty): {s}", .{ case.name, @errorName(e) });
+ const diff = diffResult(arena, want, got, "file", "pty");
+ if (diff.len == 0) continue;
+ if (waivers.get(case.name)) |reason| {
+ n_waived += 1;
+ std.debug.print("WAIVED {s}: {s}\n", .{ case.name, reason });
+ continue;
+ }
+ n_bad += 1;
+ std.debug.print("MISMATCH {s} (keys: {s})\n{s}", .{ case.name, case.keys, diff });
+ continue;
+ }
+ const res = runCase(arena, case, case.pane) catch |e| fatal("case {s}: {s}", .{ case.name, @errorName(e) });
+ if (goldens_path == null) {
+ const jl = std.json.Stringify.valueAlloc(arena, res, .{}) catch fatal("oom", .{});
+ out.appendSlice(arena, jl) catch fatal("oom", .{});
+ out.append(arena, '\n') catch fatal("oom", .{});
+ continue;
+ }
+ const g = goldens.get(case.name) orelse fatal("no golden for case {s} (regen goldens?)", .{case.name});
+ const diff = diffResult(arena, g, res, "helix", "pardes");
+ if (diff.len == 0) continue;
+ if (waivers.get(case.name)) |reason| {
+ n_waived += 1;
+ std.debug.print("WAIVED {s}: {s}\n", .{ case.name, reason });
+ continue;
+ }
+ n_bad += 1;
+ std.debug.print("MISMATCH {s} (keys: {s})\n{s}", .{ case.name, case.keys, diff });
}
- n_bad += 1;
- std.debug.print("MISMATCH {s} (keys: {s})\n{s}", .{ case.name, case.keys, diff });
}
- if (goldens_path == null) {
+ if (!parity and goldens_path == null) {
try std.Io.File.stdout().writeStreamingAll(init.io, out.items);
return;
}
- std.debug.print("hxdiff: {d} cases, {d} mismatches, {d} waived\n", .{ n_cases, n_bad, n_waived });
+ std.debug.print("{s}: {d} cases, {d} mismatches, {d} waived\n", .{ if (parity) "hxparity" else "hxdiff", n_cases, n_bad, n_waived });
if (n_bad > 0) std.process.exit(1);
}
@@ -152,20 +194,21 @@ const Waiver = struct {
reason: []const u8,
};
-/// field-by-field compare; returns a human-readable report ("" = match)
-fn diffResult(arena: std.mem.Allocator, want: Result, got: Result) []const u8 {
+/// field-by-field compare; returns a human-readable report ("" = match).
+/// `wl`/`gl` name the two sides (helix vs pardes, or file vs pty).
+fn diffResult(arena: std.mem.Allocator, want: Result, got: Result, wl: []const u8, gl: []const u8) []const u8 {
var buf: std.ArrayList(u8) = .empty;
if (!std.mem.eql(u8, want.text, got.text)) {
- appendf(arena, &buf, " text: helix {f} != pardes {f}\n", .{ std.json.fmt(want.text, .{}), std.json.fmt(got.text, .{}) });
+ appendf(arena, &buf, " text: {s} {f} != {s} {f}\n", .{ wl, std.json.fmt(want.text, .{}), gl, std.json.fmt(got.text, .{}) });
}
if (!std.mem.eql(u8, want.mode, got.mode)) {
- appendf(arena, &buf, " mode: helix {s} != pardes {s}\n", .{ want.mode, got.mode });
+ appendf(arena, &buf, " mode: {s} {s} != {s} {s}\n", .{ wl, want.mode, gl, got.mode });
}
if (want.cursor.row != got.cursor.row or want.cursor.col != got.cursor.col) {
- appendf(arena, &buf, " cursor: helix ({d},{d}) != pardes ({d},{d})\n", .{ @as(i64, want.cursor.row), @as(i64, want.cursor.col), @as(i64, got.cursor.row), @as(i64, got.cursor.col) });
+ appendf(arena, &buf, " cursor: {s} ({d},{d}) != {s} ({d},{d})\n", .{ wl, @as(i64, want.cursor.row), @as(i64, want.cursor.col), gl, @as(i64, got.cursor.row), @as(i64, got.cursor.col) });
}
if (want.anchor.row != got.anchor.row or want.anchor.col != got.anchor.col) {
- appendf(arena, &buf, " anchor: helix ({d},{d}) != pardes ({d},{d})\n", .{ @as(i64, want.anchor.row), @as(i64, want.anchor.col), @as(i64, got.anchor.row), @as(i64, got.anchor.col) });
+ appendf(arena, &buf, " anchor: {s} ({d},{d}) != {s} ({d},{d})\n", .{ wl, @as(i64, want.anchor.row), @as(i64, want.anchor.col), gl, @as(i64, got.anchor.row), @as(i64, got.anchor.col) });
}
return buf.items;
}
@@ -175,7 +218,7 @@ fn appendf(arena: std.mem.Allocator, buf: *std.ArrayList(u8), comptime fmt: []co
buf.appendSlice(arena, s) catch {};
}
-fn runCase(arena: std.mem.Allocator, case: Case) !Result {
+fn runCase(arena: std.mem.Allocator, case: Case, pane_kind: []const u8) !Result {
// fresh core per case: the tty_only boot — one shell pane, tty mode, no
// greeting. Live-session contract: sizes arrive as the first resize event.
const core = try pardes.Pardes.init(gpa, .{ .tty_only = true });
@@ -183,9 +226,9 @@ fn runCase(arena: std.mem.Allocator, case: Case) !Result {
core.update(.{ .resize = .{ .cols = 80, .rows = 24 } });
pump(core);
- if (std.mem.eql(u8, case.pane, "file")) {
+ if (std.mem.eql(u8, pane_kind, "file")) {
_ = try core.hxOpenFileContent(case.text);
- } else if (std.mem.eql(u8, case.pane, "tty")) {
+ } else if (std.mem.eql(u8, pane_kind, "tty")) {
// the harness is the pty here: cook \n to \r\n the way ONLCR would
var cooked: std.ArrayList(u8) = .empty;
for (case.text) |ch| {
@@ -205,7 +248,7 @@ fn runCase(arena: std.mem.Allocator, case: Case) !Result {
pane.cur_col = 0;
pane.cur_pinned = true;
}
- } else fatal("case {s}: unknown pane {s}", .{ case.name, case.pane });
+ } else fatal("case {s}: unknown pane {s}", .{ case.name, pane_kind });
// keys: helix notation — printables verbatim, <...> specials
var ki: usize = 0;
@@ -280,15 +323,16 @@ fn runCase(arena: std.mem.Allocator, case: Case) !Result {
text = f.content;
} else {
// the tty motion surface, dumped honestly: whole scrollback+grid,
- // prompt rows blanked, typed runs spliced — plus the trailing '\n'
- // ghostty's dump trims off the cursor's empty final row
+ // prompt rows blanked, the edit buffer standing in. The surface keeps
+ // the cursor's empty row below the output (see Pardes.shellRows), so
+ // its line list is exactly a file content's — join it and you have the
+ // same bytes, trailing newline included, with nothing to patch up.
const pl = try core.paneCursorLines(pane);
var buf: std.ArrayList(u8) = .empty;
for (pl.lines, 0..) |ln, i| {
if (i > 0) try buf.append(arena, '\n');
try buf.appendSlice(arena, ln);
}
- if (buf.items.len > 0 and buf.items[buf.items.len - 1] != '\n') try buf.append(arena, '\n');
text = buf.items;
}
var anchor: Pos = .{ .row = pane.cur_row, .col = pane.cur_col };
diff --git a/test/snapshots/chordcut.golden b/test/snapshots/chordcut.golden
index eefc6fa3..d4496d09 100644
--- a/test/snapshots/chordcut.golden
+++ b/test/snapshots/chordcut.golden
@@ -379,8 +379,8 @@
| 4 line 4line
| 5 lline 5
| 6 canon -echo; printf '\x f.txt
-| 7 ne; printf '\x1b[?1000l BOO
-| 8
+| 7 BOO
+|
|
|
|
diff --git a/test/snapshots/edit.golden b/test/snapshots/edit.golden
index 6815dbd6..2d11868f 100644
--- a/test/snapshots/edit.golden
+++ b/test/snapshots/edit.golden
@@ -1,4 +1,4 @@
-== snap inserted grid=100x30 cursor=4,9
+== snap inserted grid=100x30 cursor=16,9
|Kill Newcol Tutor Debug Colors NextColor Crt Dump
| NOR /tmp/pardes-snap/edit/cwd Del NOR /tmp/pardes-snap/edit/cwd Del
|
diff --git a/test/snapshots/exec-bigdrag.golden b/test/snapshots/exec-bigdrag.golden
index 5ea48200..19189267 100644
--- a/test/snapshots/exec-bigdrag.golden
+++ b/test/snapshots/exec-bigdrag.golden
@@ -1,4 +1,4 @@
-== snap alive grid=200x60 cursor=104,37
+== snap alive grid=200x60 cursor=119,37
|Kill Newcol Tutor Debug Colors NextColor Crt Dump
| NOR /tmp/pardes-snap/exec-bigdrag/cwd Del NOR /tmp/pardes-snap/exec-bigdrag/cwd Del
| bash: line: command not found
diff --git a/test/snapshots/exec.golden b/test/snapshots/exec.golden
index 5f1470c9..00211a42 100644
--- a/test/snapshots/exec.golden
+++ b/test/snapshots/exec.golden
@@ -1,4 +1,4 @@
-== snap exec grid=100x30 cursor=4,7
+== snap exec grid=100x30 cursor=16,7
|Kill Newcol Tutor Debug Colors NextColor Crt Dump
| NOR /tmp/pardes-snap/exec/cwd Del NOR /tmp/pardes-snap/exec/cwd Del
|
diff --git a/test/snapshots/fsearch.golden b/test/snapshots/fsearch.golden
index 8a7aabd8..8ea0ffbf 100644
--- a/test/snapshots/fsearch.golden
+++ b/test/snapshots/fsearch.golden
@@ -69,19 +69,19 @@
| 4 l4
| 5 l5
| 6 MARK b g.txt
-| 7 l7 /tmp/pardes-snap/fsearch/cwd/g.txt:2:MARK a
-| 8 l8 /tmp/pardes-snap/fsearch/cwd/g.txt:6:MARK b
-| 9 l9 /tmp/pardes-snap/fsearch/cwd/g.txt:10:MARK c
+| 7 l7
+| 8 l8
+| 9 l9
| 10 MARK c
| 11 l11
| 12 l12
|
|
-| NOR /tmp/pardes-snap/fsearch/cwd Del
-|
-| g.txt
-|
-|
+| NOR /tmp/pardes-snap/fsearch/cwd/+Search Del NOR /tmp/pardes-snap/fsearch/cwd Del
+| 1 /tmp/pardes-snap/fsearch/cwd/g.txt:2:1 MARK a
+| 2 /tmp/pardes-snap/fsearch/cwd/g.txt:6:1 MARK b g.txt
+| 3 /tmp/pardes-snap/fsearch/cwd/g.txt:10:1 MARK c
+| 4
|
|
|
@@ -100,19 +100,19 @@
| 4 l4
| 5 l5
| 6 MARK b g.txt
-| 7 l7 /tmp/pardes-snap/fsearch/cwd/g.txt:2:MARK a
-| 8 l8 /tmp/pardes-snap/fsearch/cwd/g.txt:6:MARK b
-| 9 l9 /tmp/pardes-snap/fsearch/cwd/g.txt:10:MARK c
+| 7 l7
+| 8 l8
+| 9 l9
| 10 MARK c
| 11 l11
| 12 l12
|
|
-| NOR /tmp/pardes-snap/fsearch/cwd Del
-|
-| g.txt
-|
-|
+| NOR /tmp/pardes-snap/fsearch/cwd/+Search Del NOR /tmp/pardes-snap/fsearch/cwd Del
+| 1 /tmp/pardes-snap/fsearch/cwd/g.txt:2:1 MARK a
+| 2 /tmp/pardes-snap/fsearch/cwd/g.txt:6:1 MARK b g.txt
+| 3 /tmp/pardes-snap/fsearch/cwd/g.txt:10:1 MARK c
+| 4
|
|
|
@@ -131,7 +131,7 @@
|5: 0-1 d,#525252, 2-6 #5a5a5a,d, 7-59 d,d, 60-61 d,#525252, 62-119 d,d, 120-121 d,#525252, 122-179 d,d,
|6: 0-1 d,#525252, 2-6 #5a5a5a,d, 7-59 d,d, 60-61 d,#525252, 62-119 d,d, 120-121 d,#525252, 122-179 d,d,
|7: 0-1 d,#525252, 2-6 #5a5a5a,d, 7-59 d,d, 60-61 d,#525252, 62-119 d,d, 120-121 d,#525252, 122-179 d,d,
-|8: 0-1 d,#525252, 2-6 #5a5a5a,d, 7-59 d,d, 60-61 d,#525252, 62-119 #d8c8a8,#4a3a1e, 120-121 d,#525252, 122-179 d,d,
+|8: 0-1 d,#525252, 2-6 #5a5a5a,d, 7-59 d,d, 60-61 d,#525252, 62-119 d,d, 120-121 d,#525252, 122-179 d,d,
|9: 0-1 d,#525252, 2-6 #5a5a5a,d, 7-59 d,d, 60-61 d,#525252, 62-119 d,d, 120-121 d,#525252, 122-179 d,d,
|10: 0-1 d,#525252, 2-6 #5a5a5a,d, 7-59 d,d, 60-61 d,#525252, 62-119 d,d, 120-121 d,#525252, 122-179 d,d,
|11: 0-1 d,#525252, 2-6 #5a5a5a,d, 7-59 d,d, 60-61 d,#525252, 62-119 d,d, 120-121 d,#525252, 122-179 d,d,
@@ -139,8 +139,8 @@
|13: 0-1 d,#525252, 2-6 #5a5a5a,d, 7-59 d,d, 60-61 d,#525252, 62-119 d,d, 120-121 d,#525252, 122-179 d,d,
|14: 0-1 d,#525252, 2-6 #5a5a5a,d, 7-59 d,d, 60-61 d,#525252, 62-119 d,d, 120-121 d,#525252, 122-179 d,d,
|15: 0-1 d,#525252, 2-6 #5a5a5a,d, 7-59 d,d, 60-61 d,#525252, 62-119 d,d, 120-121 d,#525252, 122-179 d,d,
-|16: 0-1 d,#525252, 2-6 #5a5a5a,d, 7-59 d,d, 60-61 d,#383c54, 62-97 #969696,#2c2a3e, 98-119 d,#2c2a3e, 120-121 d,#525252, 122-179 d,d,
-|17: 0-1 d,#525252, 2-6 #5a5a5a,d, 7-59 d,d, 60-61 d,#525252, 62-119 d,d, 120-121 d,#525252, 122-179 d,d,
+|16: 0-1 d,#383c54, 2-45 #969696,#2c2a3e, 46-59 d,#2c2a3e, 60-61 d,#383c54, 62-97 #969696,#2c2a3e, 98-119 d,#2c2a3e, 120-121 d,#525252, 122-179 d,d,
+|17: 0-1 d,#525252, 2-6 #5a5a5a,d, 7-59 #d8c8a8,#4a3a1e, 60-61 d,#525252, 62-119 d,d, 120-121 d,#525252, 122-179 d,d,
|18: 0-1 d,#525252, 2-6 #5a5a5a,d, 7-59 d,d, 60-61 d,#525252, 62-119 d,d, 120-121 d,#525252, 122-179 d,d,
|19: 0-1 d,#525252, 2-6 #5a5a5a,d, 7-59 d,d, 60-61 d,#525252, 62-119 d,d, 120-121 d,#525252, 122-179 d,d,
|20: 0-1 d,#525252, 2-6 #5a5a5a,d, 7-59 d,d, 60-61 d,#525252, 62-119 d,d, 120-121 d,#525252, 122-179 d,d,
@@ -162,17 +162,20 @@
| 4 l4
| 5 l5
| 6 MARK b g.txt
-| 7 l7 /tmp/pardes-snap/fsearch/cwd/g.txt:2:MARK a
-| 8 l8 /tmp/pardes-snap/fsearch/cwd/g.txt:6:MARK b
-| 9 l9 /tmp/pardes-snap/fsearch/cwd/g.txt:10:MARK c
+| 7 l7
+| 8 l8
+| 9 l9
| 10 MARK c
| 11 l11
| 12 l12
|
|
-| NOR /tmp/pardes-snap/fsearch/cwd Del
+| NOR /tmp/pardes-snap/fsearch/cwd/+Search Del NOR /tmp/pardes-snap/fsearch/cwd Del
+| 1 /tmp/pardes-snap/fsearch/cwd/g.txt:2:1 MARK a
+| 2 /tmp/pardes-snap/fsearch/cwd/g.txt:6:1 MARK b g.txt
+| 3 /tmp/pardes-snap/fsearch/cwd/g.txt:10:1 MARK c
+| 4
|
-| g.txt
|
|
|
@@ -181,31 +184,28 @@
|
|
|
-|
-|
-|
-== snap third grid=180x30 cursor=7,11
+== snap third grid=180x30 cursor=7,9
|Kill Newcol Tutor Debug Colors NextColor Crt Dump
| NOR /tmp/pardes-snap/fsearch/cwd/g.txt Save Del NOR /tmp/pardes-snap/fsearch/cwd Del NOR /tmp/pardes-snap/fsearch/cwd Del
-| 1 l1
-| 2 MARK a g.txt g.txt
| 3 l3
-| 4 l4
+| 4 l4 g.txt g.txt
| 5 l5
-| 6 MARK b g.txt
-| 7 l7 /tmp/pardes-snap/fsearch/cwd/g.txt:2:MARK a
-| 8 l8 /tmp/pardes-snap/fsearch/cwd/g.txt:6:MARK b
-| 9 l9 /tmp/pardes-snap/fsearch/cwd/g.txt:10:MARK c
+| 6 MARK b
+| 7 l7
+| 8 l8 g.txt
+| 9 l9
| 10 MARK c
| 11 l11
| 12 l12
|
|
-| NOR /tmp/pardes-snap/fsearch/cwd Del
-|
-| g.txt
|
|
+| NOR /tmp/pardes-snap/fsearch/cwd/+Search Del NOR /tmp/pardes-snap/fsearch/cwd Del
+| 1 /tmp/pardes-snap/fsearch/cwd/g.txt:2:1 MARK a
+| 2 /tmp/pardes-snap/fsearch/cwd/g.txt:6:1 MARK b g.txt
+| 3 /tmp/pardes-snap/fsearch/cwd/g.txt:10:1 MARK c
+| 4
|
|
|
@@ -224,19 +224,19 @@
| 4 l4
| 5 l5
| 6 MARK b g.txt
-| 7 l7 /tmp/pardes-snap/fsearch/cwd/g.txt:2:MARK a
-| 8 l8 /tmp/pardes-snap/fsearch/cwd/g.txt:6:MARK b
-| 9 l9 /tmp/pardes-snap/fsearch/cwd/g.txt:10:MARK c
+| 7 l7
+| 8 l8
+| 9 l9
| 10 MARK c
| 11 l11
| 12 l12
|
|
-| NOR /tmp/pardes-snap/fsearch/cwd Del
-|
-| g.txt
-|
-|
+| NOR /tmp/pardes-snap/fsearch/cwd/+Search Del NOR /tmp/pardes-snap/fsearch/cwd Del
+| 1 /tmp/pardes-snap/fsearch/cwd/g.txt:2:1 MARK a
+| 2 /tmp/pardes-snap/fsearch/cwd/g.txt:6:1 MARK b g.txt
+| 3 /tmp/pardes-snap/fsearch/cwd/g.txt:10:1 MARK c
+| 4
|
|
|
@@ -255,19 +255,19 @@
| 4 l4
| 5 l5
| 6 MARK b g.txt
-| 7 l7 /tmp/pardes-snap/fsearch/cwd/g.txt:2:MARK a
-| 8 l8 /tmp/pardes-snap/fsearch/cwd/g.txt:6:MARK b
-| 9 l9 /tmp/pardes-snap/fsearch/cwd/g.txt:10:MARK c
+| 7 l7
+| 8 l8
+| 9 l9
| 10 MARK c
| 11 l11
| 12 l12
|
|
-| NOR /tmp/pardes-snap/fsearch/cwd Del
-|
-| g.txt
-|
-|
+| NOR /tmp/pardes-snap/fsearch/cwd/+Search Del NOR /tmp/pardes-snap/fsearch/cwd Del
+| 1 /tmp/pardes-snap/fsearch/cwd/g.txt:2:1 MARK a
+| 2 /tmp/pardes-snap/fsearch/cwd/g.txt:6:1 MARK b g.txt
+| 3 /tmp/pardes-snap/fsearch/cwd/g.txt:10:1 MARK c
+| 4
|
|
|
@@ -286,16 +286,16 @@
| 4 l4
| 5 l5
| 6 MARK b g.txt
-| 7 l7 /tmp/pardes-snap/fsearch/cwd/g.txt:2:MARK a
-| 8 l8 /tmp/pardes-snap/fsearch/cwd/g.txt:6:MARK b
-| 9 l9 /tmp/pardes-snap/fsearch/cwd/g.txt:10:MARK c
+| 7 l7
+| 8 l8
+| 9 l9
| 10 MARK c
| 11 l11
| 12 l12
|
|
-| NOR /tmp/pardes-snap/fsearch/cwd Del
-|
+| NOR /tmp/pardes-snap/fsearch/cwd/+Search Del NOR /tmp/pardes-snap/fsearch/cwd Del
+| 1
| g.txt
|
|
diff --git a/test/snapshots/fsearch.snap b/test/snapshots/fsearch.snap
index 38c39c02..7953b408 100644
--- a/test/snapshots/fsearch.snap
+++ b/test/snapshots/fsearch.snap
@@ -1,8 +1,9 @@
# `/` file search: the pattern types into the tag (persistent, no popup);
-# Enter runs grep -nH into a shell pane so the results are ordinary terminal
-# text; n/N select the next/prev result row there (prompt rows skipped via the
-# OSC 133 semantic marks) and look it — the file pane scrolls to path:NNN.
-# Esc abandons the input; a no-match search leaves n with nowhere to go.
+# Enter searches the buffer in-core and writes `path:NNN: text` into a +Search
+# OUTPUT BUFFER (a file pane with no file behind it — no Save in its tag);
+# n/N select the next/prev row there and look it, so the file pane scrolls to
+# path:NNN. Esc abandons the input; a no-match search empties the buffer and
+# leaves n with nowhere to go.
file g.txt l1\nMARK a\nl3\nl4\nl5\nMARK b\nl7\nl8\nl9\nMARK c\nl11\nl12
start 30 180 -n 3
wait 8000 g.txt
@@ -28,10 +29,10 @@ text MARK
stable 400 5000
snap typing
key enter
-wait 10000 g.txt:2:MARK
+wait 10000 g.txt:2:1 MARK
stable 700 10000
snap results
-# n selects result 1 in the results pane + scrolls the file to line 2
+# n selects result 1 in the results buffer + scrolls the file to line 2
key n
stable 700 15000
snap first
diff --git a/test/snapshots/images.golden b/test/snapshots/images.golden
index c5a2322e..fc5af97b 100644
--- a/test/snapshots/images.golden
+++ b/test/snapshots/images.golden
@@ -1,4 +1,4 @@
-== snap img-open grid=150x30 cursor=4,7
+== snap img-open grid=150x30 cursor=11,7
|Kill Newcol Tutor Debug Colors NextColor Crt Dump
| img /tmp/pardes-snap/images/cwd/pic.ppm Petscii NOR /tmp/pardes-snap/images/cwd Del NOR /tmp/pardes-snap/images/cwd Del
|
diff --git a/test/snapshots/layout-open.golden b/test/snapshots/layout-open.golden
index c4ab4219..6ccc8467 100644
--- a/test/snapshots/layout-open.golden
+++ b/test/snapshots/layout-open.golden
@@ -1,4 +1,4 @@
-== snap before grid=100x30 cursor=4,7
+== snap before grid=100x30 cursor=2,8
|Kill Newcol Tutor Debug Colors NextColor Crt Dump
| NOR /tmp/pardes-snap/layout-open/cwd Del NOR /tmp/pardes-snap/layout-open/cwd Del
|
diff --git a/test/snapshots/leader.golden b/test/snapshots/leader.golden
new file mode 100644
index 00000000..9bf559c0
--- /dev/null
+++ b/test/snapshots/leader.golden
@@ -0,0 +1,369 @@
+== snap spc grid=100x40 cursor=7,2
+|Kill Newcol Tutor Debug Colors NextColor Crt Dump
+| NOR /tmp/pardes-snap/leader/cwd/cmds.txt Sav SPC NOR /tmp/pardes-snap/leader/cwd Del
+| 1 Help
+| cmds.txt
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+== snap spc-h grid=100x40 cursor=7,2
+|Kill Newcol Tutor Debug Colors NextColor Crt Dump
+| NOR /tmp/pardes-snap/leader/cwd/cmds.txt S SPC h NOR /tmp/pardes-snap/leader/cwd Del
+| 1 Help
+| cmds.txt
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+== snap abandoned grid=100x40 cursor=7,2
+|Kill Newcol Tutor Debug Colors NextColor Crt Dump
+| NOR /tmp/pardes-snap/leader/cwd/cmds.txt Save De NOR /tmp/pardes-snap/leader/cwd Del
+| 1 Help
+| cmds.txt
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+== snap unmapped grid=100x40 cursor=7,2
+|Kill Newcol Tutor Debug Colors NextColor Crt Dump
+| NOR /tmp/pardes-snap/leader/cwd/cmds.txt Save De NOR /tmp/pardes-snap/leader/cwd Del
+| 1 Help
+| cmds.txt
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+== snap help-word grid=100x40 cursor=7,22
+|Kill Newcol Tutor Debug Colors NextColor Crt Dump
+| NOR /tmp/pardes-snap/leader/cwd/cmds.txt Save De NOR /tmp/pardes-snap/leader/cwd Del
+| 1 Help
+| cmds.txt
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+| NOR /tmp/pardes-snap/leader/cwd/+Help Del
+| 1 pardes builtins - SPC then the keys,
+| 2 or execute the name anywhere
+| 3
+| 4 SPC ? Help
+| 5 SPC c d Delcol
+| 6 SPC c n Newcol
+| 7 SPC d Del
+| 8 SPC h t Tutor
+| 9 SPC k Kill
+| 10 SPC s d Dump
+| 11 SPC s r Restore
+| 12 SPC t c Colors
+| 13 SPC t d Debug
+| 14 SPC t n NextColor
+| 15 SPC t r Crt
+| 16 SPC w Save
+| 17
+|
+== snap help-group grid=100x40 cursor=7,22
+|Kill Newcol Tutor Debug Colors NextColor Crt Dump
+| NOR /tmp/pardes-snap/leader/cwd/cmds.txt Save De NOR /tmp/pardes-snap/leader/cwd Del
+| 1 Help
+| cmds.txt
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+| NOR /tmp/pardes-snap/leader/cwd/+Help Del
+| 1 pardes builtins under SPC h
+| 2
+| 3 SPC h t Tutor
+| 4
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+== snap tutor grid=100x40 cursor=7,32
+|Kill Newcol Tutor Debug Colors NextColor Crt Dump
+| NOR /tmp/pardes-snap/leader/cwd/cmds.txt Save De NOR /tmp/pardes-snap/leader/cwd Del
+| 1 Help
+| cmds.txt
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+| NOR /tmp/pardes-snap/leader/cwd/+Help Del
+| 1 pardes builtins under SPC h
+| 2
+| 3 SPC h t Tutor
+| 4
+|
+|
+|
+|
+|
+| NOR /Tutor Save Del
+| 1
+| 2 PARDES TUTOR
+| 3 tmux + vi + acme, from
+| 4
+| 5 The model is acme's: EVERYTHING is
+| 6 the same way. Modal editing (helix-
+| 7 Press j until you reach the introdu
+| 8
+== snap deleted grid=100x40 cursor=7,22
+|Kill Newcol Tutor Debug Colors NextColor Crt Dump
+| NOR /tmp/pardes-snap/leader/cwd/cmds.txt Save De NOR /tmp/pardes-snap/leader/cwd Del
+| 1 Help
+| cmds.txt
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+| NOR /tmp/pardes-snap/leader/cwd/+Help Del
+| 1 pardes builtins under SPC h
+| 2
+| 3 SPC h t Tutor
+| 4
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+== snap help-all grid=100x40 cursor=7,22
+|Kill Newcol Tutor Debug Colors NextColor Crt Dump
+| NOR /tmp/pardes-snap/leader/cwd/cmds.txt Save De NOR /tmp/pardes-snap/leader/cwd Del
+| 1 Help
+| cmds.txt
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+| NOR /tmp/pardes-snap/leader/cwd/+Help Del
+| 1 pardes builtins - SPC then the keys,
+| 2 or execute the name anywhere
+| 3
+| 4 SPC ? Help
+| 5 SPC c d Delcol
+| 6 SPC c n Newcol
+| 7 SPC d Del
+| 8 SPC h t Tutor
+| 9 SPC k Kill
+| 10 SPC s d Dump
+| 11 SPC s r Restore
+| 12 SPC t c Colors
+| 13 SPC t d Debug
+| 14 SPC t n NextColor
+| 15 SPC t r Crt
+| 16 SPC w Save
+| 17
+|
diff --git a/test/snapshots/leader.snap b/test/snapshots/leader.snap
new file mode 100644
index 00000000..9da5f288
--- /dev/null
+++ b/test/snapshots/leader.snap
@@ -0,0 +1,64 @@
+# The SPC leader: every builtin also has a KEY PATH, and SPC walks it. While a
+# path is pending the active pane's tag shows it at its RIGHT edge (the `/`
+# input's rule — state you can see — right-aligned so it never collides with
+# the tail's builtins). `?` at any depth opens the Help builtin, listing what
+# the prefix typed so far can still reach, into a +Help OUTPUT BUFFER (a file
+# pane with no file behind it, like +Search). The paths, the Help lines and
+# the execute dispatch are all folded out of ONE enum at comptime — so
+# executing the WORD `Help` runs the same builtin the leader does.
+file cmds.txt Help
+start 40 100 cmds.txt
+wait 8000 Kill Newcol
+wait 8000 cmds.txt
+stable 700 20000
+# SPC arms the leader: the indicator appears on the active pane's tag
+key space
+stable 400 5000
+snap spc
+# ... and grows as the path is typed (`h` = the docs group)
+key h
+stable 400 5000
+snap spc-h
+# Esc abandons the sequence: indicator gone, nothing ran, cursor untouched
+key esc
+settle 700
+stable 400 5000
+snap abandoned
+# an UNMAPPED key abandons too, rather than waiting: a typo must not leave the
+# next keystroke armed at a builtin that closes panes
+key space
+key q
+stable 400 5000
+snap unmapped
+# executing the WORD `Help` (middle-click) runs the builtin — the enum IS the
+# lookup, so a name works anywhere text lives. Every builtin, with its keys.
+press middle 9 3
+release middle 9 3
+wait 10000 SPC h t
+stable 700 15000
+snap help-word
+# `SPC h ?`: only the `h` group, rewritten into the SAME +Help buffer
+key space
+key h
+key ?
+wait 10000 under SPC h
+stable 700 15000
+snap help-group
+# `SPC h t`: the Tutor builtin, zero arguments
+key space
+key h
+key t
+wait 10000 PARDES TUTOR
+stable 700 15000
+snap tutor
+# `SPC d`: Del is pane-scoped — it closes the tutor, focus falls back
+key space
+key d
+stable 700 15000
+snap deleted
+# `SPC ?` at the root lists everything again
+key space
+key ?
+wait 10000 SPC s r
+stable 700 15000
+snap help-all
diff --git a/test/snapshots/modal.golden b/test/snapshots/modal.golden
index 57b68c99..b94e986f 100644
--- a/test/snapshots/modal.golden
+++ b/test/snapshots/modal.golden
@@ -246,7 +246,7 @@
|27: 0-1 d,#525252, 2-49 d,d, 50-51 d,#525252, 52-99 d,d,
|28: 0-1 d,#525252, 2-49 d,d, 50-51 d,#525252, 52-99 d,d,
|29: 0-1 d,#525252, 2-49 d,d, 50-51 d,#525252, 52-99 d,d,
-== snap typed grid=100x30 cursor=2,6
+== snap typed grid=100x30 cursor=7,6
|Kill Newcol Tutor Debug Colors NextColor Crt Dump
| NOR /tmp/pardes-snap/modal/cwd Del NOR /tmp/pardes-snap/modal/cwd Del
|
@@ -308,7 +308,7 @@
|
|
|
-== snap redone grid=100x30 cursor=2,6
+== snap redone grid=100x30 cursor=7,6
|Kill Newcol Tutor Debug Colors NextColor Crt Dump
| NOR /tmp/pardes-snap/modal/cwd Del NOR /tmp/pardes-snap/modal/cwd Del
|
@@ -339,7 +339,7 @@
|
|
|
-== snap bottom grid=100x30 cursor=2,8
+== snap bottom grid=100x30 cursor=2,9
|Kill Newcol Tutor Debug Colors NextColor Crt Dump
| NOR /tmp/pardes-snap/modal/cwd Del NOR /tmp/pardes-snap/modal/cwd Del
|
diff --git a/test/snapshots/psearch.golden b/test/snapshots/psearch.golden
new file mode 100644
index 00000000..8e5443d5
--- /dev/null
+++ b/test/snapshots/psearch.golden
@@ -0,0 +1,310 @@
+== snap output grid=140x30 cursor=4,8
+|Kill Newcol Tutor Debug Colors NextColor Crt Dump
+| NOR /tmp/pardes-snap/psearch/cwd Del
+|
+| x MARK a
+| zz
+| x MARK b
+| zz
+| x MARK c
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+== snap typing grid=140x30 cursor=44,1
+|Kill Newcol Tutor Debug Colors NextColor Crt Dump
+| INS /tmp/pardes-snap/psearch/cwd Del /MARK
+|
+| x MARK a
+| zz
+| x MARK b
+| zz
+| x MARK c
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+== snap results grid=140x30 cursor=74,8
+|Kill Newcol Tutor Debug Colors NextColor Crt Dump
+| NOR /tmp/pardes-snap/psearch/cwd/+Search Del NOR /tmp/pardes-snap/psearch/cwd Del
+| 1 @p0:2:3 x MARK a
+| 2 @p0:4:3 x MARK b x MARK a
+| 3 @p0:6:3 x MARK c zz
+| 4 x MARK b
+| zz
+| x MARK c
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+== snap first grid=140x30 cursor=74,3
+|Kill Newcol Tutor Debug Colors NextColor Crt Dump
+| NOR /tmp/pardes-snap/psearch/cwd/+Search Del NOR /tmp/pardes-snap/psearch/cwd Del
+| 1 @p0:2:3 x MARK a
+| 2 @p0:4:3 x MARK b x MARK a
+| 3 @p0:6:3 x MARK c zz
+| 4 x MARK b
+| zz
+| x MARK c
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+== style first
+|0: 0-48 #969696,#2c2a3e, 49-139 d,#2c2a3e,
+|1: 0-1 d,#383c54, 2-45 #969696,#2c2a3e, 46-69 d,#2c2a3e, 70-71 d,#6e4fd0, 72-107 #969696,#2c2a3e, 108-139 d,#2c2a3e,
+|2: 0-1 d,#525252, 2-6 #5a5a5a,d, 7-69 #d8c8a8,#4a3a1e, 70-71 d,#525252, 72-139 d,d,
+|3: 0-1 d,#525252, 2-6 #5a5a5a,d, 7-69 d,d, 70-71 d,#525252, 72-139 d,d,
+|4: 0-1 d,#525252, 2-6 #5a5a5a,d, 7-69 d,d, 70-71 d,#525252, 72-139 d,d,
+|5: 0-1 d,#525252, 2-6 #5a5a5a,d, 7-69 d,d, 70-71 d,#525252, 72-139 d,d,
+|6: 0-1 d,#525252, 2-6 #5a5a5a,d, 7-69 d,d, 70-71 d,#525252, 72-139 d,d,
+|7: 0-1 d,#525252, 2-6 #5a5a5a,d, 7-69 d,d, 70-71 d,#525252, 72-139 d,d,
+|8: 0-1 d,#525252, 2-6 #5a5a5a,d, 7-69 d,d, 70-71 d,#525252, 72-139 d,d,
+|9: 0-1 d,#525252, 2-6 #5a5a5a,d, 7-69 d,d, 70-71 d,#525252, 72-139 d,d,
+|10: 0-1 d,#525252, 2-6 #5a5a5a,d, 7-69 d,d, 70-71 d,#525252, 72-139 d,d,
+|11: 0-1 d,#525252, 2-6 #5a5a5a,d, 7-69 d,d, 70-71 d,#525252, 72-139 d,d,
+|12: 0-1 d,#525252, 2-6 #5a5a5a,d, 7-69 d,d, 70-71 d,#525252, 72-139 d,d,
+|13: 0-1 d,#525252, 2-6 #5a5a5a,d, 7-69 d,d, 70-71 d,#525252, 72-139 d,d,
+|14: 0-1 d,#525252, 2-6 #5a5a5a,d, 7-69 d,d, 70-71 d,#525252, 72-139 d,d,
+|15: 0-1 d,#525252, 2-6 #5a5a5a,d, 7-69 d,d, 70-71 d,#525252, 72-139 d,d,
+|16: 0-1 d,#525252, 2-6 #5a5a5a,d, 7-69 d,d, 70-71 d,#525252, 72-139 d,d,
+|17: 0-1 d,#525252, 2-6 #5a5a5a,d, 7-69 d,d, 70-71 d,#525252, 72-139 d,d,
+|18: 0-1 d,#525252, 2-6 #5a5a5a,d, 7-69 d,d, 70-71 d,#525252, 72-139 d,d,
+|19: 0-1 d,#525252, 2-6 #5a5a5a,d, 7-69 d,d, 70-71 d,#525252, 72-139 d,d,
+|20: 0-1 d,#525252, 2-6 #5a5a5a,d, 7-69 d,d, 70-71 d,#525252, 72-139 d,d,
+|21: 0-1 d,#525252, 2-6 #5a5a5a,d, 7-69 d,d, 70-71 d,#525252, 72-139 d,d,
+|22: 0-1 d,#525252, 2-6 #5a5a5a,d, 7-69 d,d, 70-71 d,#525252, 72-139 d,d,
+|23: 0-1 d,#525252, 2-6 #5a5a5a,d, 7-69 d,d, 70-71 d,#525252, 72-139 d,d,
+|24: 0-1 d,#525252, 2-6 #5a5a5a,d, 7-69 d,d, 70-71 d,#525252, 72-139 d,d,
+|25: 0-1 d,#525252, 2-6 #5a5a5a,d, 7-69 d,d, 70-71 d,#525252, 72-139 d,d,
+|26: 0-1 d,#525252, 2-6 #5a5a5a,d, 7-69 d,d, 70-71 d,#525252, 72-139 d,d,
+|27: 0-1 d,#525252, 2-6 #5a5a5a,d, 7-69 d,d, 70-71 d,#525252, 72-139 d,d,
+|28: 0-1 d,#525252, 2-6 #5a5a5a,d, 7-69 d,d, 70-71 d,#525252, 72-139 d,d,
+|29: 0-1 d,#525252, 2-6 #5a5a5a,d, 7-69 d,d, 70-71 d,#525252, 72-139 d,d,
+== snap second grid=140x30 cursor=74,5
+|Kill Newcol Tutor Debug Colors NextColor Crt Dump
+| NOR /tmp/pardes-snap/psearch/cwd/+Search Del NOR /tmp/pardes-snap/psearch/cwd Del
+| 1 @p0:2:3 x MARK a
+| 2 @p0:4:3 x MARK b x MARK a
+| 3 @p0:6:3 x MARK c zz
+| 4 x MARK b
+| zz
+| x MARK c
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+== snap back grid=140x30 cursor=74,3
+|Kill Newcol Tutor Debug Colors NextColor Crt Dump
+| NOR /tmp/pardes-snap/psearch/cwd/+Search Del NOR /tmp/pardes-snap/psearch/cwd Del
+| 1 @p0:2:3 x MARK a
+| 2 @p0:4:3 x MARK b x MARK a
+| 3 @p0:6:3 x MARK c zz
+| 4 x MARK b
+| zz
+| x MARK c
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+== snap nomatch grid=140x30 cursor=74,3
+|Kill Newcol Tutor Debug Colors NextColor Crt Dump
+| NOR /tmp/pardes-snap/psearch/cwd/+Search Del NOR /tmp/pardes-snap/psearch/cwd Del
+| 1
+| x MARK a
+| zz
+| x MARK b
+| zz
+| x MARK c
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+== snap ttyinert grid=140x30 cursor=74,8
+|Kill Newcol Tutor Debug Colors NextColor Crt Dump
+| NOR /tmp/pardes-snap/psearch/cwd/+Search Del TTY /tmp/pardes-snap/psearch/cwd Del
+| 1 $ printf 'x MA''RK a\nzz\nx MA''RK b\nzz\nx MA''RK c\n'
+| x MARK a
+| zz
+| x MARK b
+| zz
+| x MARK c
+| $
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+== snap rightsearch grid=140x30 cursor=74,3
+|Kill Newcol Tutor Debug Colors NextColor Crt Dump
+| NOR /tmp/pardes-snap/psearch/cwd/+Search Del NOR /tmp/pardes-snap/psearch/cwd Del
+| 1 @p0:2:3 x MARK a
+| 2 @p0:4:3 x MARK b x MARK a
+| 3 @p0:6:3 x MARK c zz
+| 4 x MARK b
+| zz
+| x MARK c
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
diff --git a/test/snapshots/psearch.snap b/test/snapshots/psearch.snap
new file mode 100644
index 00000000..4c5e74d0
--- /dev/null
+++ b/test/snapshots/psearch.snap
@@ -0,0 +1,68 @@
+# `/` search on a TERMINAL pane: the same tag input as a file pane, searching
+# the shell's own scrollback (the plain text view Look reads, prompt rows
+# hidden). Results land in a +Search OUTPUT BUFFER — a file pane with no file
+# behind it, so its tag has no Save — and point at `@p0:LINE`, the look target
+# that names a live pane, since a terminal has no path. n/N walk the buffer
+# and look each row, scrolling the terminal to that line of its scrollback.
+start 30 140
+wait 8000 Kill Newcol
+stable 700 20000
+# print something to search (a bare boot starts in tty mode; the marker is
+# split so the echoed command line, which normal mode hides anyway, can never
+# be the thing that matched)
+text printf 'x MA''RK a\nzz\nx MA''RK b\nzz\nx MA''RK c\n'
+key enter
+wait 10000 MARK c
+stable 700 10000
+key c-b
+stable 600 8000
+snap output
+# / types the pattern into the terminal's tag exactly as on a file pane
+key /
+stable 400 5000
+text MARK
+stable 400 5000
+snap typing
+# Enter opens the results buffer: three @p0:LINE:COL rows, focus stays here
+key enter
+wait 10000 @p0:2:3
+stable 700 15000
+snap results
+# n looks result 1: the terminal's cursor lands on that scrollback line, at
+# the matched COLUMN (3, past the leading "x ") — not the start of the line
+key n
+stable 700 15000
+snap first
+snapstyle first
+key n
+stable 700 15000
+snap second
+# N walks back
+key N
+stable 700 15000
+snap back
+# a no-match search empties the buffer; n then has nowhere to go
+key /
+text zzqq
+key enter
+stable 700 10000
+key n
+stable 400 5000
+snap nomatch
+# right-clicking a word that names no file is the same search (acme button 3),
+# but NOT in tty mode: there the click belongs to the program on the other end,
+# so the buffer stays empty
+key c-b
+stable 600 8000
+press right 75 4
+release right 75 4
+stable 700 10000
+snap ttyinert
+# back in normal mode the same click searches for MARK
+key c-b
+stable 600 8000
+press right 75 4
+release right 75 4
+wait 10000 @p0:2:3
+stable 700 15000
+snap rightsearch
diff --git a/test/snapshots/rsearch.golden b/test/snapshots/rsearch.golden
index c067d1ee..e716ef39 100644
--- a/test/snapshots/rsearch.golden
+++ b/test/snapshots/rsearch.golden
@@ -39,17 +39,17 @@
|
| code.txt
|
-| /tmp/pardes-snap/rsearch/cwd/code.txt:1:run count_x now
-| /tmp/pardes-snap/rsearch/cwd/code.txt:3:count_x again
|
|
|
|
|
-| NOR /tmp/pardes-snap/rsearch/cwd Del
|
-| code.txt sub
|
+| NOR /tmp/pardes-snap/rsearch/cwd/+Search Del NOR /tmp/pardes-snap/rsearch/cwd Del
+| 1 /tmp/pardes-snap/rsearch/cwd/code.txt:1:5 run count_x
+| 2 /tmp/pardes-snap/rsearch/cwd/code.txt:3:1 count_x aga code.txt sub
+| 3
|
|
|
@@ -70,18 +70,16 @@
|
| code.txt
|
-| /tmp/pardes-snap/rsearch/cwd/code.txt:1:run count_x now
-| /tmp/pardes-snap/rsearch/cwd/code.txt:3:count_x again
|
|
-| /tmp/pardes-snap/rsearch/cwd/code.txt:4:pick two words
|
|
-| NOR /tmp/pardes-snap/rsearch/cwd Del
|
-| code.txt sub
|
|
+| NOR /tmp/pardes-snap/rsearch/cwd/+Search Del NOR /tmp/pardes-snap/rsearch/cwd Del
+| 1 /tmp/pardes-snap/rsearch/cwd/code.txt:4:1 pick two wo
+| 2 code.txt sub
|
|
|
@@ -91,7 +89,9 @@
|
|
|
-== snap path-open grid=180x30 cursor=7,19
+|
+|
+== snap path-open grid=180x30 cursor=7,12
|Kill Newcol Tutor Debug Colors NextColor Crt Dump
| NOR /tmp/pardes-snap/rsearch/cwd/code.txt Save Del NOR /tmp/pardes-snap/rsearch/cwd Del NOR /tmp/pardes-snap/rsearch/cwd Del
| 1 run count_x now
@@ -101,20 +101,20 @@
|
| code.txt
|
-| /tmp/pardes-snap/rsearch/cwd/code.txt:1:run count_x now
-| /tmp/pardes-snap/rsearch/cwd/code.txt:3:count_x again
-|
-|
-| /tmp/pardes-snap/rsearch/cwd/code.txt:4:pick two words
-|
-|
-| NOR /tmp/pardes-snap/rsearch/cwd/sub/dep.txt Save Del NOR /tmp/pardes-snap/rsearch/cwd Del
+| NOR /tmp/pardes-snap/rsearch/cwd/sub/dep.txt Save Del
| 1 d1
-| 2 d2 code.txt sub
+| 2 d2
| 3 DEPTARGET
| 4 d4
| 5 d5
| 6 d6
+| NOR /tmp/pardes-snap/rsearch/cwd/+Search Del NOR /tmp/pardes-snap/rsearch/cwd Del
+| 1 /tmp/pardes-snap/rsearch/cwd/code.txt:4:1 pick two wo
+| 2 code.txt sub
+|
+|
+|
+|
|
|
|
diff --git a/test/snapshots/rsearch.snap b/test/snapshots/rsearch.snap
index e42d195a..16f40137 100644
--- a/test/snapshots/rsearch.snap
+++ b/test/snapshots/rsearch.snap
@@ -1,7 +1,7 @@
# acme button-3 in a text file = search: right-click / right-select reuse the
-# `/` grep machinery. A click expands the file-ish word (look.isFileChar) and,
-# when it names no file, greps it into a shell (results are ordinary terminal
-# text; n/N walk them like `/`). A right-select greps the swept span verbatim.
+# `/` machinery. A click expands the file-ish word (look.isFileChar) and, when
+# it names no file, searches the buffer for it — hits into the +Search output
+# buffer, n/N walk them like `/`. A right-select searches the span verbatim.
# A real path (sub/dep.txt:3) still resolves and OPENS — expansion keeps / . :
# so paths are not broken; only the non-file case falls through to search.
dirmk sub
@@ -25,10 +25,10 @@ wait 10000 count_x
stable 700 15000
snap opened
# right-CLICK the identifier `count_x` (line 1, the 'n' at col 15). It names no
-# file, so the click greps it: the results list both lines that contain it.
+# file, so the click searches for it: the results list both lines containing it.
press right 15 3
release right 15 3
-wait 10000 code.txt:1:run count_x
+wait 10000 code.txt:1:5 run count_x
stable 700 15000
snap search
# right-SELECT `pick two` (line 4, sweep cols 8..15): a multi-word span passes
@@ -36,7 +36,7 @@ snap search
press right 8 6
drag right 15 6
release right 15 6
-wait 10000 code.txt:4:pick two
+wait 10000 code.txt:4:1 pick two
stable 700 15000
snap select
# right-CLICK the path `sub/dep.txt:3` (line 2, col 18): isFileChar keeps / . :
diff --git a/test/snapshots/shrinktag.golden b/test/snapshots/shrinktag.golden
index 031f15ef..d087fbda 100644
--- a/test/snapshots/shrinktag.golden
+++ b/test/snapshots/shrinktag.golden
@@ -1,4 +1,4 @@
-== snap before grid=100x30 cursor=4,7
+== snap before grid=100x30 cursor=2,8
|Kill Newcol Tutor Debug Colors NextColor Crt Dump
| NOR /tmp/pardes-snap/shrinktag/cwd Del NOR /tmp/pardes-snap/shrinktag/cwd Del
|
@@ -29,7 +29,7 @@
|
|
|
-== snap shrunk grid=100x30 cursor=4,7
+== snap shrunk grid=100x30 cursor=2,8
|Kill Newcol Tutor Debug Colors NextColor Crt Dump
| NOR /tmp/pardes-snap/shrinktag/cwd Del NOR /tmp/pardes-snap/shrinktag/cwd Del
| NOR /tmp/pardes-snap/shrinktag/cwd Del
@@ -60,7 +60,7 @@
|
|
|
-== snap move-preview grid=100x30 cursor=4,7
+== snap move-preview grid=100x30 cursor=2,8
|Kill Newcol Tutor Debug Colors NextColor Crt Dump
| NOR /tmp/pardes-snap/shrinktag/cwd Del NOR /tmp/pardes-snap/shrinktag/cwd Del
| NOR /tmp/pardes-snap/shrinktag/cwd Del
@@ -91,7 +91,7 @@
|
|
|
-== snap restored grid=100x30 cursor=4,7
+== snap restored grid=100x30 cursor=2,8
|Kill Newcol Tutor Debug Colors NextColor Crt Dump
| NOR /tmp/pardes-snap/shrinktag/cwd Del NOR /tmp/pardes-snap/shrinktag/cwd Del
|
@@ -122,7 +122,7 @@
|
|
|
-== snap bottom-shrunk grid=100x30 cursor=4,7
+== snap bottom-shrunk grid=100x30 cursor=2,8
|Kill Newcol Tutor Debug Colors NextColor Crt Dump
| NOR /tmp/pardes-snap/shrinktag/cwd Del NOR /tmp/pardes-snap/shrinktag/cwd Del
|
diff --git a/test/snapshots/tty.golden b/test/snapshots/tty.golden
index d4cbb360..1f7b8c0b 100644
--- a/test/snapshots/tty.golden
+++ b/test/snapshots/tty.golden
@@ -91,3 +91,65 @@
|
|
|
+== snap shift-esc-on grid=100x30 cursor=4,6
+|Kill Newcol Tutor Debug Colors NextColor Crt Dump
+| TTY /tmp/pardes-snap/tty/cwd Del NOR /tmp/pardes-snap/tty/cwd Del
+| $ ls
+| m.txt m.txt
+| $ echo SNAP''OK
+| SNAPOK
+| $
+|
+|
+|
+|
+|
+|
+|
+|
+|
+| NOR /tmp/pardes-snap/tty/cwd Del
+|
+| m.txt
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+== snap shift-esc-off grid=100x30 cursor=4,6
+|Kill Newcol Tutor Debug Colors NextColor Crt Dump
+| NOR /tmp/pardes-snap/tty/cwd Del NOR /tmp/pardes-snap/tty/cwd Del
+|
+| m.txt m.txt
+|
+| SNAPOK
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+| NOR /tmp/pardes-snap/tty/cwd Del
+|
+| m.txt
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
diff --git a/test/snapshots/tty.snap b/test/snapshots/tty.snap
index 97eee3f5..a3af511d 100644
--- a/test/snapshots/tty.snap
+++ b/test/snapshots/tty.snap
@@ -1,4 +1,6 @@
-# tty mode round-trip: Ctrl-b in, run a command, Ctrl-b out
+# tty mode round-trip: Ctrl-b in, run a command, Ctrl-b out. Shift-Esc is the
+# same toggle (CSI 27;2u — needs a host that reports modifiers on Escape); a
+# PLAIN Esc is not, it still goes to the shell.
file m.txt marker
start 30 100 -n 3
wait 8000 m.txt
@@ -14,3 +16,10 @@ snap tty-echo
key c-b
stable 600 8000
snap tty-off
+# Shift-Esc: in, then out again — the same toggle from the other key
+send \e[27;2u
+stable 600 8000
+snap shift-esc-on
+send \e[27;2u
+stable 600 8000
+snap shift-esc-off
diff --git a/test/snapshots/ttyedit.golden b/test/snapshots/ttyedit.golden
new file mode 100644
index 00000000..6671634e
--- /dev/null
+++ b/test/snapshots/ttyedit.golden
@@ -0,0 +1,155 @@
+== snap before grid=100x30 cursor=7,6
+|Kill Newcol Tutor Debug Colors NextColor Crt Dump
+| NOR /tmp/pardes-snap/ttyedit/cwd Del NOR /tmp/pardes-snap/ttyedit/cwd Del
+|
+| words.txt words.txt
+|
+|
+| one two three
+| four five six
+| seven eight nine
+|
+|
+|
+|
+|
+|
+|
+| NOR /tmp/pardes-snap/ttyedit/cwd Del
+|
+| words.txt
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+== snap split grid=100x30 cursor=2,7
+|Kill Newcol Tutor Debug Colors NextColor Crt Dump
+| INS /tmp/pardes-snap/ttyedit/cwd Del NOR /tmp/pardes-snap/ttyedit/cwd Del
+|
+| words.txt words.txt
+|
+|
+| one t
+| wo three
+| four five six
+| seven eight nine
+|
+|
+|
+|
+|
+|
+| NOR /tmp/pardes-snap/ttyedit/cwd Del
+|
+| words.txt
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+== snap opened grid=100x30 cursor=6,8
+|Kill Newcol Tutor Debug Colors NextColor Crt Dump
+| NOR /tmp/pardes-snap/ttyedit/cwd Del NOR /tmp/pardes-snap/ttyedit/cwd Del
+|
+| words.txt words.txt
+|
+|
+| one t
+| wo three
+| OPEN
+| four five six
+| seven eight nine
+|
+|
+|
+|
+|
+| NOR /tmp/pardes-snap/ttyedit/cwd Del
+|
+| words.txt
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+== snap joined grid=100x30 cursor=10,7
+|Kill Newcol Tutor Debug Colors NextColor Crt Dump
+| NOR /tmp/pardes-snap/ttyedit/cwd Del NOR /tmp/pardes-snap/ttyedit/cwd Del
+|
+| words.txt words.txt
+|
+|
+| one t
+| wo threeOPEN
+| four five six
+| seven eight nine
+|
+|
+|
+|
+|
+|
+| NOR /tmp/pardes-snap/ttyedit/cwd Del
+|
+| words.txt
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+== snap undone grid=100x30 cursor=7,6
+|Kill Newcol Tutor Debug Colors NextColor Crt Dump
+| NOR /tmp/pardes-snap/ttyedit/cwd Del NOR /tmp/pardes-snap/ttyedit/cwd Del
+|
+| words.txt words.txt
+|
+|
+| one two three
+| four five six
+| seven eight nine
+|
+|
+|
+|
+|
+|
+|
+| NOR /tmp/pardes-snap/ttyedit/cwd Del
+|
+| words.txt
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
diff --git a/test/snapshots/ttyedit.snap b/test/snapshots/ttyedit.snap
new file mode 100644
index 00000000..18872ce4
--- /dev/null
+++ b/test/snapshots/ttyedit.snap
@@ -0,0 +1,45 @@
+# editing a shell pane exactly like a document: Enter SPLITS the line under
+# the cursor and pushes the shell rows below it down, `o` opens a fresh line,
+# and backspace at column 0 joins back up. Before the edit buffer landed a
+# terminal could only splice single-row insertion runs, so none of this was
+# expressible. test/hxcases/parity.jsonl pins the keystroke-level equivalence
+# with a file pane case for case; this is the end-to-end pty proof.
+file words.txt alpha beta
+start 30 100 -n 3
+wait 8000 words.txt
+stable 700 20000
+key c-b
+stable 600 8000
+text printf 'one two three\nfour five six\nseven eight nine\n'
+key enter
+wait 8000 seven
+stable 700 10000
+key c-b
+stable 600 8000
+# click into the middle of the first output row
+press left 8 7
+release left 8 7
+stable 400 5000
+snap before
+# Enter splits the row there: "one t" stays, "wo three" becomes a real line
+# of its own and the two output rows below it slide down
+key i enter
+stable 400 5000
+snap split
+key esc
+stable 400 5000
+# `o` opens a line below the split's tail and pushes the rest down again
+key o
+text OPEN
+key esc
+stable 400 5000
+snap opened
+# backspace at column 0 joins the opened line back onto its predecessor
+key i home bs
+key esc
+stable 400 5000
+snap joined
+# undo walks the whole session back out to untouched shell output
+key u u u
+stable 400 5000
+snap undone
diff --git a/test/snapshots/ttylook.golden b/test/snapshots/ttylook.golden
index 410c71f7..c36f8f92 100644
--- a/test/snapshots/ttylook.golden
+++ b/test/snapshots/ttylook.golden
@@ -122,7 +122,7 @@
|
|
|
-== snap insert grid=120x30 cursor=2,3
+== snap insert grid=120x30 cursor=4,3
|Kill Newcol Tutor Debug Colors NextColor Crt Dump
| NOR /tmp/pardes-snap/ttylook/cwd Del NOR /tmp/pardes-snap/ttylook/cwd Del
|