summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-07-07 09:48:05 -0300
committerGabriel Schneider <[email protected]>2026-08-01 15:02:07 -0300
commit9da20917b6f4cf98fd9b5c2c0a904e4f6a8380a9 (patch)
treec7c8c4d1126def565eb0b1d20ad29782eb49c5e7 /test
parentb2cdd8ee719961b2f037f20bf0f88fad94ca73b4 (diff)
downloadpardes-9da20917b6f4cf98fd9b5c2c0a904e4f6a8380a9.tar.gz
pardes-9da20917b6f4cf98fd9b5c2c0a904e4f6a8380a9.zip
helix diff testing and feature parity
Diffstat (limited to 'test')
-rw-r--r--test/hxcases/cases.jsonl360
-rw-r--r--test/hxcases/goldens.jsonl360
-rwxr-xr-xtest/hxcases/regen.sh16
-rw-r--r--test/hxcases/smoke.jsonl20
-rw-r--r--test/hxcases/waivers.jsonl2
-rw-r--r--test/hxdiff.zig355
-rw-r--r--test/snapshots/fileedit.golden2
-rw-r--r--test/snapshots/modal.golden36
-rw-r--r--test/snapshots/modal.snap2
-rw-r--r--test/snapshots/scrolloff.golden8
-rw-r--r--test/snapshots/scrolloff.snap5
11 files changed, 1140 insertions, 26 deletions
diff --git a/test/hxcases/cases.jsonl b/test/hxcases/cases.jsonl
new file mode 100644
index 00000000..386013bf
--- /dev/null
+++ b/test/hxcases/cases.jsonl
@@ -0,0 +1,360 @@
+{"name": "h-basic", "pane": "file", "text": "abc def\n", "keys": "llh"}
+{"name": "h-basic-tty", "pane": "tty", "text": "abc def\n", "keys": "llh"}
+{"name": "h-crosses-nl", "pane": "file", "text": "ab\ncd\n", "keys": "jh"}
+{"name": "h-crosses-nl-tty", "pane": "tty", "text": "ab\ncd\n", "keys": "jh"}
+{"name": "l-basic", "pane": "file", "text": "abc\n", "keys": "l"}
+{"name": "l-basic-tty", "pane": "tty", "text": "abc\n", "keys": "l"}
+{"name": "l-crosses-nl", "pane": "file", "text": "ab\ncd\n", "keys": "lll"}
+{"name": "l-crosses-nl-tty", "pane": "tty", "text": "ab\ncd\n", "keys": "lll"}
+{"name": "l-count", "pane": "file", "text": "abcdef\n", "keys": "3l"}
+{"name": "l-count-tty", "pane": "tty", "text": "abcdef\n", "keys": "3l"}
+{"name": "h-count", "pane": "file", "text": "abcdef\n", "keys": "5l3h"}
+{"name": "h-count-tty", "pane": "tty", "text": "abcdef\n", "keys": "5l3h"}
+{"name": "l-clamp-eof", "pane": "file", "text": "ab\n", "keys": "9l"}
+{"name": "j-basic", "pane": "file", "text": "aa\nbb\ncc\n", "keys": "j"}
+{"name": "j-basic-tty", "pane": "tty", "text": "aa\nbb\ncc\n", "keys": "j"}
+{"name": "j-onto-nl-cell", "pane": "file", "text": "abcdef\nab\nabcdef\n", "keys": "glj"}
+{"name": "j-onto-nl-cell-tty", "pane": "tty", "text": "abcdef\nab\nabcdef\n", "keys": "glj"}
+{"name": "j-sticky-col", "pane": "file", "text": "abcdef\nab\nabcdef\n", "keys": "gljj"}
+{"name": "j-sticky-col-tty", "pane": "tty", "text": "abcdef\nab\nabcdef\n", "keys": "gljj"}
+{"name": "j-phantom", "pane": "file", "text": "one\ntwo\n", "keys": "jj"}
+{"name": "k-basic", "pane": "file", "text": "aa\nbb\n", "keys": "jk"}
+{"name": "k-basic-tty", "pane": "tty", "text": "aa\nbb\n", "keys": "jk"}
+{"name": "k-sticky-col", "pane": "file", "text": "abcdef\nab\nabcdef\n", "keys": "2jglkk"}
+{"name": "k-sticky-col-tty", "pane": "tty", "text": "abcdef\nab\nabcdef\n", "keys": "2jglkk"}
+{"name": "j-count", "pane": "file", "text": "a\nb\nc\nd\n", "keys": "2j"}
+{"name": "j-count-tty", "pane": "tty", "text": "a\nb\nc\nd\n", "keys": "2j"}
+{"name": "arrow-right-down", "pane": "file", "text": "abc\ndef\n", "keys": "<right><down>"}
+{"name": "arrow-right-down-tty", "pane": "tty", "text": "abc\ndef\n", "keys": "<right><down>"}
+{"name": "arrow-left-up", "pane": "file", "text": "abc\ndef\n", "keys": "j<right><left><up>"}
+{"name": "arrow-left-up-tty", "pane": "tty", "text": "abc\ndef\n", "keys": "j<right><left><up>"}
+{"name": "w-basic", "pane": "file", "text": "alpha beta\n", "keys": "w"}
+{"name": "w-basic-tty", "pane": "tty", "text": "alpha beta\n", "keys": "w"}
+{"name": "w-punct", "pane": "file", "text": "foo.bar baz\n", "keys": "w"}
+{"name": "w-punct-tty", "pane": "tty", "text": "foo.bar baz\n", "keys": "w"}
+{"name": "w-punct-run", "pane": "file", "text": "foo.bar baz\n", "keys": "ww"}
+{"name": "w-punct-run-tty", "pane": "tty", "text": "foo.bar baz\n", "keys": "ww"}
+{"name": "w-count", "pane": "file", "text": "one two three four\n", "keys": "2w"}
+{"name": "w-count-tty", "pane": "tty", "text": "one two three four\n", "keys": "2w"}
+{"name": "w-eol-cross", "pane": "file", "text": "ab cd\nef\n", "keys": "www"}
+{"name": "w-eol-cross-tty", "pane": "tty", "text": "ab cd\nef\n", "keys": "www"}
+{"name": "w-from-empty-line", "pane": "file", "text": "\nabc\n", "keys": "w"}
+{"name": "w-skips-blank", "pane": "file", "text": "aa\n\nbb\n", "keys": "ww"}
+{"name": "w-skips-blank-tty", "pane": "tty", "text": "aa\n\nbb\n", "keys": "ww"}
+{"name": "w-eof-collapse", "pane": "file", "text": "alpha\n", "keys": "ww"}
+{"name": "b-basic", "pane": "file", "text": "alpha beta\n", "keys": "wwb"}
+{"name": "b-basic-tty", "pane": "tty", "text": "alpha beta\n", "keys": "wwb"}
+{"name": "b-bof-noop", "pane": "file", "text": "alpha\n", "keys": "b"}
+{"name": "b-bof-noop-tty", "pane": "tty", "text": "alpha\n", "keys": "b"}
+{"name": "b-from-nl-cell", "pane": "file", "text": "ab\ncd\n", "keys": "llb"}
+{"name": "b-from-nl-cell-tty", "pane": "tty", "text": "ab\ncd\n", "keys": "llb"}
+{"name": "b-count", "pane": "file", "text": "a bb ccc dddd\n", "keys": "gl2b"}
+{"name": "b-count-tty", "pane": "tty", "text": "a bb ccc dddd\n", "keys": "gl2b"}
+{"name": "e-basic", "pane": "file", "text": "alpha beta\n", "keys": "e"}
+{"name": "e-basic-tty", "pane": "tty", "text": "alpha beta\n", "keys": "e"}
+{"name": "e-from-mid", "pane": "file", "text": "alpha beta\n", "keys": "le"}
+{"name": "e-from-mid-tty", "pane": "tty", "text": "alpha beta\n", "keys": "le"}
+{"name": "e-count", "pane": "file", "text": "a bb ccc dddd\n", "keys": "2e"}
+{"name": "e-count-tty", "pane": "tty", "text": "a bb ccc dddd\n", "keys": "2e"}
+{"name": "e-cross-blank", "pane": "file", "text": "aa\n\nbb\n", "keys": "ee"}
+{"name": "e-cross-blank-tty", "pane": "tty", "text": "aa\n\nbb\n", "keys": "ee"}
+{"name": "W-basic", "pane": "file", "text": "foo.bar baz\n", "keys": "W"}
+{"name": "W-basic-tty", "pane": "tty", "text": "foo.bar baz\n", "keys": "W"}
+{"name": "B-basic", "pane": "file", "text": "foo.bar baz\n", "keys": "WB"}
+{"name": "B-basic-tty", "pane": "tty", "text": "foo.bar baz\n", "keys": "WB"}
+{"name": "E-basic", "pane": "file", "text": "foo.bar baz\n", "keys": "E"}
+{"name": "E-basic-tty", "pane": "tty", "text": "foo.bar baz\n", "keys": "E"}
+{"name": "f-basic", "pane": "file", "text": "abcabc\n", "keys": "fc"}
+{"name": "f-basic-tty", "pane": "tty", "text": "abcabc\n", "keys": "fc"}
+{"name": "f-count", "pane": "file", "text": "abcabc\n", "keys": "2fc"}
+{"name": "f-count-tty", "pane": "tty", "text": "abcabc\n", "keys": "2fc"}
+{"name": "f-cross-line", "pane": "file", "text": "one\ntwo\n", "keys": "fw"}
+{"name": "f-cross-line-tty", "pane": "tty", "text": "one\ntwo\n", "keys": "fw"}
+{"name": "f-notfound-noop", "pane": "file", "text": "abc\n", "keys": "fz"}
+{"name": "f-notfound-noop-tty", "pane": "tty", "text": "abc\n", "keys": "fz"}
+{"name": "f-digit-arg", "pane": "file", "text": "a4b\n", "keys": "f4"}
+{"name": "f-digit-arg-tty", "pane": "tty", "text": "a4b\n", "keys": "f4"}
+{"name": "f-repeat-same-target", "pane": "file", "text": "abcabc\n", "keys": "fafa"}
+{"name": "t-basic", "pane": "file", "text": "abcabc\n", "keys": "tc"}
+{"name": "t-basic-tty", "pane": "tty", "text": "abcabc\n", "keys": "tc"}
+{"name": "t-twice-skips-edge", "pane": "file", "text": "abcabc\n", "keys": "tctc"}
+{"name": "t-twice-skips-edge-tty", "pane": "tty", "text": "abcabc\n", "keys": "tctc"}
+{"name": "F-backward", "pane": "file", "text": "abcabc\n", "keys": "glFa"}
+{"name": "F-backward-tty", "pane": "tty", "text": "abcabc\n", "keys": "glFa"}
+{"name": "T-backward", "pane": "file", "text": "abcabc\n", "keys": "glTa"}
+{"name": "T-backward-tty", "pane": "tty", "text": "abcabc\n", "keys": "glTa"}
+{"name": "alt-dot-repeat-f", "pane": "file", "text": "abcabc\n", "keys": "fc<A-.>"}
+{"name": "alt-dot-repeat-f-tty", "pane": "tty", "text": "abcabc\n", "keys": "fc<A-.>"}
+{"name": "alt-dot-repeat-t", "pane": "file", "text": "abcabc\n", "keys": "tc<A-.>"}
+{"name": "alt-dot-repeat-t-tty", "pane": "tty", "text": "abcabc\n", "keys": "tc<A-.>"}
+{"name": "alt-dot-count", "pane": "file", "text": "axbxcxd\n", "keys": "fx2<A-.>"}
+{"name": "alt-dot-count-tty", "pane": "tty", "text": "axbxcxd\n", "keys": "fx2<A-.>"}
+{"name": "G-count", "pane": "file", "text": "l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\n", "keys": "5G"}
+{"name": "G-count-tty", "pane": "tty", "text": "l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\n", "keys": "5G"}
+{"name": "G-count-clamp", "pane": "file", "text": "one\ntwo\n", "keys": "99G"}
+{"name": "G-count-clamp-tty", "pane": "tty", "text": "one\ntwo\n", "keys": "99G"}
+{"name": "G-bare-noop", "pane": "file", "text": "aa\nbb\ncc\n", "keys": "jG"}
+{"name": "G-bare-noop-tty", "pane": "tty", "text": "aa\nbb\ncc\n", "keys": "jG"}
+{"name": "count-multidigit", "pane": "file", "text": "l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\n", "keys": "10j"}
+{"name": "count-multidigit-tty", "pane": "tty", "text": "l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\n", "keys": "10j"}
+{"name": "G-count-resets-col", "pane": "file", "text": "l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\n", "keys": "3l5G"}
+{"name": "G-count-resets-col-tty", "pane": "tty", "text": "l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\n", "keys": "3l5G"}
+{"name": "gg-basic", "pane": "file", "text": "aa\nbb\ncc\n", "keys": "jjgg"}
+{"name": "gg-basic-tty", "pane": "tty", "text": "aa\nbb\ncc\n", "keys": "jjgg"}
+{"name": "gg-count", "pane": "file", "text": "l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\n", "keys": "3gg"}
+{"name": "gg-count-tty", "pane": "tty", "text": "l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\n", "keys": "3gg"}
+{"name": "ge-basic", "pane": "file", "text": "aa\nbb\ncc\n", "keys": "ge"}
+{"name": "ge-basic-tty", "pane": "tty", "text": "aa\nbb\ncc\n", "keys": "ge"}
+{"name": "ge-col0", "pane": "file", "text": "alpha beta\n", "keys": "wwge"}
+{"name": "ge-col0-tty", "pane": "tty", "text": "alpha beta\n", "keys": "wwge"}
+{"name": "gh-basic", "pane": "file", "text": "abc\n", "keys": "llgh"}
+{"name": "gh-basic-tty", "pane": "tty", "text": "abc\n", "keys": "llgh"}
+{"name": "gl-basic", "pane": "file", "text": "abc def\n", "keys": "gl"}
+{"name": "gl-basic-tty", "pane": "tty", "text": "abc def\n", "keys": "gl"}
+{"name": "gl-empty-line", "pane": "file", "text": "\nabc\n", "keys": "gl"}
+{"name": "gl-empty-line-tty", "pane": "tty", "text": "\nabc\n", "keys": "gl"}
+{"name": "gs-basic", "pane": "file", "text": " abc\n", "keys": "llgs"}
+{"name": "gs-basic-tty", "pane": "tty", "text": " abc\n", "keys": "llgs"}
+{"name": "gs-allws-noop", "pane": "file", "text": " \nx\n", "keys": "gs"}
+{"name": "gs-allws-noop-tty", "pane": "tty", "text": " \nx\n", "keys": "gs"}
+{"name": "gbar-count", "pane": "file", "text": "abcdef\n", "keys": "4g|"}
+{"name": "gbar-count-tty", "pane": "tty", "text": "abcdef\n", "keys": "4g|"}
+{"name": "gbar-clamp", "pane": "file", "text": "ab\n", "keys": "9g|"}
+{"name": "gbar-clamp-tty", "pane": "tty", "text": "ab\n", "keys": "9g|"}
+{"name": "gbar-nocount", "pane": "file", "text": "abcdef\n", "keys": "llg|"}
+{"name": "gbar-nocount-tty", "pane": "tty", "text": "abcdef\n", "keys": "llg|"}
+{"name": "gj-basic", "pane": "file", "text": "abcdef\nab\nabcdef\n", "keys": "glgjgj"}
+{"name": "gj-basic-tty", "pane": "tty", "text": "abcdef\nab\nabcdef\n", "keys": "glgjgj"}
+{"name": "gk-basic", "pane": "file", "text": "aa\nbb\ncc\n", "keys": "jjgk"}
+{"name": "gk-basic-tty", "pane": "tty", "text": "aa\nbb\ncc\n", "keys": "jjgk"}
+{"name": "home-basic", "pane": "file", "text": "abc def\n", "keys": "w<home>"}
+{"name": "home-basic-tty", "pane": "tty", "text": "abc def\n", "keys": "w<home>"}
+{"name": "end-basic", "pane": "file", "text": "abc def\n", "keys": "<end>"}
+{"name": "end-basic-tty", "pane": "tty", "text": "abc def\n", "keys": "<end>"}
+{"name": "end-empty-line", "pane": "file", "text": "\nabc\n", "keys": "<end>"}
+{"name": "end-empty-line-tty", "pane": "tty", "text": "\nabc\n", "keys": "<end>"}
+{"name": "gt-noscroll", "pane": "file", "text": "l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\n", "keys": "5jgt"}
+{"name": "gt-noscroll-tty", "pane": "tty", "text": "l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\n", "keys": "5jgt"}
+{"name": "gc-noscroll", "pane": "file", "text": "l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\n", "keys": "gc"}
+{"name": "gb-noscroll", "pane": "file", "text": "l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\n", "keys": "5jgb"}
+{"name": "gt-after-zz", "pane": "file", "text": "l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\nl12\nl13\nl14\nl15\nl16\nl17\nl18\nl19\nl20\nl21\nl22\nl23\nl24\nl25\nl26\nl27\nl28\nl29\n", "keys": "15jzzgt"}
+{"name": "gc-after-zz", "pane": "file", "text": "l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\nl12\nl13\nl14\nl15\nl16\nl17\nl18\nl19\nl20\nl21\nl22\nl23\nl24\nl25\nl26\nl27\nl28\nl29\n", "keys": "15jzzgc"}
+{"name": "gb-after-zz", "pane": "file", "text": "l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\nl12\nl13\nl14\nl15\nl16\nl17\nl18\nl19\nl20\nl21\nl22\nl23\nl24\nl25\nl26\nl27\nl28\nl29\n", "keys": "15jzzgb"}
+{"name": "gt-count", "pane": "file", "text": "l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\nl12\nl13\nl14\nl15\nl16\nl17\nl18\nl19\nl20\nl21\nl22\nl23\nl24\nl25\nl26\nl27\nl28\nl29\n", "keys": "15jzz2gt"}
+{"name": "ctrl-d-basic", "pane": "file", "text": "l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\nl12\nl13\nl14\nl15\nl16\nl17\nl18\nl19\nl20\nl21\nl22\nl23\nl24\nl25\nl26\nl27\nl28\nl29\n", "keys": "<C-d>"}
+{"name": "ctrl-d-small", "pane": "file", "text": "l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\n", "keys": "<C-d>"}
+{"name": "ctrl-d-sticky", "pane": "file", "text": "l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\nl12\nl13\nl14\nl15\nl16\nl17\nl18\nl19\nl20\nl21\nl22\nl23\nl24\nl25\nl26\nl27\nl28\nl29\n", "keys": "5l<C-d>"}
+{"name": "ctrl-u-back", "pane": "file", "text": "l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\nl12\nl13\nl14\nl15\nl16\nl17\nl18\nl19\nl20\nl21\nl22\nl23\nl24\nl25\nl26\nl27\nl28\nl29\n", "keys": "20j<C-u>"}
+{"name": "ctrl-f-page", "pane": "file", "text": "l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\nl12\nl13\nl14\nl15\nl16\nl17\nl18\nl19\nl20\nl21\nl22\nl23\nl24\nl25\nl26\nl27\nl28\nl29\n", "keys": "<C-f>"}
+{"name": "ctrl-f-small", "pane": "file", "text": "l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\n", "keys": "<C-f>"}
+{"name": "ctrl-b-page", "pane": "file", "text": "l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\nl12\nl13\nl14\nl15\nl16\nl17\nl18\nl19\nl20\nl21\nl22\nl23\nl24\nl25\nl26\nl27\nl28\nl29\n", "keys": "20j<C-b>"}
+{"name": "pgdn-basic", "pane": "file", "text": "l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\nl12\nl13\nl14\nl15\nl16\nl17\nl18\nl19\nl20\nl21\nl22\nl23\nl24\nl25\nl26\nl27\nl28\nl29\n", "keys": "<pagedown>"}
+{"name": "pgup-basic", "pane": "file", "text": "l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\nl12\nl13\nl14\nl15\nl16\nl17\nl18\nl19\nl20\nl21\nl22\nl23\nl24\nl25\nl26\nl27\nl28\nl29\n", "keys": "25j<pageup>"}
+{"name": "pgup-noop-in-view", "pane": "file", "text": "l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\n", "keys": "5j<pageup>"}
+{"name": "pgup-noop-in-view-tty", "pane": "tty", "text": "l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\n", "keys": "5j<pageup>"}
+{"name": "v-ctrl-d-extends", "pane": "file", "text": "l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\nl12\nl13\nl14\nl15\nl16\nl17\nl18\nl19\nl20\nl21\nl22\nl23\nl24\nl25\nl26\nl27\nl28\nl29\n", "keys": "v<C-d>"}
+{"name": "zt-then-gt", "pane": "file", "text": "l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\nl12\nl13\nl14\nl15\nl16\nl17\nl18\nl19\nl20\nl21\nl22\nl23\nl24\nl25\nl26\nl27\nl28\nl29\n", "keys": "12jztgt"}
+{"name": "zz-then-gt", "pane": "file", "text": "l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\nl12\nl13\nl14\nl15\nl16\nl17\nl18\nl19\nl20\nl21\nl22\nl23\nl24\nl25\nl26\nl27\nl28\nl29\n", "keys": "15jzzgt"}
+{"name": "zb-then-gt", "pane": "file", "text": "l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\nl12\nl13\nl14\nl15\nl16\nl17\nl18\nl19\nl20\nl21\nl22\nl23\nl24\nl25\nl26\nl27\nl28\nl29\n", "keys": "12jzbgt"}
+{"name": "zc-alias-zz", "pane": "file", "text": "l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\nl12\nl13\nl14\nl15\nl16\nl17\nl18\nl19\nl20\nl21\nl22\nl23\nl24\nl25\nl26\nl27\nl28\nl29\n", "keys": "15jzcgt"}
+{"name": "zj-snaps-cursor", "pane": "file", "text": "l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\nl12\nl13\nl14\nl15\nl16\nl17\nl18\nl19\nl20\nl21\nl22\nl23\nl24\nl25\nl26\nl27\nl28\nl29\n", "keys": "zj"}
+{"name": "zj-noop-in-band", "pane": "file", "text": "l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\nl12\nl13\nl14\nl15\nl16\nl17\nl18\nl19\nl20\nl21\nl22\nl23\nl24\nl25\nl26\nl27\nl28\nl29\n", "keys": "10jzj"}
+{"name": "zk-bottom-snap", "pane": "file", "text": "l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\nl12\nl13\nl14\nl15\nl16\nl17\nl18\nl19\nl20\nl21\nl22\nl23\nl24\nl25\nl26\nl27\nl28\nl29\n", "keys": "20jzk"}
+{"name": "z-ctrl-d-alias", "pane": "file", "text": "l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\nl12\nl13\nl14\nl15\nl16\nl17\nl18\nl19\nl20\nl21\nl22\nl23\nl24\nl25\nl26\nl27\nl28\nl29\n", "keys": "z<C-d>"}
+{"name": "z-pgdn-alias", "pane": "file", "text": "l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\nl12\nl13\nl14\nl15\nl16\nl17\nl18\nl19\nl20\nl21\nl22\nl23\nl24\nl25\nl26\nl27\nl28\nl29\n", "keys": "z<pagedown>"}
+{"name": "v-reports-select", "pane": "file", "text": "alpha beta\n", "keys": "v"}
+{"name": "v-reports-select-tty", "pane": "tty", "text": "alpha beta\n", "keys": "v"}
+{"name": "v-extend-w", "pane": "file", "text": "alpha beta gamma\n", "keys": "vww"}
+{"name": "v-extend-w-tty", "pane": "tty", "text": "alpha beta gamma\n", "keys": "vww"}
+{"name": "v-anchor-fixed-crossing", "pane": "file", "text": "abcdef\n", "keys": "llvhhh"}
+{"name": "v-anchor-fixed-crossing-tty", "pane": "tty", "text": "abcdef\n", "keys": "llvhhh"}
+{"name": "v-exit-keeps-sel", "pane": "file", "text": "alpha beta\n", "keys": "vwv"}
+{"name": "v-exit-keeps-sel-tty", "pane": "tty", "text": "alpha beta\n", "keys": "vwv"}
+{"name": "v-esc-keeps-sel", "pane": "file", "text": "alpha beta\n", "keys": "vw<esc>"}
+{"name": "v-esc-keeps-sel-tty", "pane": "tty", "text": "alpha beta\n", "keys": "vw<esc>"}
+{"name": "v-gl-extends", "pane": "file", "text": "abc def\n", "keys": "vgl"}
+{"name": "v-gl-extends-tty", "pane": "tty", "text": "abc def\n", "keys": "vgl"}
+{"name": "v-gg-extends", "pane": "file", "text": "aa\nbb\ncc\n", "keys": "jjvgg"}
+{"name": "v-gg-extends-tty", "pane": "tty", "text": "aa\nbb\ncc\n", "keys": "jjvgg"}
+{"name": "v-find-extends", "pane": "file", "text": "abcabc\n", "keys": "vfcfc"}
+{"name": "v-find-extends-tty", "pane": "tty", "text": "abcabc\n", "keys": "vfcfc"}
+{"name": "v-x-extends-line", "pane": "file", "text": "ab\ncd\nef\n", "keys": "vjx"}
+{"name": "v-x-extends-line-tty", "pane": "tty", "text": "ab\ncd\nef\n", "keys": "vjx"}
+{"name": "v-j-phantom-blocked", "pane": "file", "text": "one\ntwo\n", "keys": "vjj"}
+{"name": "w-esc-keeps-sel", "pane": "file", "text": "alpha beta\n", "keys": "w<esc>"}
+{"name": "w-esc-keeps-sel-tty", "pane": "tty", "text": "alpha beta\n", "keys": "w<esc>"}
+{"name": "semi-collapse", "pane": "file", "text": "alpha beta\n", "keys": "w;"}
+{"name": "semi-collapse-tty", "pane": "tty", "text": "alpha beta\n", "keys": "w;"}
+{"name": "altsemi-flip", "pane": "file", "text": "alpha beta\n", "keys": "w<A-;>"}
+{"name": "altsemi-flip-tty", "pane": "tty", "text": "alpha beta\n", "keys": "w<A-;>"}
+{"name": "altsemi-flip-x", "pane": "file", "text": "one\ntwo\ncc\n", "keys": "x<A-;>"}
+{"name": "altsemi-flip-x-tty", "pane": "tty", "text": "one\ntwo\ncc\n", "keys": "x<A-;>"}
+{"name": "percent-select-all", "pane": "file", "text": "one\ntwo\n", "keys": "%"}
+{"name": "percent-then-d", "pane": "file", "text": "one\ntwo\n", "keys": "%d"}
+{"name": "x-selects-line", "pane": "file", "text": "one\ntwo\nthree\n", "keys": "x"}
+{"name": "x-selects-line-tty", "pane": "tty", "text": "one\ntwo\nthree\n", "keys": "x"}
+{"name": "x-extends", "pane": "file", "text": "one\ntwo\nthree\n", "keys": "xx"}
+{"name": "x-extends-tty", "pane": "tty", "text": "one\ntwo\nthree\n", "keys": "xx"}
+{"name": "x-count", "pane": "file", "text": "a\nb\nc\nd\n", "keys": "2x"}
+{"name": "x-count-tty", "pane": "tty", "text": "a\nb\nc\nd\n", "keys": "2x"}
+{"name": "x-empty-line", "pane": "file", "text": "a\n\nb\nc\n", "keys": "jx"}
+{"name": "x-empty-line-tty", "pane": "tty", "text": "a\n\nb\nc\n", "keys": "jx"}
+{"name": "x-last-line", "pane": "file", "text": "one\ntwo\n", "keys": "jx"}
+{"name": "x-then-w-drops", "pane": "file", "text": "aa\nbb cc\n", "keys": "xw"}
+{"name": "x-then-w-drops-tty", "pane": "tty", "text": "aa\nbb cc\n", "keys": "xw"}
+{"name": "x-then-d", "pane": "file", "text": "one\ntwo\nthree\n", "keys": "xd"}
+{"name": "X-bare", "pane": "file", "text": "abc\ndef\n", "keys": "lX"}
+{"name": "X-bare-tty", "pane": "tty", "text": "abc\ndef\n", "keys": "lX"}
+{"name": "X-from-v", "pane": "file", "text": "abc\ndef\nghi\n", "keys": "lvjX"}
+{"name": "X-from-v-tty", "pane": "tty", "text": "abc\ndef\nghi\n", "keys": "lvjX"}
+{"name": "X-keeps-direction", "pane": "file", "text": "abc\ndef\nghi\n", "keys": "jlvkX"}
+{"name": "X-keeps-direction-tty", "pane": "tty", "text": "abc\ndef\nghi\n", "keys": "jlvkX"}
+{"name": "X-noop-when-full", "pane": "file", "text": "abc\ndef\n", "keys": "XX"}
+{"name": "altx-shrink", "pane": "file", "text": "abc\ndef\nghi\n", "keys": "lv2j<A-x>"}
+{"name": "altx-shrink-tty", "pane": "tty", "text": "abc\ndef\nghi\n", "keys": "lv2j<A-x>"}
+{"name": "altx-single-line-noop", "pane": "file", "text": "abcdef\n", "keys": "lvll<A-x>"}
+{"name": "altx-single-line-noop-tty", "pane": "tty", "text": "abcdef\n", "keys": "lvll<A-x>"}
+{"name": "mm-basic", "pane": "file", "text": "a(bc(d)e)f\n", "keys": "lmm"}
+{"name": "mm-basic-tty", "pane": "tty", "text": "a(bc(d)e)f\n", "keys": "lmm"}
+{"name": "mm-back", "pane": "file", "text": "a(bc(d)e)f\n", "keys": "8lmm"}
+{"name": "mm-back-tty", "pane": "tty", "text": "a(bc(d)e)f\n", "keys": "8lmm"}
+{"name": "mm-nonbracket-noop", "pane": "file", "text": "abc\n", "keys": "mm"}
+{"name": "mm-nonbracket-noop-tty", "pane": "tty", "text": "abc\n", "keys": "mm"}
+{"name": "mi-paren", "pane": "file", "text": "fn(a, b)\n", "keys": "fami("}
+{"name": "mi-paren-tty", "pane": "tty", "text": "fn(a, b)\n", "keys": "fami("}
+{"name": "ma-paren", "pane": "file", "text": "fn(a, b)\n", "keys": "fama("}
+{"name": "ma-paren-tty", "pane": "tty", "text": "fn(a, b)\n", "keys": "fama("}
+{"name": "mi-nested", "pane": "file", "text": "(a(b)c)\n", "keys": "3lmi("}
+{"name": "mi-nested-tty", "pane": "tty", "text": "(a(b)c)\n", "keys": "3lmi("}
+{"name": "mi-word", "pane": "file", "text": "alpha beta\n", "keys": "wwmiw"}
+{"name": "mi-word-tty", "pane": "tty", "text": "alpha beta\n", "keys": "wwmiw"}
+{"name": "mi-WORD", "pane": "file", "text": "a.b c.d\n", "keys": "miW"}
+{"name": "mi-WORD-tty", "pane": "tty", "text": "a.b c.d\n", "keys": "miW"}
+{"name": "ma-word", "pane": "file", "text": "alpha beta gamma\n", "keys": "wwmaw"}
+{"name": "mi-quote", "pane": "file", "text": "say \"hi there\" ok\n", "keys": "6lmi\""}
+{"name": "ma-quote", "pane": "file", "text": "say \"hi there\" ok\n", "keys": "6lma\""}
+{"name": "mi-para", "pane": "file", "text": "aa\nbb\n\ncc\n", "keys": "mip"}
+{"name": "mi-para-tty", "pane": "tty", "text": "aa\nbb\n\ncc\n", "keys": "mip"}
+{"name": "ma-para", "pane": "file", "text": "aa\nbb\n\ncc\n", "keys": "map"}
+{"name": "mi-para-blank", "pane": "file", "text": "aa\n\ncc\n", "keys": "jmip"}
+{"name": "mi-empty-pair", "pane": "file", "text": "a()b\n", "keys": "lmi("}
+{"name": "par-fwd", "pane": "file", "text": "aa\nbb\n\ncc\ndd\n\nee\n", "keys": "]p"}
+{"name": "par-fwd-tty", "pane": "tty", "text": "aa\nbb\n\ncc\ndd\n\nee\n", "keys": "]p"}
+{"name": "par-fwd-count", "pane": "file", "text": "aa\nbb\n\ncc\ndd\n\nee\n", "keys": "2]p"}
+{"name": "par-fwd-count-tty", "pane": "tty", "text": "aa\nbb\n\ncc\ndd\n\nee\n", "keys": "2]p"}
+{"name": "par-bwd", "pane": "file", "text": "aa\nbb\n\ncc\ndd\n\nee\n", "keys": "6j[p"}
+{"name": "par-bwd-tty", "pane": "tty", "text": "aa\nbb\n\ncc\ndd\n\nee\n", "keys": "6j[p"}
+{"name": "par-fwd-mid", "pane": "file", "text": "aa\nbb\n\ncc\ndd\n\nee\n", "keys": "j]p"}
+{"name": "par-fwd-mid-tty", "pane": "tty", "text": "aa\nbb\n\ncc\ndd\n\nee\n", "keys": "j]p"}
+{"name": "par-bwd-mid", "pane": "file", "text": "aa\nbb\n\ncc\ndd\n\nee\n", "keys": "4j[p"}
+{"name": "par-bwd-mid-tty", "pane": "tty", "text": "aa\nbb\n\ncc\ndd\n\nee\n", "keys": "4j[p"}
+{"name": "par-fwd-at-end", "pane": "file", "text": "aa\n\nbb\n", "keys": "2j]p"}
+{"name": "add-nl-below", "pane": "file", "text": "aa\nbb\n", "keys": "]<space>"}
+{"name": "add-nl-above", "pane": "file", "text": "aa\nbb\n", "keys": "j[<space>"}
+{"name": "add-nl-count", "pane": "file", "text": "aa\nbb\n", "keys": "2]<space>"}
+{"name": "d-word", "pane": "file", "text": "alpha beta\n", "keys": "wd"}
+{"name": "d-bare-char", "pane": "file", "text": "abc\n", "keys": "d"}
+{"name": "d-on-nl-joins", "pane": "file", "text": "ab\ncd\n", "keys": "lld"}
+{"name": "d-x-lines", "pane": "file", "text": "aaa\nbbb\nccc\n", "keys": "xd"}
+{"name": "d-percent-all", "pane": "file", "text": "one\ntwo\n", "keys": "%d"}
+{"name": "alt-d-noyank", "pane": "file", "text": "alpha beta\n", "keys": "w<A-d>"}
+{"name": "alt-c-window-op", "pane": "file", "text": "abc def\n", "keys": "<A-c>"}
+{"name": "d-then-p", "pane": "file", "text": "alpha beta\n", "keys": "wdp"}
+{"name": "c-word", "pane": "file", "text": "alpha beta gamma\n", "keys": "wcX<esc>"}
+{"name": "c-bare", "pane": "file", "text": "abc\n", "keys": "cZ<esc>"}
+{"name": "c-linewise", "pane": "file", "text": "aaa\nbbb\n", "keys": "xcX<esc>"}
+{"name": "c-linewise-indent", "pane": "file", "text": " aa\nbb\n", "keys": "xcZ<esc>"}
+{"name": "y-keeps-sel", "pane": "file", "text": "alpha beta\n", "keys": "wy"}
+{"name": "y-keeps-sel-tty", "pane": "tty", "text": "alpha beta\n", "keys": "wy"}
+{"name": "y-bare-char", "pane": "file", "text": "abc def\n", "keys": "yp"}
+{"name": "y-bare-eol", "pane": "file", "text": "ab\ncd\n", "keys": "glyp"}
+{"name": "yp-word", "pane": "file", "text": "alpha beta\n", "keys": "weyp"}
+{"name": "xyp-linewise", "pane": "file", "text": "one\ntwo\n", "keys": "xyp"}
+{"name": "xyp-below-end-line", "pane": "file", "text": "one\ntwo\n", "keys": "xyjp"}
+{"name": "xyP-linewise", "pane": "file", "text": "one\ntwo\n", "keys": "xyP"}
+{"name": "vlyp-inline", "pane": "file", "text": "abcd\n", "keys": "vlyp"}
+{"name": "vlyP-inline", "pane": "file", "text": "abcd\n", "keys": "vlyP"}
+{"name": "p-count", "pane": "file", "text": "ab\n", "keys": "vy2p"}
+{"name": "p-after-sel-end", "pane": "file", "text": "abcd\n", "keys": "vlyllp"}
+{"name": "wP-before", "pane": "file", "text": "one two\n", "keys": "wyggwP"}
+{"name": "u-restores-sel", "pane": "file", "text": "alpha beta\n", "keys": "wdu"}
+{"name": "u-insert-session", "pane": "file", "text": "abc\n", "keys": "ixyz<esc>u"}
+{"name": "U-redo", "pane": "file", "text": "alpha beta\n", "keys": "wduU"}
+{"name": "u-linewise", "pane": "file", "text": "aaa\nbbb\n", "keys": "xdu"}
+{"name": "r-bare", "pane": "file", "text": "abc\n", "keys": "rz"}
+{"name": "r-sel-keeps", "pane": "file", "text": "abcdef\n", "keys": "vllrz"}
+{"name": "r-x-replaces-nl", "pane": "file", "text": "ab\ncd\n", "keys": "xrz"}
+{"name": "r-cancel-esc", "pane": "file", "text": "abc\n", "keys": "r<esc>l"}
+{"name": "R-inline", "pane": "file", "text": "alpha beta\n", "keys": "wyggR"}
+{"name": "R-linewise", "pane": "file", "text": "one\ntwo\n", "keys": "xyjR"}
+{"name": "tilde-sel-keeps", "pane": "file", "text": "abc\n", "keys": "vll~"}
+{"name": "tilde-bare", "pane": "file", "text": "aBc\n", "keys": "~"}
+{"name": "backtick-lower", "pane": "file", "text": "ABC\n", "keys": "vll`"}
+{"name": "alt-backtick-upper", "pane": "file", "text": "abc\n", "keys": "vll<A-`>"}
+{"name": "J-cursor-stays", "pane": "file", "text": "aa\nbb\ncc\n", "keys": "J"}
+{"name": "J-sel-lines", "pane": "file", "text": "aa\nbb\ncc\ndd\n", "keys": "xxJ"}
+{"name": "J-ws-collapse", "pane": "file", "text": "aa\n bb\n", "keys": "J"}
+{"name": "J-empty-next-nospace", "pane": "file", "text": "aa\n\nbb\n", "keys": "J"}
+{"name": "J-last-line-eats-nl", "pane": "file", "text": "aa\nbb\n", "keys": "jJ"}
+{"name": "J-count-ignored", "pane": "file", "text": "a\nb\nc\nd\n", "keys": "3J"}
+{"name": "indent-basic", "pane": "file", "text": "foo\nbar\n", "keys": "<gt>"}
+{"name": "indent-realign", "pane": "file", "text": " x\n", "keys": "gs<gt>"}
+{"name": "indent-cursor-maps", "pane": "file", "text": "foo\n", "keys": "ll<gt>"}
+{"name": "indent-x-sel", "pane": "file", "text": "a\n\nb\n", "keys": "x2x<gt>"}
+{"name": "indent-count", "pane": "file", "text": "x\n", "keys": "2<gt>"}
+{"name": "unindent-basic", "pane": "file", "text": " x\n", "keys": "gs<lt>"}
+{"name": "unindent-tab", "pane": "file", "text": "\tx\n", "keys": "<lt>"}
+{"name": "unindent-partial", "pane": "file", "text": " x\n", "keys": "<lt>"}
+{"name": "unindent-cursor", "pane": "file", "text": " ab\n", "keys": "gl<lt>"}
+{"name": "ctrl-a-sel", "pane": "file", "text": "15 x\n", "keys": "vl<C-a>"}
+{"name": "ctrl-a-bare-digit", "pane": "file", "text": "7\n", "keys": "<C-a>"}
+{"name": "ctrl-a-noop-nonint", "pane": "file", "text": "a 15 b\n", "keys": "f1<C-a>"}
+{"name": "ctrl-a-neg-sel", "pane": "file", "text": "-3\n", "keys": "vl<C-a>"}
+{"name": "ctrl-a-pad", "pane": "file", "text": "007\n", "keys": "vll<C-a>"}
+{"name": "ctrl-a-count", "pane": "file", "text": "5\n", "keys": "3<C-a>"}
+{"name": "ctrl-x-basic", "pane": "file", "text": "9\n", "keys": "<C-x>"}
+{"name": "ms-sel", "pane": "file", "text": "hello\n", "keys": "vllms("}
+{"name": "ms-bare", "pane": "file", "text": "ab\n", "keys": "ms("}
+{"name": "ms-quote-self", "pane": "file", "text": "ab cd\n", "keys": "wems'"}
+{"name": "md-basic", "pane": "file", "text": "a(bcd)e\n", "keys": "3lmd("}
+{"name": "md-quote", "pane": "file", "text": "a 'bc' d\n", "keys": "3lmd'"}
+{"name": "mr-basic", "pane": "file", "text": "a(bcd)e\n", "keys": "3lmr(["}
+{"name": "mr-to-quote", "pane": "file", "text": "a 'bc' d\n", "keys": "3lmr'\""}
+{"name": "o-basic", "pane": "file", "text": "ab\n", "keys": "ox<esc>"}
+{"name": "o-copies-indent-level", "pane": "file", "text": " foo\n", "keys": "o"}
+{"name": "o-partial-indent-drops", "pane": "file", "text": " foo\n", "keys": "o"}
+{"name": "o-count", "pane": "file", "text": "ab\n", "keys": "2o<esc>"}
+{"name": "O-basic", "pane": "file", "text": "aa\nbb\n", "keys": "jOx<esc>"}
+{"name": "O-copies-indent", "pane": "file", "text": " foo\n", "keys": "O"}
+{"name": "i-type-esc", "pane": "file", "text": "alpha\n", "keys": "ihello <esc>"}
+{"name": "i-keeps-sel-start", "pane": "file", "text": "alpha beta\n", "keys": "wi"}
+{"name": "wa-sel-end", "pane": "file", "text": "alpha beta\n", "keys": "wa"}
+{"name": "wiX-edit-drops-sel", "pane": "file", "text": "alpha beta\n", "keys": "wiX<esc>"}
+{"name": "a-esc-restore", "pane": "file", "text": "abc\n", "keys": "a<esc>"}
+{"name": "ax-restore", "pane": "file", "text": "abc\n", "keys": "ax<esc>"}
+{"name": "lax-restore", "pane": "file", "text": "abc\n", "keys": "lax<esc>"}
+{"name": "A-eol", "pane": "file", "text": "abc\ndef\n", "keys": "A<esc>"}
+{"name": "A-type", "pane": "file", "text": "one two\n", "keys": "AX<esc>"}
+{"name": "I-firstnonws", "pane": "file", "text": " abc\n", "keys": "llI"}
+{"name": "esc-pending-cancel", "pane": "file", "text": "abc\n", "keys": "f<esc>l"}
+{"name": "ins-enter-mid", "pane": "file", "text": "ab\n", "keys": "a<ret><esc>"}
+{"name": "ins-enter-indent", "pane": "file", "text": " foo\n", "keys": "A<ret>x<esc>"}
+{"name": "ins-backspace", "pane": "file", "text": "abc\n", "keys": "lli<backspace><esc>"}
+{"name": "ins-backspace-join", "pane": "file", "text": "ab\ncd\n", "keys": "ji<backspace><esc>"}
+{"name": "ins-ctrl-w", "pane": "file", "text": "one two\n", "keys": "A<C-w><esc>"}
+{"name": "ins-ctrl-w-col0-join", "pane": "file", "text": "ab\ncd\n", "keys": "ji<C-w><esc>"}
+{"name": "ins-alt-bs", "pane": "file", "text": "one two\n", "keys": "A<A-backspace><esc>"}
+{"name": "ins-alt-d", "pane": "file", "text": "one two\n", "keys": "i<A-d><esc>"}
+{"name": "ins-alt-del", "pane": "file", "text": "one two\n", "keys": "i<A-del><esc>"}
+{"name": "ins-ctrl-u", "pane": "file", "text": "one two\n", "keys": "A<C-u><esc>"}
+{"name": "ins-ctrl-k", "pane": "file", "text": "one two\n", "keys": "wi<C-k><esc>"}
+{"name": "ins-ctrl-h", "pane": "file", "text": "abc\n", "keys": "lli<C-h><esc>"}
+{"name": "ins-ctrl-d", "pane": "file", "text": "abc\n", "keys": "i<C-d><esc>"}
+{"name": "ins-del-eol-join", "pane": "file", "text": "ab\ncd\n", "keys": "gla<del><esc>"}
+{"name": "ins-ctrl-j", "pane": "file", "text": "ab\n", "keys": "a<C-j><esc>"}
+{"name": "ins-home", "pane": "file", "text": " abc\n", "keys": "gli<home>x<esc>"}
+{"name": "ins-end", "pane": "file", "text": " abc\n", "keys": "i<end>x<esc>"}
+{"name": "ins-pgdn", "pane": "file", "text": "l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\nl12\nl13\nl14\nl15\nl16\nl17\nl18\nl19\nl20\nl21\nl22\nl23\nl24\nl25\nl26\nl27\nl28\nl29\n", "keys": "i<pagedown><esc>"}
+{"name": "ins-arrows", "pane": "file", "text": "abc\ndef\n", "keys": "i<down><right>x<esc>"}
+{"name": "ins-tab-col0", "pane": "file", "text": "ab\n", "keys": "i<tab><esc>"}
+{"name": "ins-tab-mid", "pane": "file", "text": "ab\n", "keys": "li<tab><esc>"}
diff --git a/test/hxcases/goldens.jsonl b/test/hxcases/goldens.jsonl
new file mode 100644
index 00000000..baa45d34
--- /dev/null
+++ b/test/hxcases/goldens.jsonl
@@ -0,0 +1,360 @@
+{"name":"h-basic","text":"abc def\n","mode":"normal","cursor":{"row":0,"col":1},"anchor":{"row":0,"col":1}}
+{"name":"h-basic-tty","text":"abc def\n","mode":"normal","cursor":{"row":0,"col":1},"anchor":{"row":0,"col":1}}
+{"name":"h-crosses-nl","text":"ab\ncd\n","mode":"normal","cursor":{"row":0,"col":2},"anchor":{"row":0,"col":2}}
+{"name":"h-crosses-nl-tty","text":"ab\ncd\n","mode":"normal","cursor":{"row":0,"col":2},"anchor":{"row":0,"col":2}}
+{"name":"l-basic","text":"abc\n","mode":"normal","cursor":{"row":0,"col":1},"anchor":{"row":0,"col":1}}
+{"name":"l-basic-tty","text":"abc\n","mode":"normal","cursor":{"row":0,"col":1},"anchor":{"row":0,"col":1}}
+{"name":"l-crosses-nl","text":"ab\ncd\n","mode":"normal","cursor":{"row":1,"col":0},"anchor":{"row":1,"col":0}}
+{"name":"l-crosses-nl-tty","text":"ab\ncd\n","mode":"normal","cursor":{"row":1,"col":0},"anchor":{"row":1,"col":0}}
+{"name":"l-count","text":"abcdef\n","mode":"normal","cursor":{"row":0,"col":3},"anchor":{"row":0,"col":3}}
+{"name":"l-count-tty","text":"abcdef\n","mode":"normal","cursor":{"row":0,"col":3},"anchor":{"row":0,"col":3}}
+{"name":"h-count","text":"abcdef\n","mode":"normal","cursor":{"row":0,"col":2},"anchor":{"row":0,"col":2}}
+{"name":"h-count-tty","text":"abcdef\n","mode":"normal","cursor":{"row":0,"col":2},"anchor":{"row":0,"col":2}}
+{"name":"l-clamp-eof","text":"ab\n","mode":"normal","cursor":{"row":1,"col":0},"anchor":{"row":1,"col":0}}
+{"name":"j-basic","text":"aa\nbb\ncc\n","mode":"normal","cursor":{"row":1,"col":0},"anchor":{"row":1,"col":0}}
+{"name":"j-basic-tty","text":"aa\nbb\ncc\n","mode":"normal","cursor":{"row":1,"col":0},"anchor":{"row":1,"col":0}}
+{"name":"j-onto-nl-cell","text":"abcdef\nab\nabcdef\n","mode":"normal","cursor":{"row":1,"col":2},"anchor":{"row":1,"col":2}}
+{"name":"j-onto-nl-cell-tty","text":"abcdef\nab\nabcdef\n","mode":"normal","cursor":{"row":1,"col":2},"anchor":{"row":1,"col":2}}
+{"name":"j-sticky-col","text":"abcdef\nab\nabcdef\n","mode":"normal","cursor":{"row":2,"col":5},"anchor":{"row":2,"col":5}}
+{"name":"j-sticky-col-tty","text":"abcdef\nab\nabcdef\n","mode":"normal","cursor":{"row":2,"col":5},"anchor":{"row":2,"col":5}}
+{"name":"j-phantom","text":"one\ntwo\n","mode":"normal","cursor":{"row":2,"col":0},"anchor":{"row":2,"col":0}}
+{"name":"k-basic","text":"aa\nbb\n","mode":"normal","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":0}}
+{"name":"k-basic-tty","text":"aa\nbb\n","mode":"normal","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":0}}
+{"name":"k-sticky-col","text":"abcdef\nab\nabcdef\n","mode":"normal","cursor":{"row":0,"col":5},"anchor":{"row":0,"col":5}}
+{"name":"k-sticky-col-tty","text":"abcdef\nab\nabcdef\n","mode":"normal","cursor":{"row":0,"col":5},"anchor":{"row":0,"col":5}}
+{"name":"j-count","text":"a\nb\nc\nd\n","mode":"normal","cursor":{"row":2,"col":0},"anchor":{"row":2,"col":0}}
+{"name":"j-count-tty","text":"a\nb\nc\nd\n","mode":"normal","cursor":{"row":2,"col":0},"anchor":{"row":2,"col":0}}
+{"name":"arrow-right-down","text":"abc\ndef\n","mode":"normal","cursor":{"row":1,"col":1},"anchor":{"row":1,"col":1}}
+{"name":"arrow-right-down-tty","text":"abc\ndef\n","mode":"normal","cursor":{"row":1,"col":1},"anchor":{"row":1,"col":1}}
+{"name":"arrow-left-up","text":"abc\ndef\n","mode":"normal","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":0}}
+{"name":"arrow-left-up-tty","text":"abc\ndef\n","mode":"normal","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":0}}
+{"name":"w-basic","text":"alpha beta\n","mode":"normal","cursor":{"row":0,"col":5},"anchor":{"row":0,"col":0}}
+{"name":"w-basic-tty","text":"alpha beta\n","mode":"normal","cursor":{"row":0,"col":5},"anchor":{"row":0,"col":0}}
+{"name":"w-punct","text":"foo.bar baz\n","mode":"normal","cursor":{"row":0,"col":2},"anchor":{"row":0,"col":0}}
+{"name":"w-punct-tty","text":"foo.bar baz\n","mode":"normal","cursor":{"row":0,"col":2},"anchor":{"row":0,"col":0}}
+{"name":"w-punct-run","text":"foo.bar baz\n","mode":"normal","cursor":{"row":0,"col":3},"anchor":{"row":0,"col":3}}
+{"name":"w-punct-run-tty","text":"foo.bar baz\n","mode":"normal","cursor":{"row":0,"col":3},"anchor":{"row":0,"col":3}}
+{"name":"w-count","text":"one two three four\n","mode":"normal","cursor":{"row":0,"col":7},"anchor":{"row":0,"col":4}}
+{"name":"w-count-tty","text":"one two three four\n","mode":"normal","cursor":{"row":0,"col":7},"anchor":{"row":0,"col":4}}
+{"name":"w-eol-cross","text":"ab cd\nef\n","mode":"normal","cursor":{"row":1,"col":1},"anchor":{"row":1,"col":0}}
+{"name":"w-eol-cross-tty","text":"ab cd\nef\n","mode":"normal","cursor":{"row":1,"col":1},"anchor":{"row":1,"col":0}}
+{"name":"w-from-empty-line","text":"\nabc\n","mode":"normal","cursor":{"row":1,"col":2},"anchor":{"row":1,"col":0}}
+{"name":"w-skips-blank","text":"aa\n\nbb\n","mode":"normal","cursor":{"row":2,"col":1},"anchor":{"row":2,"col":0}}
+{"name":"w-skips-blank-tty","text":"aa\n\nbb\n","mode":"normal","cursor":{"row":2,"col":1},"anchor":{"row":2,"col":0}}
+{"name":"w-eof-collapse","text":"alpha\n","mode":"normal","cursor":{"row":1,"col":0},"anchor":{"row":1,"col":0}}
+{"name":"b-basic","text":"alpha beta\n","mode":"normal","cursor":{"row":0,"col":6},"anchor":{"row":0,"col":9}}
+{"name":"b-basic-tty","text":"alpha beta\n","mode":"normal","cursor":{"row":0,"col":6},"anchor":{"row":0,"col":9}}
+{"name":"b-bof-noop","text":"alpha\n","mode":"normal","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":0}}
+{"name":"b-bof-noop-tty","text":"alpha\n","mode":"normal","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":0}}
+{"name":"b-from-nl-cell","text":"ab\ncd\n","mode":"normal","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":1}}
+{"name":"b-from-nl-cell-tty","text":"ab\ncd\n","mode":"normal","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":1}}
+{"name":"b-count","text":"a bb ccc dddd\n","mode":"normal","cursor":{"row":0,"col":5},"anchor":{"row":0,"col":8}}
+{"name":"b-count-tty","text":"a bb ccc dddd\n","mode":"normal","cursor":{"row":0,"col":5},"anchor":{"row":0,"col":8}}
+{"name":"e-basic","text":"alpha beta\n","mode":"normal","cursor":{"row":0,"col":4},"anchor":{"row":0,"col":0}}
+{"name":"e-basic-tty","text":"alpha beta\n","mode":"normal","cursor":{"row":0,"col":4},"anchor":{"row":0,"col":0}}
+{"name":"e-from-mid","text":"alpha beta\n","mode":"normal","cursor":{"row":0,"col":4},"anchor":{"row":0,"col":1}}
+{"name":"e-from-mid-tty","text":"alpha beta\n","mode":"normal","cursor":{"row":0,"col":4},"anchor":{"row":0,"col":1}}
+{"name":"e-count","text":"a bb ccc dddd\n","mode":"normal","cursor":{"row":0,"col":7},"anchor":{"row":0,"col":4}}
+{"name":"e-count-tty","text":"a bb ccc dddd\n","mode":"normal","cursor":{"row":0,"col":7},"anchor":{"row":0,"col":4}}
+{"name":"e-cross-blank","text":"aa\n\nbb\n","mode":"normal","cursor":{"row":2,"col":1},"anchor":{"row":2,"col":0}}
+{"name":"e-cross-blank-tty","text":"aa\n\nbb\n","mode":"normal","cursor":{"row":2,"col":1},"anchor":{"row":2,"col":0}}
+{"name":"W-basic","text":"foo.bar baz\n","mode":"normal","cursor":{"row":0,"col":7},"anchor":{"row":0,"col":0}}
+{"name":"W-basic-tty","text":"foo.bar baz\n","mode":"normal","cursor":{"row":0,"col":7},"anchor":{"row":0,"col":0}}
+{"name":"B-basic","text":"foo.bar baz\n","mode":"normal","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":7}}
+{"name":"B-basic-tty","text":"foo.bar baz\n","mode":"normal","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":7}}
+{"name":"E-basic","text":"foo.bar baz\n","mode":"normal","cursor":{"row":0,"col":6},"anchor":{"row":0,"col":0}}
+{"name":"E-basic-tty","text":"foo.bar baz\n","mode":"normal","cursor":{"row":0,"col":6},"anchor":{"row":0,"col":0}}
+{"name":"f-basic","text":"abcabc\n","mode":"normal","cursor":{"row":0,"col":2},"anchor":{"row":0,"col":0}}
+{"name":"f-basic-tty","text":"abcabc\n","mode":"normal","cursor":{"row":0,"col":2},"anchor":{"row":0,"col":0}}
+{"name":"f-count","text":"abcabc\n","mode":"normal","cursor":{"row":0,"col":5},"anchor":{"row":0,"col":0}}
+{"name":"f-count-tty","text":"abcabc\n","mode":"normal","cursor":{"row":0,"col":5},"anchor":{"row":0,"col":0}}
+{"name":"f-cross-line","text":"one\ntwo\n","mode":"normal","cursor":{"row":1,"col":1},"anchor":{"row":0,"col":0}}
+{"name":"f-cross-line-tty","text":"one\ntwo\n","mode":"normal","cursor":{"row":1,"col":1},"anchor":{"row":0,"col":0}}
+{"name":"f-notfound-noop","text":"abc\n","mode":"normal","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":0}}
+{"name":"f-notfound-noop-tty","text":"abc\n","mode":"normal","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":0}}
+{"name":"f-digit-arg","text":"a4b\n","mode":"normal","cursor":{"row":0,"col":1},"anchor":{"row":0,"col":0}}
+{"name":"f-digit-arg-tty","text":"a4b\n","mode":"normal","cursor":{"row":0,"col":1},"anchor":{"row":0,"col":0}}
+{"name":"f-repeat-same-target","text":"abcabc\n","mode":"normal","cursor":{"row":0,"col":3},"anchor":{"row":0,"col":0}}
+{"name":"t-basic","text":"abcabc\n","mode":"normal","cursor":{"row":0,"col":1},"anchor":{"row":0,"col":0}}
+{"name":"t-basic-tty","text":"abcabc\n","mode":"normal","cursor":{"row":0,"col":1},"anchor":{"row":0,"col":0}}
+{"name":"t-twice-skips-edge","text":"abcabc\n","mode":"normal","cursor":{"row":0,"col":4},"anchor":{"row":0,"col":1}}
+{"name":"t-twice-skips-edge-tty","text":"abcabc\n","mode":"normal","cursor":{"row":0,"col":4},"anchor":{"row":0,"col":1}}
+{"name":"F-backward","text":"abcabc\n","mode":"normal","cursor":{"row":0,"col":3},"anchor":{"row":0,"col":5}}
+{"name":"F-backward-tty","text":"abcabc\n","mode":"normal","cursor":{"row":0,"col":3},"anchor":{"row":0,"col":5}}
+{"name":"T-backward","text":"abcabc\n","mode":"normal","cursor":{"row":0,"col":4},"anchor":{"row":0,"col":5}}
+{"name":"T-backward-tty","text":"abcabc\n","mode":"normal","cursor":{"row":0,"col":4},"anchor":{"row":0,"col":5}}
+{"name":"alt-dot-repeat-f","text":"abcabc\n","mode":"normal","cursor":{"row":0,"col":5},"anchor":{"row":0,"col":2}}
+{"name":"alt-dot-repeat-f-tty","text":"abcabc\n","mode":"normal","cursor":{"row":0,"col":5},"anchor":{"row":0,"col":2}}
+{"name":"alt-dot-repeat-t","text":"abcabc\n","mode":"normal","cursor":{"row":0,"col":4},"anchor":{"row":0,"col":1}}
+{"name":"alt-dot-repeat-t-tty","text":"abcabc\n","mode":"normal","cursor":{"row":0,"col":4},"anchor":{"row":0,"col":1}}
+{"name":"alt-dot-count","text":"axbxcxd\n","mode":"normal","cursor":{"row":0,"col":5},"anchor":{"row":0,"col":3}}
+{"name":"alt-dot-count-tty","text":"axbxcxd\n","mode":"normal","cursor":{"row":0,"col":5},"anchor":{"row":0,"col":3}}
+{"name":"G-count","text":"l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\n","mode":"normal","cursor":{"row":4,"col":0},"anchor":{"row":4,"col":0}}
+{"name":"G-count-tty","text":"l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\n","mode":"normal","cursor":{"row":4,"col":0},"anchor":{"row":4,"col":0}}
+{"name":"G-count-clamp","text":"one\ntwo\n","mode":"normal","cursor":{"row":1,"col":0},"anchor":{"row":1,"col":0}}
+{"name":"G-count-clamp-tty","text":"one\ntwo\n","mode":"normal","cursor":{"row":1,"col":0},"anchor":{"row":1,"col":0}}
+{"name":"G-bare-noop","text":"aa\nbb\ncc\n","mode":"normal","cursor":{"row":1,"col":0},"anchor":{"row":1,"col":0}}
+{"name":"G-bare-noop-tty","text":"aa\nbb\ncc\n","mode":"normal","cursor":{"row":1,"col":0},"anchor":{"row":1,"col":0}}
+{"name":"count-multidigit","text":"l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\n","mode":"normal","cursor":{"row":10,"col":0},"anchor":{"row":10,"col":0}}
+{"name":"count-multidigit-tty","text":"l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\n","mode":"normal","cursor":{"row":10,"col":0},"anchor":{"row":10,"col":0}}
+{"name":"G-count-resets-col","text":"l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\n","mode":"normal","cursor":{"row":4,"col":0},"anchor":{"row":4,"col":0}}
+{"name":"G-count-resets-col-tty","text":"l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\n","mode":"normal","cursor":{"row":4,"col":0},"anchor":{"row":4,"col":0}}
+{"name":"gg-basic","text":"aa\nbb\ncc\n","mode":"normal","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":0}}
+{"name":"gg-basic-tty","text":"aa\nbb\ncc\n","mode":"normal","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":0}}
+{"name":"gg-count","text":"l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\n","mode":"normal","cursor":{"row":2,"col":0},"anchor":{"row":2,"col":0}}
+{"name":"gg-count-tty","text":"l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\n","mode":"normal","cursor":{"row":2,"col":0},"anchor":{"row":2,"col":0}}
+{"name":"ge-basic","text":"aa\nbb\ncc\n","mode":"normal","cursor":{"row":2,"col":0},"anchor":{"row":2,"col":0}}
+{"name":"ge-basic-tty","text":"aa\nbb\ncc\n","mode":"normal","cursor":{"row":2,"col":0},"anchor":{"row":2,"col":0}}
+{"name":"ge-col0","text":"alpha beta\n","mode":"normal","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":0}}
+{"name":"ge-col0-tty","text":"alpha beta\n","mode":"normal","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":0}}
+{"name":"gh-basic","text":"abc\n","mode":"normal","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":0}}
+{"name":"gh-basic-tty","text":"abc\n","mode":"normal","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":0}}
+{"name":"gl-basic","text":"abc def\n","mode":"normal","cursor":{"row":0,"col":6},"anchor":{"row":0,"col":6}}
+{"name":"gl-basic-tty","text":"abc def\n","mode":"normal","cursor":{"row":0,"col":6},"anchor":{"row":0,"col":6}}
+{"name":"gl-empty-line","text":"\nabc\n","mode":"normal","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":0}}
+{"name":"gl-empty-line-tty","text":"\nabc\n","mode":"normal","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":0}}
+{"name":"gs-basic","text":" abc\n","mode":"normal","cursor":{"row":0,"col":2},"anchor":{"row":0,"col":2}}
+{"name":"gs-basic-tty","text":" abc\n","mode":"normal","cursor":{"row":0,"col":2},"anchor":{"row":0,"col":2}}
+{"name":"gs-allws-noop","text":" \nx\n","mode":"normal","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":0}}
+{"name":"gs-allws-noop-tty","text":" \nx\n","mode":"normal","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":0}}
+{"name":"gbar-count","text":"abcdef\n","mode":"normal","cursor":{"row":0,"col":3},"anchor":{"row":0,"col":3}}
+{"name":"gbar-count-tty","text":"abcdef\n","mode":"normal","cursor":{"row":0,"col":3},"anchor":{"row":0,"col":3}}
+{"name":"gbar-clamp","text":"ab\n","mode":"normal","cursor":{"row":0,"col":2},"anchor":{"row":0,"col":2}}
+{"name":"gbar-clamp-tty","text":"ab\n","mode":"normal","cursor":{"row":0,"col":2},"anchor":{"row":0,"col":2}}
+{"name":"gbar-nocount","text":"abcdef\n","mode":"normal","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":0}}
+{"name":"gbar-nocount-tty","text":"abcdef\n","mode":"normal","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":0}}
+{"name":"gj-basic","text":"abcdef\nab\nabcdef\n","mode":"normal","cursor":{"row":2,"col":5},"anchor":{"row":2,"col":5}}
+{"name":"gj-basic-tty","text":"abcdef\nab\nabcdef\n","mode":"normal","cursor":{"row":2,"col":5},"anchor":{"row":2,"col":5}}
+{"name":"gk-basic","text":"aa\nbb\ncc\n","mode":"normal","cursor":{"row":1,"col":0},"anchor":{"row":1,"col":0}}
+{"name":"gk-basic-tty","text":"aa\nbb\ncc\n","mode":"normal","cursor":{"row":1,"col":0},"anchor":{"row":1,"col":0}}
+{"name":"home-basic","text":"abc def\n","mode":"normal","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":0}}
+{"name":"home-basic-tty","text":"abc def\n","mode":"normal","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":0}}
+{"name":"end-basic","text":"abc def\n","mode":"normal","cursor":{"row":0,"col":6},"anchor":{"row":0,"col":6}}
+{"name":"end-basic-tty","text":"abc def\n","mode":"normal","cursor":{"row":0,"col":6},"anchor":{"row":0,"col":6}}
+{"name":"end-empty-line","text":"\nabc\n","mode":"normal","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":0}}
+{"name":"end-empty-line-tty","text":"\nabc\n","mode":"normal","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":0}}
+{"name":"gt-noscroll","text":"l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\n","mode":"normal","cursor":{"row":3,"col":0},"anchor":{"row":3,"col":0}}
+{"name":"gt-noscroll-tty","text":"l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\n","mode":"normal","cursor":{"row":3,"col":0},"anchor":{"row":3,"col":0}}
+{"name":"gc-noscroll","text":"l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\n","mode":"normal","cursor":{"row":6,"col":0},"anchor":{"row":6,"col":0}}
+{"name":"gb-noscroll","text":"l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\n","mode":"normal","cursor":{"row":9,"col":0},"anchor":{"row":9,"col":0}}
+{"name":"gt-after-zz","text":"l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\nl12\nl13\nl14\nl15\nl16\nl17\nl18\nl19\nl20\nl21\nl22\nl23\nl24\nl25\nl26\nl27\nl28\nl29\n","mode":"normal","cursor":{"row":8,"col":0},"anchor":{"row":8,"col":0}}
+{"name":"gc-after-zz","text":"l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\nl12\nl13\nl14\nl15\nl16\nl17\nl18\nl19\nl20\nl21\nl22\nl23\nl24\nl25\nl26\nl27\nl28\nl29\n","mode":"normal","cursor":{"row":15,"col":0},"anchor":{"row":15,"col":0}}
+{"name":"gb-after-zz","text":"l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\nl12\nl13\nl14\nl15\nl16\nl17\nl18\nl19\nl20\nl21\nl22\nl23\nl24\nl25\nl26\nl27\nl28\nl29\n","mode":"normal","cursor":{"row":23,"col":0},"anchor":{"row":23,"col":0}}
+{"name":"gt-count","text":"l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\nl12\nl13\nl14\nl15\nl16\nl17\nl18\nl19\nl20\nl21\nl22\nl23\nl24\nl25\nl26\nl27\nl28\nl29\n","mode":"normal","cursor":{"row":9,"col":0},"anchor":{"row":9,"col":0}}
+{"name":"ctrl-d-basic","text":"l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\nl12\nl13\nl14\nl15\nl16\nl17\nl18\nl19\nl20\nl21\nl22\nl23\nl24\nl25\nl26\nl27\nl28\nl29\n","mode":"normal","cursor":{"row":11,"col":0},"anchor":{"row":11,"col":0}}
+{"name":"ctrl-d-small","text":"l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\n","mode":"normal","cursor":{"row":11,"col":0},"anchor":{"row":11,"col":0}}
+{"name":"ctrl-d-sticky","text":"l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\nl12\nl13\nl14\nl15\nl16\nl17\nl18\nl19\nl20\nl21\nl22\nl23\nl24\nl25\nl26\nl27\nl28\nl29\n","mode":"normal","cursor":{"row":12,"col":1},"anchor":{"row":12,"col":1}}
+{"name":"ctrl-u-back","text":"l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\nl12\nl13\nl14\nl15\nl16\nl17\nl18\nl19\nl20\nl21\nl22\nl23\nl24\nl25\nl26\nl27\nl28\nl29\n","mode":"normal","cursor":{"row":9,"col":0},"anchor":{"row":9,"col":0}}
+{"name":"ctrl-f-page","text":"l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\nl12\nl13\nl14\nl15\nl16\nl17\nl18\nl19\nl20\nl21\nl22\nl23\nl24\nl25\nl26\nl27\nl28\nl29\n","mode":"normal","cursor":{"row":25,"col":0},"anchor":{"row":25,"col":0}}
+{"name":"ctrl-f-small","text":"l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\n","mode":"normal","cursor":{"row":12,"col":0},"anchor":{"row":12,"col":0}}
+{"name":"ctrl-b-page","text":"l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\nl12\nl13\nl14\nl15\nl16\nl17\nl18\nl19\nl20\nl21\nl22\nl23\nl24\nl25\nl26\nl27\nl28\nl29\n","mode":"normal","cursor":{"row":18,"col":0},"anchor":{"row":18,"col":0}}
+{"name":"pgdn-basic","text":"l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\nl12\nl13\nl14\nl15\nl16\nl17\nl18\nl19\nl20\nl21\nl22\nl23\nl24\nl25\nl26\nl27\nl28\nl29\n","mode":"normal","cursor":{"row":25,"col":0},"anchor":{"row":25,"col":0}}
+{"name":"pgup-basic","text":"l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\nl12\nl13\nl14\nl15\nl16\nl17\nl18\nl19\nl20\nl21\nl22\nl23\nl24\nl25\nl26\nl27\nl28\nl29\n","mode":"normal","cursor":{"row":18,"col":0},"anchor":{"row":18,"col":0}}
+{"name":"pgup-noop-in-view","text":"l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\n","mode":"normal","cursor":{"row":5,"col":0},"anchor":{"row":5,"col":0}}
+{"name":"pgup-noop-in-view-tty","text":"l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\n","mode":"normal","cursor":{"row":5,"col":0},"anchor":{"row":5,"col":0}}
+{"name":"v-ctrl-d-extends","text":"l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\nl12\nl13\nl14\nl15\nl16\nl17\nl18\nl19\nl20\nl21\nl22\nl23\nl24\nl25\nl26\nl27\nl28\nl29\n","mode":"select","cursor":{"row":11,"col":0},"anchor":{"row":0,"col":0}}
+{"name":"zt-then-gt","text":"l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\nl12\nl13\nl14\nl15\nl16\nl17\nl18\nl19\nl20\nl21\nl22\nl23\nl24\nl25\nl26\nl27\nl28\nl29\n","mode":"normal","cursor":{"row":12,"col":0},"anchor":{"row":12,"col":0}}
+{"name":"zz-then-gt","text":"l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\nl12\nl13\nl14\nl15\nl16\nl17\nl18\nl19\nl20\nl21\nl22\nl23\nl24\nl25\nl26\nl27\nl28\nl29\n","mode":"normal","cursor":{"row":8,"col":0},"anchor":{"row":8,"col":0}}
+{"name":"zb-then-gt","text":"l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\nl12\nl13\nl14\nl15\nl16\nl17\nl18\nl19\nl20\nl21\nl22\nl23\nl24\nl25\nl26\nl27\nl28\nl29\n","mode":"normal","cursor":{"row":3,"col":0},"anchor":{"row":3,"col":0}}
+{"name":"zc-alias-zz","text":"l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\nl12\nl13\nl14\nl15\nl16\nl17\nl18\nl19\nl20\nl21\nl22\nl23\nl24\nl25\nl26\nl27\nl28\nl29\n","mode":"normal","cursor":{"row":8,"col":0},"anchor":{"row":8,"col":0}}
+{"name":"zj-snaps-cursor","text":"l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\nl12\nl13\nl14\nl15\nl16\nl17\nl18\nl19\nl20\nl21\nl22\nl23\nl24\nl25\nl26\nl27\nl28\nl29\n","mode":"normal","cursor":{"row":4,"col":0},"anchor":{"row":4,"col":0}}
+{"name":"zj-noop-in-band","text":"l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\nl12\nl13\nl14\nl15\nl16\nl17\nl18\nl19\nl20\nl21\nl22\nl23\nl24\nl25\nl26\nl27\nl28\nl29\n","mode":"normal","cursor":{"row":10,"col":0},"anchor":{"row":10,"col":0}}
+{"name":"zk-bottom-snap","text":"l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\nl12\nl13\nl14\nl15\nl16\nl17\nl18\nl19\nl20\nl21\nl22\nl23\nl24\nl25\nl26\nl27\nl28\nl29\n","mode":"normal","cursor":{"row":19,"col":0},"anchor":{"row":19,"col":0}}
+{"name":"z-ctrl-d-alias","text":"l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\nl12\nl13\nl14\nl15\nl16\nl17\nl18\nl19\nl20\nl21\nl22\nl23\nl24\nl25\nl26\nl27\nl28\nl29\n","mode":"normal","cursor":{"row":11,"col":0},"anchor":{"row":11,"col":0}}
+{"name":"z-pgdn-alias","text":"l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\nl12\nl13\nl14\nl15\nl16\nl17\nl18\nl19\nl20\nl21\nl22\nl23\nl24\nl25\nl26\nl27\nl28\nl29\n","mode":"normal","cursor":{"row":25,"col":0},"anchor":{"row":25,"col":0}}
+{"name":"v-reports-select","text":"alpha beta\n","mode":"select","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":0}}
+{"name":"v-reports-select-tty","text":"alpha beta\n","mode":"select","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":0}}
+{"name":"v-extend-w","text":"alpha beta gamma\n","mode":"select","cursor":{"row":0,"col":10},"anchor":{"row":0,"col":0}}
+{"name":"v-extend-w-tty","text":"alpha beta gamma\n","mode":"select","cursor":{"row":0,"col":10},"anchor":{"row":0,"col":0}}
+{"name":"v-anchor-fixed-crossing","text":"abcdef\n","mode":"select","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":2}}
+{"name":"v-anchor-fixed-crossing-tty","text":"abcdef\n","mode":"select","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":2}}
+{"name":"v-exit-keeps-sel","text":"alpha beta\n","mode":"normal","cursor":{"row":0,"col":5},"anchor":{"row":0,"col":0}}
+{"name":"v-exit-keeps-sel-tty","text":"alpha beta\n","mode":"normal","cursor":{"row":0,"col":5},"anchor":{"row":0,"col":0}}
+{"name":"v-esc-keeps-sel","text":"alpha beta\n","mode":"normal","cursor":{"row":0,"col":5},"anchor":{"row":0,"col":0}}
+{"name":"v-esc-keeps-sel-tty","text":"alpha beta\n","mode":"normal","cursor":{"row":0,"col":5},"anchor":{"row":0,"col":0}}
+{"name":"v-gl-extends","text":"abc def\n","mode":"select","cursor":{"row":0,"col":6},"anchor":{"row":0,"col":0}}
+{"name":"v-gl-extends-tty","text":"abc def\n","mode":"select","cursor":{"row":0,"col":6},"anchor":{"row":0,"col":0}}
+{"name":"v-gg-extends","text":"aa\nbb\ncc\n","mode":"select","cursor":{"row":0,"col":0},"anchor":{"row":2,"col":0}}
+{"name":"v-gg-extends-tty","text":"aa\nbb\ncc\n","mode":"select","cursor":{"row":0,"col":0},"anchor":{"row":2,"col":0}}
+{"name":"v-find-extends","text":"abcabc\n","mode":"select","cursor":{"row":0,"col":5},"anchor":{"row":0,"col":0}}
+{"name":"v-find-extends-tty","text":"abcabc\n","mode":"select","cursor":{"row":0,"col":5},"anchor":{"row":0,"col":0}}
+{"name":"v-x-extends-line","text":"ab\ncd\nef\n","mode":"select","cursor":{"row":1,"col":2},"anchor":{"row":0,"col":0}}
+{"name":"v-x-extends-line-tty","text":"ab\ncd\nef\n","mode":"select","cursor":{"row":1,"col":2},"anchor":{"row":0,"col":0}}
+{"name":"v-j-phantom-blocked","text":"one\ntwo\n","mode":"select","cursor":{"row":1,"col":0},"anchor":{"row":0,"col":0}}
+{"name":"w-esc-keeps-sel","text":"alpha beta\n","mode":"normal","cursor":{"row":0,"col":5},"anchor":{"row":0,"col":0}}
+{"name":"w-esc-keeps-sel-tty","text":"alpha beta\n","mode":"normal","cursor":{"row":0,"col":5},"anchor":{"row":0,"col":0}}
+{"name":"semi-collapse","text":"alpha beta\n","mode":"normal","cursor":{"row":0,"col":5},"anchor":{"row":0,"col":5}}
+{"name":"semi-collapse-tty","text":"alpha beta\n","mode":"normal","cursor":{"row":0,"col":5},"anchor":{"row":0,"col":5}}
+{"name":"altsemi-flip","text":"alpha beta\n","mode":"normal","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":5}}
+{"name":"altsemi-flip-tty","text":"alpha beta\n","mode":"normal","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":5}}
+{"name":"altsemi-flip-x","text":"one\ntwo\ncc\n","mode":"normal","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":3}}
+{"name":"altsemi-flip-x-tty","text":"one\ntwo\ncc\n","mode":"normal","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":3}}
+{"name":"percent-select-all","text":"one\ntwo\n","mode":"normal","cursor":{"row":1,"col":3},"anchor":{"row":0,"col":0}}
+{"name":"percent-then-d","text":"","mode":"normal","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":0}}
+{"name":"x-selects-line","text":"one\ntwo\nthree\n","mode":"normal","cursor":{"row":0,"col":3},"anchor":{"row":0,"col":0}}
+{"name":"x-selects-line-tty","text":"one\ntwo\nthree\n","mode":"normal","cursor":{"row":0,"col":3},"anchor":{"row":0,"col":0}}
+{"name":"x-extends","text":"one\ntwo\nthree\n","mode":"normal","cursor":{"row":1,"col":3},"anchor":{"row":0,"col":0}}
+{"name":"x-extends-tty","text":"one\ntwo\nthree\n","mode":"normal","cursor":{"row":1,"col":3},"anchor":{"row":0,"col":0}}
+{"name":"x-count","text":"a\nb\nc\nd\n","mode":"normal","cursor":{"row":1,"col":1},"anchor":{"row":0,"col":0}}
+{"name":"x-count-tty","text":"a\nb\nc\nd\n","mode":"normal","cursor":{"row":1,"col":1},"anchor":{"row":0,"col":0}}
+{"name":"x-empty-line","text":"a\n\nb\nc\n","mode":"normal","cursor":{"row":2,"col":1},"anchor":{"row":1,"col":0}}
+{"name":"x-empty-line-tty","text":"a\n\nb\nc\n","mode":"normal","cursor":{"row":2,"col":1},"anchor":{"row":1,"col":0}}
+{"name":"x-last-line","text":"one\ntwo\n","mode":"normal","cursor":{"row":1,"col":3},"anchor":{"row":1,"col":0}}
+{"name":"x-then-w-drops","text":"aa\nbb cc\n","mode":"normal","cursor":{"row":1,"col":2},"anchor":{"row":1,"col":0}}
+{"name":"x-then-w-drops-tty","text":"aa\nbb cc\n","mode":"normal","cursor":{"row":1,"col":2},"anchor":{"row":1,"col":0}}
+{"name":"x-then-d","text":"two\nthree\n","mode":"normal","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":0}}
+{"name":"X-bare","text":"abc\ndef\n","mode":"normal","cursor":{"row":0,"col":3},"anchor":{"row":0,"col":0}}
+{"name":"X-bare-tty","text":"abc\ndef\n","mode":"normal","cursor":{"row":0,"col":3},"anchor":{"row":0,"col":0}}
+{"name":"X-from-v","text":"abc\ndef\nghi\n","mode":"select","cursor":{"row":1,"col":3},"anchor":{"row":0,"col":0}}
+{"name":"X-from-v-tty","text":"abc\ndef\nghi\n","mode":"select","cursor":{"row":1,"col":3},"anchor":{"row":0,"col":0}}
+{"name":"X-keeps-direction","text":"abc\ndef\nghi\n","mode":"select","cursor":{"row":0,"col":0},"anchor":{"row":1,"col":3}}
+{"name":"X-keeps-direction-tty","text":"abc\ndef\nghi\n","mode":"select","cursor":{"row":0,"col":0},"anchor":{"row":1,"col":3}}
+{"name":"X-noop-when-full","text":"abc\ndef\n","mode":"normal","cursor":{"row":0,"col":3},"anchor":{"row":0,"col":0}}
+{"name":"altx-shrink","text":"abc\ndef\nghi\n","mode":"select","cursor":{"row":1,"col":3},"anchor":{"row":1,"col":0}}
+{"name":"altx-shrink-tty","text":"abc\ndef\nghi\n","mode":"select","cursor":{"row":1,"col":3},"anchor":{"row":1,"col":0}}
+{"name":"altx-single-line-noop","text":"abcdef\n","mode":"select","cursor":{"row":0,"col":3},"anchor":{"row":0,"col":1}}
+{"name":"altx-single-line-noop-tty","text":"abcdef\n","mode":"select","cursor":{"row":0,"col":3},"anchor":{"row":0,"col":1}}
+{"name":"mm-basic","text":"a(bc(d)e)f\n","mode":"normal","cursor":{"row":0,"col":8},"anchor":{"row":0,"col":8}}
+{"name":"mm-basic-tty","text":"a(bc(d)e)f\n","mode":"normal","cursor":{"row":0,"col":8},"anchor":{"row":0,"col":8}}
+{"name":"mm-back","text":"a(bc(d)e)f\n","mode":"normal","cursor":{"row":0,"col":1},"anchor":{"row":0,"col":1}}
+{"name":"mm-back-tty","text":"a(bc(d)e)f\n","mode":"normal","cursor":{"row":0,"col":1},"anchor":{"row":0,"col":1}}
+{"name":"mm-nonbracket-noop","text":"abc\n","mode":"normal","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":0}}
+{"name":"mm-nonbracket-noop-tty","text":"abc\n","mode":"normal","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":0}}
+{"name":"mi-paren","text":"fn(a, b)\n","mode":"normal","cursor":{"row":0,"col":6},"anchor":{"row":0,"col":3}}
+{"name":"mi-paren-tty","text":"fn(a, b)\n","mode":"normal","cursor":{"row":0,"col":6},"anchor":{"row":0,"col":3}}
+{"name":"ma-paren","text":"fn(a, b)\n","mode":"normal","cursor":{"row":0,"col":7},"anchor":{"row":0,"col":2}}
+{"name":"ma-paren-tty","text":"fn(a, b)\n","mode":"normal","cursor":{"row":0,"col":7},"anchor":{"row":0,"col":2}}
+{"name":"mi-nested","text":"(a(b)c)\n","mode":"normal","cursor":{"row":0,"col":3},"anchor":{"row":0,"col":3}}
+{"name":"mi-nested-tty","text":"(a(b)c)\n","mode":"normal","cursor":{"row":0,"col":3},"anchor":{"row":0,"col":3}}
+{"name":"mi-word","text":"alpha beta\n","mode":"normal","cursor":{"row":0,"col":9},"anchor":{"row":0,"col":6}}
+{"name":"mi-word-tty","text":"alpha beta\n","mode":"normal","cursor":{"row":0,"col":9},"anchor":{"row":0,"col":6}}
+{"name":"mi-WORD","text":"a.b c.d\n","mode":"normal","cursor":{"row":0,"col":2},"anchor":{"row":0,"col":0}}
+{"name":"mi-WORD-tty","text":"a.b c.d\n","mode":"normal","cursor":{"row":0,"col":2},"anchor":{"row":0,"col":0}}
+{"name":"ma-word","text":"alpha beta gamma\n","mode":"normal","cursor":{"row":0,"col":10},"anchor":{"row":0,"col":10}}
+{"name":"mi-quote","text":"say \"hi there\" ok\n","mode":"normal","cursor":{"row":0,"col":12},"anchor":{"row":0,"col":5}}
+{"name":"ma-quote","text":"say \"hi there\" ok\n","mode":"normal","cursor":{"row":0,"col":13},"anchor":{"row":0,"col":4}}
+{"name":"mi-para","text":"aa\nbb\n\ncc\n","mode":"normal","cursor":{"row":1,"col":2},"anchor":{"row":0,"col":0}}
+{"name":"mi-para-tty","text":"aa\nbb\n\ncc\n","mode":"normal","cursor":{"row":1,"col":2},"anchor":{"row":0,"col":0}}
+{"name":"ma-para","text":"aa\nbb\n\ncc\n","mode":"normal","cursor":{"row":2,"col":0},"anchor":{"row":0,"col":0}}
+{"name":"mi-para-blank","text":"aa\n\ncc\n","mode":"normal","cursor":{"row":2,"col":2},"anchor":{"row":2,"col":0}}
+{"name":"mi-empty-pair","text":"a()b\n","mode":"normal","cursor":{"row":0,"col":2},"anchor":{"row":0,"col":2}}
+{"name":"par-fwd","text":"aa\nbb\n\ncc\ndd\n\nee\n","mode":"normal","cursor":{"row":2,"col":0},"anchor":{"row":0,"col":0}}
+{"name":"par-fwd-tty","text":"aa\nbb\n\ncc\ndd\n\nee\n","mode":"normal","cursor":{"row":2,"col":0},"anchor":{"row":0,"col":0}}
+{"name":"par-fwd-count","text":"aa\nbb\n\ncc\ndd\n\nee\n","mode":"normal","cursor":{"row":5,"col":0},"anchor":{"row":0,"col":0}}
+{"name":"par-fwd-count-tty","text":"aa\nbb\n\ncc\ndd\n\nee\n","mode":"normal","cursor":{"row":5,"col":0},"anchor":{"row":0,"col":0}}
+{"name":"par-bwd","text":"aa\nbb\n\ncc\ndd\n\nee\n","mode":"normal","cursor":{"row":3,"col":0},"anchor":{"row":5,"col":0}}
+{"name":"par-bwd-tty","text":"aa\nbb\n\ncc\ndd\n\nee\n","mode":"normal","cursor":{"row":3,"col":0},"anchor":{"row":5,"col":0}}
+{"name":"par-fwd-mid","text":"aa\nbb\n\ncc\ndd\n\nee\n","mode":"normal","cursor":{"row":2,"col":0},"anchor":{"row":1,"col":0}}
+{"name":"par-fwd-mid-tty","text":"aa\nbb\n\ncc\ndd\n\nee\n","mode":"normal","cursor":{"row":2,"col":0},"anchor":{"row":1,"col":0}}
+{"name":"par-bwd-mid","text":"aa\nbb\n\ncc\ndd\n\nee\n","mode":"normal","cursor":{"row":3,"col":0},"anchor":{"row":4,"col":0}}
+{"name":"par-bwd-mid-tty","text":"aa\nbb\n\ncc\ndd\n\nee\n","mode":"normal","cursor":{"row":3,"col":0},"anchor":{"row":4,"col":0}}
+{"name":"par-fwd-at-end","text":"aa\n\nbb\n","mode":"normal","cursor":{"row":2,"col":2},"anchor":{"row":2,"col":0}}
+{"name":"add-nl-below","text":"aa\n\nbb\n","mode":"normal","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":0}}
+{"name":"add-nl-above","text":"aa\n\nbb\n","mode":"normal","cursor":{"row":2,"col":0},"anchor":{"row":2,"col":0}}
+{"name":"add-nl-count","text":"aa\n\n\nbb\n","mode":"normal","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":0}}
+{"name":"d-word","text":"beta\n","mode":"normal","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":0}}
+{"name":"d-bare-char","text":"bc\n","mode":"normal","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":0}}
+{"name":"d-on-nl-joins","text":"abcd\n","mode":"normal","cursor":{"row":0,"col":2},"anchor":{"row":0,"col":2}}
+{"name":"d-x-lines","text":"bbb\nccc\n","mode":"normal","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":0}}
+{"name":"d-percent-all","text":"","mode":"normal","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":0}}
+{"name":"alt-d-noyank","text":"beta\n","mode":"normal","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":0}}
+{"name":"alt-c-window-op","text":"bc def\n","mode":"insert","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":0}}
+{"name":"d-then-p","text":"balpha eta\n","mode":"normal","cursor":{"row":0,"col":6},"anchor":{"row":0,"col":1}}
+{"name":"c-word","text":"Xbeta gamma\n","mode":"normal","cursor":{"row":0,"col":1},"anchor":{"row":0,"col":1}}
+{"name":"c-bare","text":"Zbc\n","mode":"normal","cursor":{"row":0,"col":1},"anchor":{"row":0,"col":1}}
+{"name":"c-linewise","text":"X\nbbb\n","mode":"normal","cursor":{"row":0,"col":1},"anchor":{"row":0,"col":1}}
+{"name":"c-linewise-indent","text":"Z\nbb\n","mode":"normal","cursor":{"row":0,"col":1},"anchor":{"row":0,"col":1}}
+{"name":"y-keeps-sel","text":"alpha beta\n","mode":"normal","cursor":{"row":0,"col":5},"anchor":{"row":0,"col":0}}
+{"name":"y-keeps-sel-tty","text":"alpha beta\n","mode":"normal","cursor":{"row":0,"col":5},"anchor":{"row":0,"col":0}}
+{"name":"y-bare-char","text":"aabc def\n","mode":"normal","cursor":{"row":0,"col":1},"anchor":{"row":0,"col":1}}
+{"name":"y-bare-eol","text":"abb\ncd\n","mode":"normal","cursor":{"row":0,"col":2},"anchor":{"row":0,"col":2}}
+{"name":"yp-word","text":"alpha beta beta\n","mode":"normal","cursor":{"row":0,"col":14},"anchor":{"row":0,"col":10}}
+{"name":"xyp-linewise","text":"one\none\ntwo\n","mode":"normal","cursor":{"row":1,"col":3},"anchor":{"row":1,"col":0}}
+{"name":"xyp-below-end-line","text":"one\ntwo\none\n","mode":"normal","cursor":{"row":2,"col":3},"anchor":{"row":2,"col":0}}
+{"name":"xyP-linewise","text":"one\none\ntwo\n","mode":"normal","cursor":{"row":0,"col":3},"anchor":{"row":0,"col":0}}
+{"name":"vlyp-inline","text":"ababcd\n","mode":"normal","cursor":{"row":0,"col":3},"anchor":{"row":0,"col":2}}
+{"name":"vlyP-inline","text":"ababcd\n","mode":"normal","cursor":{"row":0,"col":1},"anchor":{"row":0,"col":0}}
+{"name":"p-count","text":"aaab\n","mode":"normal","cursor":{"row":0,"col":2},"anchor":{"row":0,"col":1}}
+{"name":"p-after-sel-end","text":"abcdab\n","mode":"normal","cursor":{"row":0,"col":5},"anchor":{"row":0,"col":4}}
+{"name":"wP-before","text":"one one two\n","mode":"normal","cursor":{"row":0,"col":3},"anchor":{"row":0,"col":0}}
+{"name":"u-restores-sel","text":"alpha beta\n","mode":"normal","cursor":{"row":0,"col":5},"anchor":{"row":0,"col":0}}
+{"name":"u-insert-session","text":"abc\n","mode":"normal","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":0}}
+{"name":"U-redo","text":"beta\n","mode":"normal","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":0}}
+{"name":"u-linewise","text":"aaa\nbbb\n","mode":"normal","cursor":{"row":0,"col":3},"anchor":{"row":0,"col":0}}
+{"name":"r-bare","text":"zbc\n","mode":"normal","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":0}}
+{"name":"r-sel-keeps","text":"zzzdef\n","mode":"normal","cursor":{"row":0,"col":2},"anchor":{"row":0,"col":0}}
+{"name":"r-x-replaces-nl","text":"zzzcd\n","mode":"normal","cursor":{"row":0,"col":2},"anchor":{"row":0,"col":0}}
+{"name":"r-cancel-esc","text":"abc\n","mode":"normal","cursor":{"row":0,"col":1},"anchor":{"row":0,"col":1}}
+{"name":"R-inline","text":"alpha lpha beta\n","mode":"normal","cursor":{"row":0,"col":5},"anchor":{"row":0,"col":0}}
+{"name":"R-linewise","text":"one\ntwoone\n","mode":"normal","cursor":{"row":1,"col":6},"anchor":{"row":1,"col":3}}
+{"name":"tilde-sel-keeps","text":"ABC\n","mode":"normal","cursor":{"row":0,"col":2},"anchor":{"row":0,"col":0}}
+{"name":"tilde-bare","text":"ABc\n","mode":"normal","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":0}}
+{"name":"backtick-lower","text":"abc\n","mode":"normal","cursor":{"row":0,"col":2},"anchor":{"row":0,"col":0}}
+{"name":"alt-backtick-upper","text":"ABC\n","mode":"normal","cursor":{"row":0,"col":2},"anchor":{"row":0,"col":0}}
+{"name":"J-cursor-stays","text":"aa bb\ncc\n","mode":"normal","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":0}}
+{"name":"J-sel-lines","text":"aa bb\ncc\ndd\n","mode":"normal","cursor":{"row":0,"col":5},"anchor":{"row":0,"col":0}}
+{"name":"J-ws-collapse","text":"aa bb\n","mode":"normal","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":0}}
+{"name":"J-empty-next-nospace","text":"aa\nbb\n","mode":"normal","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":0}}
+{"name":"J-last-line-eats-nl","text":"aa\nbb","mode":"normal","cursor":{"row":1,"col":0},"anchor":{"row":1,"col":0}}
+{"name":"J-count-ignored","text":"a b\nc\nd\n","mode":"normal","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":0}}
+{"name":"indent-basic","text":" foo\nbar\n","mode":"normal","cursor":{"row":0,"col":4},"anchor":{"row":0,"col":4}}
+{"name":"indent-realign","text":" x\n","mode":"normal","cursor":{"row":0,"col":8},"anchor":{"row":0,"col":8}}
+{"name":"indent-cursor-maps","text":" foo\n","mode":"normal","cursor":{"row":0,"col":6},"anchor":{"row":0,"col":6}}
+{"name":"indent-x-sel","text":" a\n\n b\n","mode":"normal","cursor":{"row":2,"col":5},"anchor":{"row":0,"col":4}}
+{"name":"indent-count","text":" x\n","mode":"normal","cursor":{"row":0,"col":8},"anchor":{"row":0,"col":8}}
+{"name":"unindent-basic","text":" x\n","mode":"normal","cursor":{"row":0,"col":4},"anchor":{"row":0,"col":4}}
+{"name":"unindent-tab","text":"x\n","mode":"normal","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":0}}
+{"name":"unindent-partial","text":"x\n","mode":"normal","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":0}}
+{"name":"unindent-cursor","text":"ab\n","mode":"normal","cursor":{"row":0,"col":1},"anchor":{"row":0,"col":1}}
+{"name":"ctrl-a-sel","text":"16 x\n","mode":"normal","cursor":{"row":0,"col":1},"anchor":{"row":0,"col":0}}
+{"name":"ctrl-a-bare-digit","text":"8\n","mode":"normal","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":0}}
+{"name":"ctrl-a-noop-nonint","text":"a 15 b\n","mode":"normal","cursor":{"row":0,"col":2},"anchor":{"row":0,"col":0}}
+{"name":"ctrl-a-neg-sel","text":"-2\n","mode":"normal","cursor":{"row":0,"col":1},"anchor":{"row":0,"col":0}}
+{"name":"ctrl-a-pad","text":"008\n","mode":"normal","cursor":{"row":0,"col":2},"anchor":{"row":0,"col":0}}
+{"name":"ctrl-a-count","text":"8\n","mode":"normal","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":0}}
+{"name":"ctrl-x-basic","text":"8\n","mode":"normal","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":0}}
+{"name":"ms-sel","text":"(hel)lo\n","mode":"normal","cursor":{"row":0,"col":4},"anchor":{"row":0,"col":0}}
+{"name":"ms-bare","text":"(a)b\n","mode":"normal","cursor":{"row":0,"col":2},"anchor":{"row":0,"col":0}}
+{"name":"ms-quote-self","text":"ab' cd'\n","mode":"normal","cursor":{"row":0,"col":6},"anchor":{"row":0,"col":2}}
+{"name":"md-basic","text":"abcde\n","mode":"normal","cursor":{"row":0,"col":2},"anchor":{"row":0,"col":2}}
+{"name":"md-quote","text":"a bc d\n","mode":"normal","cursor":{"row":0,"col":2},"anchor":{"row":0,"col":2}}
+{"name":"mr-basic","text":"a[bcd]e\n","mode":"normal","cursor":{"row":0,"col":3},"anchor":{"row":0,"col":3}}
+{"name":"mr-to-quote","text":"a \"bc\" d\n","mode":"normal","cursor":{"row":0,"col":3},"anchor":{"row":0,"col":3}}
+{"name":"o-basic","text":"ab\nx\n","mode":"normal","cursor":{"row":1,"col":1},"anchor":{"row":1,"col":1}}
+{"name":"o-copies-indent-level","text":" foo\n \n","mode":"insert","cursor":{"row":1,"col":4},"anchor":{"row":1,"col":4}}
+{"name":"o-partial-indent-drops","text":" foo\n\n","mode":"insert","cursor":{"row":1,"col":0},"anchor":{"row":1,"col":0}}
+{"name":"o-count","text":"ab\n\n\n","mode":"normal","cursor":{"row":1,"col":0},"anchor":{"row":1,"col":0}}
+{"name":"O-basic","text":"aa\nx\nbb\n","mode":"normal","cursor":{"row":1,"col":1},"anchor":{"row":1,"col":1}}
+{"name":"O-copies-indent","text":" \n foo\n","mode":"insert","cursor":{"row":0,"col":4},"anchor":{"row":0,"col":4}}
+{"name":"i-type-esc","text":"hello alpha\n","mode":"normal","cursor":{"row":0,"col":6},"anchor":{"row":0,"col":6}}
+{"name":"i-keeps-sel-start","text":"alpha beta\n","mode":"insert","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":5}}
+{"name":"wa-sel-end","text":"alpha beta\n","mode":"insert","cursor":{"row":0,"col":6},"anchor":{"row":0,"col":0}}
+{"name":"wiX-edit-drops-sel","text":"Xalpha beta\n","mode":"normal","cursor":{"row":0,"col":1},"anchor":{"row":0,"col":6}}
+{"name":"a-esc-restore","text":"abc\n","mode":"normal","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":0}}
+{"name":"ax-restore","text":"axbc\n","mode":"normal","cursor":{"row":0,"col":1},"anchor":{"row":0,"col":0}}
+{"name":"lax-restore","text":"abxc\n","mode":"normal","cursor":{"row":0,"col":2},"anchor":{"row":0,"col":1}}
+{"name":"A-eol","text":"abc\ndef\n","mode":"normal","cursor":{"row":0,"col":3},"anchor":{"row":0,"col":3}}
+{"name":"A-type","text":"one twoX\n","mode":"normal","cursor":{"row":0,"col":8},"anchor":{"row":0,"col":8}}
+{"name":"I-firstnonws","text":" abc\n","mode":"insert","cursor":{"row":0,"col":2},"anchor":{"row":0,"col":2}}
+{"name":"esc-pending-cancel","text":"abc\n","mode":"normal","cursor":{"row":0,"col":1},"anchor":{"row":0,"col":1}}
+{"name":"ins-enter-mid","text":"a\nb\n","mode":"normal","cursor":{"row":0,"col":1},"anchor":{"row":0,"col":0}}
+{"name":"ins-enter-indent","text":" foo\n x\n","mode":"normal","cursor":{"row":1,"col":5},"anchor":{"row":1,"col":5}}
+{"name":"ins-backspace","text":"ac\n","mode":"normal","cursor":{"row":0,"col":1},"anchor":{"row":0,"col":1}}
+{"name":"ins-backspace-join","text":"abcd\n","mode":"normal","cursor":{"row":0,"col":2},"anchor":{"row":0,"col":2}}
+{"name":"ins-ctrl-w","text":"one \n","mode":"normal","cursor":{"row":0,"col":4},"anchor":{"row":0,"col":4}}
+{"name":"ins-ctrl-w-col0-join","text":"cd\n","mode":"normal","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":0}}
+{"name":"ins-alt-bs","text":"one \n","mode":"normal","cursor":{"row":0,"col":4},"anchor":{"row":0,"col":4}}
+{"name":"ins-alt-d","text":" two\n","mode":"normal","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":0}}
+{"name":"ins-alt-del","text":" two\n","mode":"normal","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":0}}
+{"name":"ins-ctrl-u","text":"\n","mode":"normal","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":0}}
+{"name":"ins-ctrl-k","text":"\n","mode":"normal","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":0}}
+{"name":"ins-ctrl-h","text":"ac\n","mode":"normal","cursor":{"row":0,"col":1},"anchor":{"row":0,"col":1}}
+{"name":"ins-ctrl-d","text":"bc\n","mode":"normal","cursor":{"row":0,"col":0},"anchor":{"row":0,"col":0}}
+{"name":"ins-del-eol-join","text":"abcd\n","mode":"normal","cursor":{"row":0,"col":1},"anchor":{"row":0,"col":1}}
+{"name":"ins-ctrl-j","text":"a\nb\n","mode":"normal","cursor":{"row":0,"col":1},"anchor":{"row":0,"col":0}}
+{"name":"ins-home","text":"x abc\n","mode":"normal","cursor":{"row":0,"col":1},"anchor":{"row":0,"col":1}}
+{"name":"ins-end","text":" abcx\n","mode":"normal","cursor":{"row":0,"col":6},"anchor":{"row":0,"col":6}}
+{"name":"ins-pgdn","text":"l00\nl01\nl02\nl03\nl04\nl05\nl06\nl07\nl08\nl09\nl10\nl11\nl12\nl13\nl14\nl15\nl16\nl17\nl18\nl19\nl20\nl21\nl22\nl23\nl24\nl25\nl26\nl27\nl28\nl29\n","mode":"normal","cursor":{"row":25,"col":0},"anchor":{"row":25,"col":0}}
+{"name":"ins-arrows","text":"abc\ndxef\n","mode":"normal","cursor":{"row":1,"col":2},"anchor":{"row":1,"col":2}}
+{"name":"ins-tab-col0","text":" ab\n","mode":"normal","cursor":{"row":0,"col":4},"anchor":{"row":0,"col":4}}
+{"name":"ins-tab-mid","text":"a b\n","mode":"normal","cursor":{"row":0,"col":4},"anchor":{"row":0,"col":4}}
diff --git a/test/hxcases/regen.sh b/test/hxcases/regen.sh
new file mode 100755
index 00000000..308cc9d2
--- /dev/null
+++ b/test/hxcases/regen.sh
@@ -0,0 +1,16 @@
+#!/bin/sh
+# Regenerate test/hxcases/goldens.jsonl by running the helix reference harness
+# over cases.jsonl. The harness is hx-harness on the pardes-harness branch of
+# the genizah helix checkout (see docs/helix-keys.md, "Differential testing"):
+# cd ~/05-genizah/helix && git switch pardes-harness && \
+# cargo build --release -p helix-term --features helix-term/integration --bin hx-harness
+# Override the binary with $HX_HARNESS. helix ignores pane:"tty" — a tty
+# twin's golden is helix on the same text, which IS the oracle for tty
+# navigation parity.
+set -eu
+cd "$(dirname "$0")"
+HX_HARNESS="${HX_HARNESS:-$HOME/05-genizah/helix/target/release/hx-harness}"
+[ -x "$HX_HARNESS" ] || { echo "regen.sh: no hx-harness at $HX_HARNESS (set \$HX_HARNESS)" >&2; exit 1; }
+"$HX_HARNESS" cases.jsonl > goldens.jsonl.tmp
+mv goldens.jsonl.tmp goldens.jsonl
+wc -l goldens.jsonl
diff --git a/test/hxcases/smoke.jsonl b/test/hxcases/smoke.jsonl
new file mode 100644
index 00000000..0c25adda
--- /dev/null
+++ b/test/hxcases/smoke.jsonl
@@ -0,0 +1,20 @@
+{"name":"w-basic","pane":"file","text":"alpha beta\n","keys":"w"}
+{"name":"w-then-b","pane":"file","text":"alpha beta\n","keys":"wb"}
+{"name":"e-basic","pane":"file","text":"alpha beta\n","keys":"e"}
+{"name":"x-line","pane":"file","text":"one\ntwo\nthree\n","keys":"x"}
+{"name":"x-twice","pane":"file","text":"one\ntwo\nthree\n","keys":"xx"}
+{"name":"d-char","pane":"file","text":"alpha beta\n","keys":"d"}
+{"name":"x-then-d","pane":"file","text":"one\ntwo\nthree\n","keys":"xd"}
+{"name":"i-type-esc","pane":"file","text":"alpha\n","keys":"ihello <esc>"}
+{"name":"f-char","pane":"file","text":"alpha beta\n","keys":"ft"}
+{"name":"f-cross-newline","pane":"file","text":"one\ntwo\n","keys":"fw"}
+{"name":"r-char","pane":"file","text":"alpha\n","keys":"rX"}
+{"name":"j-join","pane":"file","text":"one\ntwo\nthree\n","keys":"J"}
+{"name":"semicolon-collapse","pane":"file","text":"alpha beta\n","keys":"w;"}
+{"name":"percent-all","pane":"file","text":"one\ntwo\n","keys":"%"}
+{"name":"mi-paren","pane":"file","text":"fn(a, b)\n","keys":"fami("}
+{"name":"count-2w","pane":"file","text":"one two three four\n","keys":"2w"}
+{"name":"ge-then-gt","pane":"file","text":"l1\nl2\nl3\nl4\nl5\nl6\nl7\nl8\nl9\nl10\nl11\nl12\nl13\nl14\nl15\nl16\nl17\nl18\nl19\nl20\nl21\nl22\nl23\nl24\nl25\nl26\nl27\nl28\nl29\nl30\n","keys":"gegt"}
+{"name":"zt-then-gt","pane":"file","text":"l1\nl2\nl3\nl4\nl5\nl6\nl7\nl8\nl9\nl10\nl11\nl12\nl13\nl14\nl15\nl16\nl17\nl18\nl19\nl20\nl21\nl22\nl23\nl24\nl25\nl26\nl27\nl28\nl29\nl30\n","keys":"12jztgt"}
+{"name":"v-select","pane":"file","text":"alpha beta\n","keys":"vw"}
+{"name":"noop","pane":"file","text":"alpha\n","keys":""}
diff --git a/test/hxcases/waivers.jsonl b/test/hxcases/waivers.jsonl
new file mode 100644
index 00000000..8110a84c
--- /dev/null
+++ b/test/hxcases/waivers.jsonl
@@ -0,0 +1,2 @@
+{"name":"wiX-edit-drops-sel","reason":"helix maps the selection through every insert-mode edit; pardes drops it on the first edit instead (anchor-only divergence, text+cursor+mode match). Re-judged phase 5 and kept: mapping the anchor through all ~23 mutation sites in handleInsert (multi-line inserts, EOL joins, kill runs) is genuinely invasive, and nothing in pardes consumes an implicit selection after insert-mode typing - the acme chords need explicit selections, which never enter insert."}
+{"name":"alt-c-window-op","reason":"deliberate pardes binding: Alt-c is the move-pane-to-fresh-column window op in ANY mode (do-not-touch contract); helix Alt-c is change-noyank. Alt-d + i covers the helix behavior; the buffer is untouched on the pardes side."}
diff --git a/test/hxdiff.zig b/test/hxdiff.zig
new file mode 100644
index 00000000..62638e23
--- /dev/null
+++ b/test/hxdiff.zig
@@ -0,0 +1,355 @@
+// pardes-hxdiff: the pardes half of the helix differential harness. Drives
+// the sans-IO core (src/pardes.zig) headlessly over JSON-Lines cases and
+// prints one contract result line per case on stdout, input order — the same
+// case/result contract helix's hx-harness speaks (pardes-harness branch in
+// the genizah checkout), so the two editors diff key-for-key.
+//
+// case: {"name":"w-basic","pane":"file","text":"alpha beta\n","keys":"w"}
+// result: {"name":"w-basic","text":"alpha beta\n","mode":"normal",
+// "cursor":{"row":0,"col":5},"anchor":{"row":0,"col":0}}
+//
+// zig build hxdiff (diff the checked-in corpus)
+// zig build hxdiff -- <cases.jsonl> (results to stdout, no diff)
+// ./zig-out/bin/pardes-hxdiff <cases.jsonl> [goldens.jsonl [waivers.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.
+//
+// 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
+// (Pardes.hxOpenFileContent, the test-only entry). pane "tty" keeps the
+// tty_only boot shell — no real pty, the spawn effect is ignored — feeds the
+// text as pty output with \n cooked to \r\n (ONLCR), then leaves tty mode
+// with Ctrl-<tty_toggle> exactly like a live session and parks the cursor at
+// (0,0), mirroring the helix harness's initial Selection::single(0,1). The
+// final tty text is the motion surface (paneCursorLines) re-joined, plus the
+// trailing '\n' ghostty's dump trims off the cursor's empty row — the case
+// text always ends in one, so this restores byte-for-byte comparability.
+// Keys are helix notation: printables verbatim, <esc> <ret> <tab> <space>
+// <backspace> <del> <up/down/left/right> <home/end> <pageup/pagedown>
+// <C-x> <A-x> <lt> <gt>. The clipboard round-trips through a fake:
+// set_clipboard stores the core's yank register, get_clipboard answers with
+// Event.paste, so y-then-p behaves like a live session.
+const std = @import("std");
+const libc = std.c;
+const pardes = @import("pardes");
+
+const gpa = std.heap.page_allocator;
+
+pub fn main(init: std.process.Init) !void {
+ var arena_state = std.heap.ArenaAllocator.init(gpa);
+ defer arena_state.deinit();
+ const arena = arena_state.allocator();
+ const args = try init.minimal.args.toSlice(init.arena.allocator());
+
+ var cases_path: ?[]const u8 = null;
+ var goldens_path: ?[]const u8 = null;
+ var waivers_path: ?[]const u8 = null;
+ for (args[1..]) |a| {
+ if (cases_path == null) {
+ cases_path = a;
+ } 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) });
+
+ // goldens + waivers, keyed by name (diff mode)
+ var goldens: std.StringHashMap(Result) = .init(arena);
+ if (goldens_path) |gp| {
+ const gsrc = readFileAlloc(arena, gp) catch |e| fatal("read {s}: {s}", .{ gp, @errorName(e) });
+ var glines = std.mem.splitScalar(u8, gsrc, '\n');
+ var lno: usize = 0;
+ while (glines.next()) |raw| {
+ lno += 1;
+ const line = std.mem.trim(u8, raw, " \t\r");
+ if (line.len == 0) continue;
+ const g = std.json.parseFromSliceLeaky(Result, arena, line, .{ .ignore_unknown_fields = true }) catch |e|
+ fatal("{s}:{d}: bad golden line: {s}", .{ gp, lno, @errorName(e) });
+ goldens.put(g.name, g) catch fatal("oom", .{});
+ }
+ }
+ var waivers: std.StringHashMap([]const u8) = .init(arena);
+ if (waivers_path) |wp| {
+ const wsrc = readFileAlloc(arena, wp) catch |e| fatal("read {s}: {s}", .{ wp, @errorName(e) });
+ var wlines = std.mem.splitScalar(u8, wsrc, '\n');
+ var lno: usize = 0;
+ while (wlines.next()) |raw| {
+ lno += 1;
+ const line = std.mem.trim(u8, raw, " \t\r");
+ if (line.len == 0) continue;
+ const w = std.json.parseFromSliceLeaky(Waiver, arena, line, .{ .ignore_unknown_fields = true }) catch |e|
+ fatal("{s}:{d}: bad waiver line: {s}", .{ wp, lno, @errorName(e) });
+ if (w.reason.len == 0) fatal("{s}:{d}: waiver {s} MUST carry a reason", .{ wp, lno, w.name });
+ waivers.put(w.name, w.reason) catch fatal("oom", .{});
+ }
+ }
+
+ var out: std.ArrayList(u8) = .empty;
+ 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;
+ }
+ n_bad += 1;
+ std.debug.print("MISMATCH {s} (keys: {s})\n{s}", .{ case.name, case.keys, diff });
+ }
+ if (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 });
+ if (n_bad > 0) std.process.exit(1);
+}
+
+const Case = struct {
+ name: []const u8,
+ pane: []const u8 = "file",
+ text: []const u8,
+ keys: []const u8,
+};
+
+const Pos = struct { row: i32, col: i32 };
+
+// field order is the contract's emit order (Stringify writes declaration order)
+const Result = struct {
+ name: []const u8,
+ text: []const u8,
+ mode: []const u8,
+ cursor: Pos,
+ anchor: Pos,
+};
+
+const Waiver = struct {
+ name: []const u8,
+ 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 {
+ 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, .{}) });
+ }
+ if (!std.mem.eql(u8, want.mode, got.mode)) {
+ appendf(arena, &buf, " mode: helix {s} != pardes {s}\n", .{ want.mode, 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) });
+ }
+ 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) });
+ }
+ return buf.items;
+}
+
+fn appendf(arena: std.mem.Allocator, buf: *std.ArrayList(u8), comptime fmt: []const u8, fargs: anytype) void {
+ const s = std.fmt.allocPrint(arena, fmt, fargs) catch return;
+ buf.appendSlice(arena, s) catch {};
+}
+
+fn runCase(arena: std.mem.Allocator, case: Case) !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 });
+ defer core.deinit();
+ var clip: []const u8 = "";
+ core.update(.{ .resize = .{ .cols = 80, .rows = 24 } });
+ pump(core, arena, &clip);
+
+ if (std.mem.eql(u8, case.pane, "file")) {
+ _ = try core.hxOpenFileContent(case.text);
+ } else if (std.mem.eql(u8, case.pane, "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| {
+ if (ch == '\n') try cooked.append(arena, '\r');
+ try cooked.append(arena, ch);
+ }
+ core.update(.{ .output = .{ .pane = 0, .bytes = cooked.items } });
+ pump(core, arena, &clip);
+ // leave tty mode the way a live session does: Ctrl-<tty_toggle>,
+ // then park the cursor at (0,0) — the helix harness's initial
+ // Selection::single(0,1) equivalent (a live exit would leave the
+ // modal cursor tracking the shell cursor below the output)
+ core.update(.{ .key = .{ .cp = core.opts.tty_toggle, .ctrl = true } });
+ pump(core, arena, &clip);
+ if (core.panes[core.active]) |pane| {
+ pane.cur_row = 0;
+ pane.cur_col = 0;
+ pane.cur_pinned = true;
+ }
+ } else fatal("case {s}: unknown pane {s}", .{ case.name, case.pane });
+
+ // keys: helix notation — printables verbatim, <...> specials
+ var ki: usize = 0;
+ while (ki < case.keys.len) {
+ var key: pardes.Key = .{ .cp = 0 };
+ if (case.keys[ki] == '<') {
+ const close = std.mem.indexOfScalarPos(u8, case.keys, ki, '>') orelse
+ fatal("case {s}: unterminated < in keys", .{case.name});
+ var name = case.keys[ki + 1 .. close];
+ ki = close + 1;
+ while (true) {
+ if (std.mem.startsWith(u8, name, "C-")) {
+ key.ctrl = true;
+ name = name[2..];
+ } else if (std.mem.startsWith(u8, name, "A-")) {
+ key.alt = true;
+ name = name[2..];
+ } else break;
+ }
+ if (name.len == 1) {
+ key.cp = name[0];
+ if (!key.ctrl and !key.alt) key.text = name;
+ } else if (std.mem.eql(u8, name, "esc")) {
+ key.cp = pardes.Key.escape;
+ } else if (std.mem.eql(u8, name, "ret")) {
+ key.cp = pardes.Key.enter;
+ } else if (std.mem.eql(u8, name, "tab")) {
+ key.cp = pardes.Key.tab;
+ } else if (std.mem.eql(u8, name, "space")) {
+ key.cp = ' ';
+ if (!key.ctrl and !key.alt) key.text = " ";
+ } else if (std.mem.eql(u8, name, "backspace")) {
+ key.cp = pardes.Key.backspace;
+ } else if (std.mem.eql(u8, name, "del")) {
+ key.cp = pardes.Key.delete;
+ } else if (std.mem.eql(u8, name, "lt")) {
+ key.cp = '<';
+ if (!key.ctrl and !key.alt) key.text = "<";
+ } else if (std.mem.eql(u8, name, "gt")) {
+ key.cp = '>';
+ if (!key.ctrl and !key.alt) key.text = ">";
+ } else if (std.mem.eql(u8, name, "up")) {
+ key.cp = pardes.Key.up;
+ } else if (std.mem.eql(u8, name, "down")) {
+ key.cp = pardes.Key.down;
+ } else if (std.mem.eql(u8, name, "left")) {
+ key.cp = pardes.Key.left;
+ } else if (std.mem.eql(u8, name, "right")) {
+ key.cp = pardes.Key.right;
+ } else if (std.mem.eql(u8, name, "home")) {
+ key.cp = pardes.Key.home;
+ } else if (std.mem.eql(u8, name, "end")) {
+ key.cp = pardes.Key.end;
+ } else if (std.mem.eql(u8, name, "pageup")) {
+ key.cp = pardes.Key.page_up;
+ } else if (std.mem.eql(u8, name, "pagedown")) {
+ key.cp = pardes.Key.page_down;
+ } else fatal("case {s}: unknown key <{s}>", .{ case.name, name });
+ } else {
+ key.cp = case.keys[ki];
+ key.text = case.keys[ki .. ki + 1];
+ ki += 1;
+ }
+ core.update(.{ .key = key });
+ pump(core, arena, &clip);
+ }
+
+ // one contract result line off the active pane's final state
+ const pane = core.panes[core.active] orelse return error.NoActivePane;
+ var text: []const u8 = undefined;
+ if (pane.file) |f| {
+ 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
+ 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 };
+ if (pane.vsel.active) {
+ anchor = .{ .row = pane.vsel.row, .col = pane.vsel.col };
+ } else if (pane.msel.active) {
+ // line selection: the far end (the r0/r1 not holding the cursor), col 0
+ anchor = .{ .row = if (pane.cur_row == pane.msel.r1) pane.msel.r0 else pane.msel.r1, .col = 0 };
+ }
+ // mode "select" ONLY while v-extend is on (helix keeps x/%/mi selections
+ // in normal mode); pane.mode itself never holds a select state
+ const mode = if (pane.mode == .normal and pane.select) "select" else @tagName(pane.mode);
+ return .{
+ .name = case.name,
+ .text = try arena.dupe(u8, text),
+ .mode = mode,
+ .cursor = .{ .row = pane.cur_row, .col = pane.cur_col },
+ .anchor = anchor,
+ };
+}
+
+/// Drain queued effects. The fake clipboard: set_clipboard snapshots the
+/// core's yank register, get_clipboard answers with Event.paste — the async
+/// OSC 52 round trip of a live shell, collapsed to a synchronous reply.
+/// Everything else (spawn, write, resize_pty, ...) is ignored: no real pty.
+fn pump(core: *pardes.Pardes, arena: std.mem.Allocator, clip: *[]const u8) void {
+ while (true) {
+ var want_paste = false;
+ while (core.nextEffect()) |eff| switch (eff) {
+ .set_clipboard => clip.* = if (core.yank) |y| (arena.dupe(u8, y) catch "") else "",
+ .get_clipboard => want_paste = true,
+ else => {},
+ };
+ if (!want_paste) return;
+ core.update(.{ .paste = clip.* });
+ }
+}
+
+fn readFileAlloc(arena: std.mem.Allocator, path: []const u8) ![]u8 {
+ const path_z = try arena.dupeZ(u8, path);
+ const fd = libc.open(path_z, .{ .ACCMODE = .RDONLY });
+ if (fd < 0) return error.OpenFailed;
+ defer _ = libc.close(fd);
+ var buf: std.ArrayList(u8) = .empty;
+ var chunk: [16384]u8 = undefined;
+ while (true) {
+ const n = libc.read(fd, &chunk, chunk.len);
+ if (n < 0) {
+ if (libc.errno(n) == .INTR) continue;
+ return error.ReadFailed;
+ }
+ if (n == 0) break;
+ try buf.appendSlice(arena, chunk[0..@intCast(n)]);
+ }
+ return buf.items;
+}
+
+fn fatal(comptime fmt: []const u8, args: anytype) noreturn {
+ std.debug.print("pardes-hxdiff: " ++ fmt ++ "\n", args);
+ std.process.exit(1);
+}
diff --git a/test/snapshots/fileedit.golden b/test/snapshots/fileedit.golden
index 72950b96..13d6835a 100644
--- a/test/snapshots/fileedit.golden
+++ b/test/snapshots/fileedit.golden
@@ -60,7 +60,7 @@
|
|
|
-== snap undone grid=100x30 cursor=7,3
+== snap undone grid=100x30 cursor=13,3
|Kill Newcol Tutor Debug Colors NextColor Dump
| NOR /tmp/pardes-snap/fileedit/cwd/f.txt Save Del NOR /tmp/pardes-snap/fileedit/cwd Del
| 1 liEDIT''MEne 1
diff --git a/test/snapshots/modal.golden b/test/snapshots/modal.golden
index 668fd02c..dfe34130 100644
--- a/test/snapshots/modal.golden
+++ b/test/snapshots/modal.golden
@@ -29,7 +29,7 @@
|
|
|
-== snap moved grid=100x30 cursor=2,6
+== snap moved grid=100x30 cursor=5,6
|Kill Newcol Tutor Debug Colors NextColor Dump
| NOR /tmp/pardes-snap/modal/cwd Del NOR /tmp/pardes-snap/modal/cwd Del
|
@@ -60,7 +60,7 @@
|
|
|
-== snap xsel grid=100x30 cursor=2,6
+== snap xsel grid=100x30 cursor=15,6
|Kill Newcol Tutor Debug Colors NextColor Dump
| NOR /tmp/pardes-snap/modal/cwd Del NOR /tmp/pardes-snap/modal/cwd Del
|
@@ -98,7 +98,7 @@
|3: 0-1 d,#525252, 2-49 d,d, 50-51 d,#525252, 52-99 d,d,
|4: 0-1 d,#525252, 2-49 d,d, 50-51 d,#525252, 52-99 d,d,
|5: 0-1 d,#525252, 2-49 d,d, 50-51 d,#525252, 52-99 d,d,
-|6: 0-1 d,#525252, 2-49 #d8c8a8,#4a3a1e, 50-51 d,#525252, 52-99 d,d,
+|6: 0-1 d,#525252, 2-15 #d8c8a8,#4a3a1e, 16-49 d,d, 50-51 d,#525252, 52-99 d,d,
|7: 0-1 d,#525252, 2-49 d,d, 50-51 d,#525252, 52-99 d,d,
|8: 0-1 d,#525252, 2-49 d,d, 50-51 d,#525252, 52-99 d,d,
|9: 0-1 d,#525252, 2-49 d,d, 50-51 d,#525252, 52-99 d,d,
@@ -122,7 +122,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 xsel2 grid=100x30 cursor=2,7
+== snap xsel2 grid=100x30 cursor=15,7
|Kill Newcol Tutor Debug Colors NextColor Dump
| NOR /tmp/pardes-snap/modal/cwd Del NOR /tmp/pardes-snap/modal/cwd Del
|
@@ -153,7 +153,7 @@
|
|
|
-== snap yanked grid=100x30 cursor=2,6
+== snap yanked grid=100x30 cursor=15,7
|Kill Newcol Tutor Debug Colors NextColor Dump
| NOR /tmp/pardes-snap/modal/cwd Del NOR /tmp/pardes-snap/modal/cwd Del
|
@@ -184,7 +184,7 @@
|
|
|
-== snap vsel grid=100x30 cursor=3,7
+== snap vsel grid=100x30 cursor=16,8
|Kill Newcol Tutor Debug Colors NextColor Dump
| NOR /tmp/pardes-snap/modal/cwd Del NOR /tmp/pardes-snap/modal/cwd Del
|
@@ -223,8 +223,8 @@
|4: 0-1 d,#525252, 2-49 d,d, 50-51 d,#525252, 52-99 d,d,
|5: 0-1 d,#525252, 2-49 d,d, 50-51 d,#525252, 52-99 d,d,
|6: 0-1 d,#525252, 2-49 #d8c8a8,#4a3a1e, 50-51 d,#525252, 52-99 d,d,
-|7: 0-1 d,#525252, 2-3 #d8c8a8,#4a3a1e, 4-49 d,d, 50-51 d,#525252, 52-99 d,d,
-|8: 0-1 d,#525252, 2-49 d,d, 50-51 d,#525252, 52-99 d,d,
+|7: 0-1 d,#525252, 2-49 #d8c8a8,#4a3a1e, 50-51 d,#525252, 52-99 d,d,
+|8: 0-1 d,#525252, 2-16 #d8c8a8,#4a3a1e, 17-49 d,d, 50-51 d,#525252, 52-99 d,d,
|9: 0-1 d,#525252, 2-49 d,d, 50-51 d,#525252, 52-99 d,d,
|10: 0-1 d,#525252, 2-49 d,d, 50-51 d,#525252, 52-99 d,d,
|11: 0-1 d,#525252, 2-49 d,d, 50-51 d,#525252, 52-99 d,d,
@@ -246,15 +246,15 @@
|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=3,7
+== snap typed grid=100x30 cursor=2,6
|Kill Newcol Tutor Debug Colors NextColor Dump
| NOR /tmp/pardes-snap/modal/cwd Del NOR /tmp/pardes-snap/modal/cwd Del
|
| words.txt words.txt
|
|
-| one two three
-| fTYPEDour five six
+| TYPEDone two three
+| four five six
| seven eight nine
|
|
@@ -277,7 +277,7 @@
|
|
|
-== snap undone grid=100x30 cursor=3,7
+== snap undone grid=100x30 cursor=16,8
|Kill Newcol Tutor Debug Colors NextColor Dump
| NOR /tmp/pardes-snap/modal/cwd Del NOR /tmp/pardes-snap/modal/cwd Del
|
@@ -308,15 +308,15 @@
|
|
|
-== snap redone grid=100x30 cursor=3,7
+== snap redone grid=100x30 cursor=2,6
|Kill Newcol Tutor Debug Colors NextColor Dump
| NOR /tmp/pardes-snap/modal/cwd Del NOR /tmp/pardes-snap/modal/cwd Del
|
| words.txt words.txt
|
|
-| one two three
-| fTYPEDour five six
+| TYPEDone two three
+| four five six
| seven eight nine
|
|
@@ -339,15 +339,15 @@
|
|
|
-== snap bottom grid=100x30 cursor=2,9
+== snap bottom grid=100x30 cursor=2,8
|Kill Newcol Tutor Debug Colors NextColor Dump
| NOR /tmp/pardes-snap/modal/cwd Del NOR /tmp/pardes-snap/modal/cwd Del
|
| words.txt words.txt
|
|
-| one two three
-| fTYPEDour five six
+| TYPEDone two three
+| four five six
| seven eight nine
|
|
diff --git a/test/snapshots/modal.snap b/test/snapshots/modal.snap
index 73905175..68809e18 100644
--- a/test/snapshots/modal.snap
+++ b/test/snapshots/modal.snap
@@ -48,6 +48,6 @@ snap undone
key U
stable 400 5000
snap redone
-key G
+key g e
stable 400 5000
snap bottom
diff --git a/test/snapshots/scrolloff.golden b/test/snapshots/scrolloff.golden
index 90e0e83f..f2165a10 100644
--- a/test/snapshots/scrolloff.golden
+++ b/test/snapshots/scrolloff.golden
@@ -122,7 +122,7 @@
|
|
|
-== snap end grid=100x30 cursor=7,7
+== snap end grid=100x30 cursor=7,6
|Kill Newcol Tutor Debug Colors NextColor Dump
| NOR /tmp/pardes-snap/scrolloff/cwd/g.txt Save De NOR /tmp/pardes-snap/scrolloff/cwd Del
| 36 line 36
@@ -156,12 +156,12 @@
== snap kup grid=100x30 cursor=7,4
|Kill Newcol Tutor Debug Colors NextColor Dump
| NOR /tmp/pardes-snap/scrolloff/cwd/g.txt Save De NOR /tmp/pardes-snap/scrolloff/cwd Del
-| 31 line 31
-| 32 line 32 g.txt
+| 30 line 30
+| 31 line 31 g.txt
+| 32 line 32
| 33 line 33
| 34 line 34
| 35 line 35
-| 36 line 36
| NOR /tmp/pardes-snap/scrolloff/cwd Del
|
| g.txt
diff --git a/test/snapshots/scrolloff.snap b/test/snapshots/scrolloff.snap
index 067e851f..af64a3c7 100644
--- a/test/snapshots/scrolloff.snap
+++ b/test/snapshots/scrolloff.snap
@@ -27,8 +27,9 @@ snap scrolled1
key j j j j
stable 400 5000
snap jdown
-# (d) G: bottom clamp, view stops at EOF-at-bottom, cursor on the bottom row
-key G
+# (d) ge (goto last line; bare G is a helix no-op): bottom clamp, view stops
+# at EOF-at-bottom, cursor on the bottom row
+key g e
stable 400 5000
snap end
# (c) k-walk up: cursor climbs to the top margin, then the view scrolls