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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
//! One selection model for everything (0014 wave 2): normal mode is a
//! collapsed selection, visual mode is a stretched one, multicursor is
//! several. Cursor / anchor / extra-cursors used to be three fields that
//! could disagree; the set owns them with the invariants in one place.
/// One selection: the anchor sits, the head moves. Collapsed (equal) is
/// a cursor. Byte offsets, always char boundaries.
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct Selection {
pub anchor: usize,
pub head: usize,
}
impl Selection {
pub fn cursor(at: usize) -> Self {
Self {
anchor: at,
head: at,
}
}
pub fn collapsed(self) -> bool {
self.anchor == self.head
}
/// (start, end) ordered.
pub fn range(self) -> (usize, usize) {
(self.anchor.min(self.head), self.anchor.max(self.head))
}
}
/// The editor's selections: a primary plus zero or more extras.
/// Invariants (enforced by `normalize`): extras sorted, deduped, none
/// equal to the primary's head.
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct SelectionSet {
primary: Selection,
extras: Vec<Selection>,
}
impl Default for SelectionSet {
fn default() -> Self {
Self {
primary: Selection::cursor(0),
extras: Vec::new(),
}
}
}
impl SelectionSet {
pub fn primary(&self) -> Selection {
self.primary
}
/// Every head, primary first (0013 §3 cascade order).
pub fn heads(&self) -> Vec<usize> {
std::iter::once(self.primary.head)
.chain(self.extras.iter().map(|s| s.head))
.collect()
}
pub fn extra_heads(&self) -> &[Selection] {
&self.extras
}
pub fn count(&self) -> usize {
1 + self.extras.len()
}
/// Move the primary head (motions, edits).
pub fn set_head(&mut self, head: usize) {
self.primary.head = head;
}
/// Move head and anchor together (leaving visual mode, plain moves).
pub fn collapse_primary(&mut self, at: usize) {
self.primary = Selection::cursor(at);
}
/// Enter/extend visual: the anchor stays, the head walks.
pub fn stretch_primary(&mut self, anchor: usize, head: usize) {
self.primary = Selection { anchor, head };
}
/// `Q`: drop the extra under the primary, else plant one there.
pub fn toggle_extra(&mut self) {
if let Some(i) = self.extras.iter().position(|s| s.head == self.primary.head) {
self.extras.remove(i);
} else {
self.extras.push(self.primary);
}
self.normalize();
}
/// Plant an extra cursor (both ends at `at`). Never on the
/// primary's head — `Space c` and match-planting skip that spot;
/// stacking on the primary is `Q`'s job (toggle_extra).
pub fn plant_extra(&mut self, at: usize) {
if at != self.primary.head && !self.extras.iter().any(|s| s.head == at) {
self.extras.push(Selection::cursor(at));
}
self.normalize();
}
/// Sorted, deduped. An extra MAY sit on the primary's head — `Q`
/// plants exactly there, then a motion walks them apart (0013).
pub fn normalize(&mut self) {
self.extras.sort_by_key(|s| s.head);
self.extras.dedup();
}
/// Esc: extras die, primary stays.
pub fn collapse_extras(&mut self) {
self.extras.clear();
}
/// Replace the extras wholesale — the motion cascade replants
/// computed heads, and stacked-on-primary extras survive (they were
/// planted by Q on purpose, 0013 §3).
pub fn set_extras(&mut self, heads: impl IntoIterator<Item = usize>) {
self.extras = heads.into_iter().map(Selection::cursor).collect();
self.normalize();
}
/// Plant a real (stretched) extra selection — occurrence selection
/// (0049 §7) keeps anchor/head, unlike the collapsed `Space c`
/// cursor. Skips ranges already owned by the primary or an extra.
pub fn plant_extra_selection(&mut self, anchor: usize, head: usize) {
let selection = Selection { anchor, head };
if selection.range() == self.primary.range()
|| self
.extras
.iter()
.any(|extra| extra.range() == selection.range())
{
return;
}
self.extras.push(selection);
self.normalize();
}
/// Replace the extras wholesale, keeping each anchor/head and
/// direction — occurrence selections are real ranges, so the
/// motion/insert cascades replant full selections where the plain
/// cursor flow replants heads.
pub fn set_extra_selections(&mut self, selections: impl IntoIterator<Item = Selection>) {
self.extras = selections.into_iter().collect();
self.normalize();
}
/// Drop one extra by exact identity (occurrence pop, 0049 §7).
/// Returns the removed selection when one matched.
pub fn remove_extra(&mut self, selection: Selection) -> Option<Selection> {
let at = self.extras.iter().position(|extra| *extra == selection)?;
Some(self.extras.remove(at))
}
/// Map every endpoint in place, preserving selection direction and capacity.
pub fn map_positions(&mut self, mut map: impl FnMut(usize) -> usize) {
self.primary.anchor = map(self.primary.anchor);
self.primary.head = map(self.primary.head);
for selection in &mut self.extras {
selection.anchor = map(selection.anchor);
selection.head = map(selection.head);
}
self.normalize();
}
/// After an edit shifted bytes: remap every head/anchor by delta at
/// a point (the mirrored-edit cascade's bookkeeping).
pub fn remap(&mut self, at: usize, delta: isize) {
let shift = |p: &mut usize| {
if *p >= at {
*p = (*p as isize + delta).max(0) as usize;
}
};
shift(&mut self.primary.head);
shift(&mut self.primary.anchor);
for s in &mut self.extras {
shift(&mut s.head);
shift(&mut s.anchor);
}
self.normalize();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn invariants_hold() {
let mut s = SelectionSet::default();
s.set_head(5);
s.toggle_extra(); // Q plants ON the primary (0013 semantics)
assert_eq!(s.count(), 2);
s.plant_extra(2);
s.plant_extra(2); // dup dies
assert_eq!(s.count(), 3);
assert_eq!(s.heads(), vec![5, 2, 5]);
s.toggle_extra(); // an extra sits under the primary → drops it
assert_eq!(s.count(), 2);
assert_eq!(s.heads(), vec![5, 2]);
s.collapse_extras();
assert_eq!(s.count(), 1);
}
#[test]
fn remap_shifts_past_the_edit() {
let mut s = SelectionSet::default();
s.collapse_primary(10);
s.plant_extra(20);
s.remap(5, 3); // 3 bytes inserted at 5
assert_eq!(s.heads(), vec![13, 23]);
s.remap(5, -3);
assert_eq!(s.heads(), vec![10, 20]);
}
}