From 05ca060fc91930f0e931f39dcf56729ec71543dd Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Mon, 28 Sep 2026 15:26:39 -0300 Subject: Every helix-golf example is a differential case, one per step of its walkthrough The ten examples on helix-golf are imported as 118 cases: each numbered step of an example's walkthrough is the case whose keys are the command up to and including that step, so the first mismatch names the step where pardes and helix part. Six more restart at a step that begins with %, from helix's own text there, so a later step is tested even when an earlier one misses. zig build hxgolf compares all 124 with goldens from hx-harness. Co-Authored-By: Claude Opus 5.5 --- README.md | 1 + 1 file changed, 1 insertion(+) (limited to 'README.md') diff --git a/README.md b/README.md index 8899d68a..c82bd310 100644 --- a/README.md +++ b/README.md @@ -129,6 +129,7 @@ zig build hxdiff-test comparator, allocation and CLI regression checks zig build hxdiff-live compare against a freshly run hx-harness zig build hxdiff-update update reference results only after comparison passes zig build hxparity file-pane vs pty-pane editing parity +zig build hxgolf every helix-golf example, step by step, against helix zig build mupdf-check compile, link, render and search docs/design.pdf zig build web-snap browser highlighting and touch interactions zig build web-driver-test browser driver timeouts and cleanup -- cgit v1.3 From 68b0fe7600987587ed0c27cf38a3fca8216c45b4 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Mon, 28 Sep 2026 15:40:03 -0300 Subject: docs/selections.md says how normal mode holds its selections The primary and the others, cells against helix's gaps, how setRanges normalizes, which commands replay per range and which make one edit, and what undo, the register and repeat do with several ranges, with the places helix differs listed at the end. Co-Authored-By: Claude Opus 5.5 --- README.md | 1 + docs/selections.md | 103 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+) create mode 100644 docs/selections.md (limited to 'README.md') diff --git a/README.md b/README.md index c82bd310..3e7a96f2 100644 --- a/README.md +++ b/README.md @@ -229,6 +229,7 @@ sketch; `docs/design.pdf` is a retained rendering/search fixture and may lag it. | `docs/detached.md` | one core, many frontends, over a unix socket | | `docs/config.md` | build options and runtime configuration | | `docs/tags.md` | editable workspace, column and pane tags; filename drafts | +| `docs/selections.md` | the normal-mode selection model and how it differs from helix's | | `docs/fs.md` | default 9P service, Look resolution, and named mounts | | `docs/lsp.md` | in-process ZLS and external language servers | | `docs/lsp-evaluation.md` | why that backend, measured against the alternatives | diff --git a/docs/selections.md b/docs/selections.md new file mode 100644 index 00000000..5b992cc4 --- /dev/null +++ b/docs/selections.md @@ -0,0 +1,103 @@ +# Selections + +How normal mode holds and changes its selections, and where that differs from +helix's `Selection` (a list of ranges over gap offsets, with a primary index). +Key-by-key behaviour is in docs/helix-keys.md; this is the model underneath. + +## What a selection is + +Every `Text` (src/Text.zig) has its own: a pane's body, its tag, a prompt's +answer, a column's or the workspace's tag. A selection is one primary range +plus up to 63 others. + +- The PRIMARY is where the cursor always was: `cur_row`/`cur_col` is the + head, `vsel.row`/`vsel.col` the anchor while `vsel.active`, and a bare + cursor (inactive `vsel`) is the one-character range under it. `msel` is + the older whole-line form (`r0`..`r1`), kept for search-result highlights. +- The others are `sels[0..nsel]`, each a `SelRange` of head and anchor plus + its own `j`/`k` goal column, in document order. The primary is not in the + list; `Text.ranges` slots it in and returns its index. +- Ends are CELLS, the characters a block cursor sits on, where helix's are + gaps between characters. `Text.cellRange`, `cellOffRange` and `rangeCells` + convert. A range covers at least one character, as in helix (min width 1), + so there are no empty ranges. +- `vsel.explicit` says the selection was made on purpose (`v`, `x`, `%`, + `s`, `n`/`N`, ...) rather than left behind by a motion. Only pardes reads + it: the look (Enter) and execute (Tab) chords act on explicit selections + and on the word under the cursor otherwise. +- `select` is `v` extend mode. `append_at` remembers where an `a` began, so + Esc can give back the range it appended over. + +`Text.setRanges` is the one writer of a whole selection: it sorts by start, +merges ranges that overlap or share a start (helix `normalize`), follows the +primary through the merges, and keeps the first 64. `setRange` writes one +range and drops the others. + +## Making, splitting, merging, rotating, removing + +- Made several by `C`/`Alt-C` (copy to the next/previous line), `s` (every + match inside each range), `S` (the pieces between matches), `Alt-s` (one + per line), `o`/`O`, and `Alt-J` (the spaces a join put in). `s`/`S` + re-run on every keystroke from the selection the prompt opened on + (`Pane.sel_snap`); Esc is that snapshot restored. +- `Alt-minus` merges all into one, `Alt-_` the consecutive ones, `_` trims + whitespace (dropping all-blank ranges). +- `)`/`(` rotate which range is primary; `Alt-)`/`Alt-(` rotate the TEXT + between ranges, the primary moving with its text. +- `,` keeps only the primary, `Alt-,` removes it (the next range takes over). +- After `s`, `S` and `Alt-s` the primary is the first range, which is + helix's behaviour (its own TODO), not a choice. + +## How a command reaches every range + +A parsed action has a scope (`modal.Normal.Action.scope`). + +- `per_selection` (motions, `d`, `c`, `y`, `p`, `r`, `~`, `>`, `ms`, ...): + `normal.replaySels` runs the ONE-range handler once per range, last range + first, each pass seeing a single selection. A pass's result is stored as + distances from the END of the text, which an edit at an earlier range + cannot move; `setRanges` then puts them back together. The mode, prefix + and select state that survive are the primary's pass. Insert-mode keys are + replayed the same way. +- `once` (the `multi` family, `s`/`S`, `J`/`Alt-J`, `Alt-(`/`Alt-)`, `&`, + `Ctrl-c`, undo/redo, builtins, search): one function takes `Text.ranges`, + makes one edit and hands the mapped ranges to `setRanges`, the way a helix + command builds one transaction. +- Anything that reaches outside the text (a builtin, a language query, a + look) runs once from the primary and drops back to one cursor + (`normal.multiOnce`). + +## Undo, registers, repeat + +- One keystroke is one undo step however many ranges it edited: only the + first replay pass records (`pushUndo`). +- An undo snapshot (`File.Snapshot`) holds the content and the PRIMARY + range. Undo puts the primary back and leaves the other ranges where they + were; helix restores the whole selection of that revision. +- There is ONE register, `Pardes.yank`, written by `y`, `d` and `c` and read + by `p`, `P` and `R`. A yank at several ranges joins their texts with + newlines into it, and a paste puts that whole value at every range. +- `Alt-.` repeats the last `f`/`t`/`F`/`T`. There is no `.` and there are no + macros. + +## Where it differs from helix + +- Registers: helix keeps one value PER RANGE and pastes value `i` at range + `i`; it has named registers (`"a`), special ones (`#` range indices, + `.` the selection, `/` the search, `_` the black hole) and inserts a + register in insert mode with `Ctrl-r`. None exist here (waiver + `msel-yank-paste`). +- Insert mode: helix maps every range through each keystroke's edit, so a + range an `i`/`a` started from stretches over what is typed and keeps its + anchor. Here only the cursor cells move, and after Esc only the primary's + `append_at` span comes back (waivers `wiX-edit-drops-sel`, `msel-append`). +- Direction of a one-character range: both of its cells are the same cell, + so it has none. helix can flip one (`Alt-;`) and the commands that read + the head (`&`, the next extend) see the difference. +- Count: at most 64 ranges; matches past that are dropped silently. +- `n`/`N` walk the look ring (acme), not regex search hits, and there is no + `*`; helix-golf's `*`, `""N` and `n` do not apply. +- `s`/`S` match as sam does (docs/helix-keys.md, "Regex selection"): each + line is its own haystack, so `^`/`$` hold at every line and a class like + `\s` does not reach the newline unless the pattern names `\n`. +- Undo restores only the primary (above). -- cgit v1.3