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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
|
# 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).
## The mouse
A B1, B2 or B3 sweep selects a stream, as acme's does (libframe
frselect.c:115): from the press to the end of its line, the lines between
whole, the last from its start to the release, the character under the
pointer included. A plain B1 click puts the caret on its cell. A second
plain click at the same cell within half a second is acme's double-click
(plan9port acme text.c:1407, textdoubleclick): just after `{ [ ( < «` or
just before their closers it selects up to the match, nested pairs
counted; at a line's start or end the whole line; just inside `' " \``
the quoted text; anywhere else the word (letters, digits, `_` and any
non-ASCII character). It works in bodies, tags and a terminal in normal
mode; a terminal whose program has the tty gets its own clicks.
A B2 or B3 pressed while B1 holds a sweep is acme's chord: B1-B2 cuts,
B1-B3 pastes over it. Everything done while B1 stays down is one undo
step, as acme marks the file once per B1 hold (text.c:881, textselect), so
B1-B2 then B1-B3 is a copy: the text cut and pasted back, the cut text
left in the register.
A B1 sweep held past a pane's top (on its tag) or bottom (past it, or on
its last row when the pane reaches the screen's bottom, which the pointer
cannot leave) scrolls the body a line a tick, as acme's frselect scrolls,
the sweep's end under the pointer and its start on its text. A sweep that
scrolled ends as the text's own selection, from where it began to where it
ended. B2 and B3 sweeps do not scroll.
|