summaryrefslogtreecommitdiff
path: root/docs/selections.md
blob: 5cc1df1adf37e153aef17144cb0623f986ae5526 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# 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.
"helix" here is the reference build docs/helix-keys.md names: `hx-harness`
at `694e7dfdd`, upstream `278b24389`.

## 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 others, up to `memory.limits.selections` in all (1024 on the
desktop, 64 on the board).

- 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]`, in room allocated when a second range
  appears and given back when the selection is one range again (a column's
  or the workspace's tag allocates from `Text.gpa`, any other text from its
  pane's), 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. `restore_cursor` says the insert session
  began with `a`, so Esc gives each range back the character it was
  stretched by.

`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 `max_selections`. `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), `<n>o`/`<n>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.
- `K`/`Alt-K` keep or drop the ranges a regex matches in, on the `s` prompt;
  `Alt-:` turns every range forward.
- `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`).

## Insert mode

Each range lives on through insert mode. Every insert-mode edit happens at
the cursor, so `edit.insertKey` runs it on the bare cursor and then carries
the range through what it did to the text, with helix's `Range::map` rules:
at an insertion, a backward range's head and an empty range move past it
and a forward range's head stays before it, so typing slides an `i` range
and stretches an `a` range. Enter slides or stretches the range by what the
cursor moved; an arrow key collapses it. The other ranges get the same by
the replay.

## 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.
- Registers (`Registers.zig`) hold one value per range. A yank under the
  replay fills slot `Pardes.multi_index` of `multi_count`, and `p`, `P`, `R`
  and insert `Ctrl-r` read slot i at range i, the last value standing in
  for missing ones. `"<reg>` names the register (`Pardes.register` for the
  length of one command); `_ # . % /` are computed or special and `+`/`*`
  is the system clipboard. The acme chords and a paste into a terminal read
  the default register joined by newlines. The clipboard's read is an
  answer from the shell that comes later, so `"+p` pastes once, at the
  primary, and insert `Ctrl-r +` types what pardes last put on the
  clipboard rather than asking the desktop.
- `Alt-.` repeats the last `f`/`t`/`F`/`T`. `.` and macros (`Q`/`q`) are
  keys typed again (`Macro.zig`), so whatever those keys do to the ranges
  happens again: there is no edit log underneath.

## Where it differs from helix

- 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 `memory.limits.selections` ranges (1024 on the desktop, 64
  on the board); 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).