Rust · 11277 bytes Raw Blame History
1 /// A position in the buffer (0-indexed)
2 #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
3 pub struct Position {
4 pub line: usize,
5 pub col: usize,
6 }
7
8 impl Position {
9 pub fn new(line: usize, col: usize) -> Self {
10 Self { line, col }
11 }
12 }
13
14 impl PartialOrd for Position {
15 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
16 Some(self.cmp(other))
17 }
18 }
19
20 impl Ord for Position {
21 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
22 match self.line.cmp(&other.line) {
23 std::cmp::Ordering::Equal => self.col.cmp(&other.col),
24 ord => ord,
25 }
26 }
27 }
28
29 /// Selection represented by anchor and cursor positions
30 #[derive(Debug, Clone, Copy, Default)]
31 pub struct Selection {
32 /// Where the selection started
33 pub anchor: Position,
34 /// Current cursor position (end of selection)
35 pub cursor: Position,
36 }
37
38 impl Selection {
39 pub fn new(anchor: Position, cursor: Position) -> Self {
40 Self { anchor, cursor }
41 }
42
43 /// Get the start and end of the selection (ordered)
44 pub fn ordered(&self) -> (Position, Position) {
45 if self.anchor <= self.cursor {
46 (self.anchor, self.cursor)
47 } else {
48 (self.cursor, self.anchor)
49 }
50 }
51
52 /// Check if selection is empty (anchor == cursor)
53 #[allow(dead_code)]
54 pub fn is_empty(&self) -> bool {
55 self.anchor == self.cursor
56 }
57
58 /// Collapse selection to cursor position
59 #[allow(dead_code)]
60 pub fn collapse(&mut self) {
61 self.anchor = self.cursor;
62 }
63 }
64
65 /// Cursor with selection support
66 #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
67 pub struct Cursor {
68 pub line: usize,
69 pub col: usize,
70 /// Desired column for vertical movement
71 pub desired_col: usize,
72 /// Selection anchor (if different from cursor, there's a selection)
73 pub anchor_line: usize,
74 pub anchor_col: usize,
75 /// Whether selection mode is active
76 pub selecting: bool,
77 }
78
79 impl Cursor {
80 pub fn new() -> Self {
81 Self::default()
82 }
83
84 pub fn at(line: usize, col: usize) -> Self {
85 Self {
86 line,
87 col,
88 desired_col: col,
89 anchor_line: line,
90 anchor_col: col,
91 selecting: false,
92 }
93 }
94
95 pub fn position(&self) -> Position {
96 Position::new(self.line, self.col)
97 }
98
99 pub fn anchor(&self) -> Position {
100 Position::new(self.anchor_line, self.anchor_col)
101 }
102
103 pub fn selection(&self) -> Selection {
104 Selection::new(self.anchor(), self.position())
105 }
106
107 pub fn has_selection(&self) -> bool {
108 self.selecting && (self.line != self.anchor_line || self.col != self.anchor_col)
109 }
110
111 /// Get ordered selection bounds (start, end)
112 pub fn selection_bounds(&self) -> Option<(Position, Position)> {
113 if self.has_selection() {
114 Some(self.selection().ordered())
115 } else {
116 None
117 }
118 }
119
120 /// Start selection at current position
121 pub fn start_selection(&mut self) {
122 self.anchor_line = self.line;
123 self.anchor_col = self.col;
124 self.selecting = true;
125 }
126
127 /// Clear selection
128 pub fn clear_selection(&mut self) {
129 self.selecting = false;
130 self.anchor_line = self.line;
131 self.anchor_col = self.col;
132 }
133
134 /// Move cursor, extending selection if in selection mode
135 pub fn move_to(&mut self, line: usize, col: usize, extend_selection: bool) {
136 if extend_selection && !self.selecting {
137 self.start_selection();
138 } else if !extend_selection && self.selecting {
139 self.clear_selection();
140 }
141 self.line = line;
142 self.col = col;
143 if !extend_selection {
144 self.anchor_line = line;
145 self.anchor_col = col;
146 }
147 }
148
149 /// Set position and update desired column
150 #[allow(dead_code)]
151 pub fn set(&mut self, line: usize, col: usize) {
152 self.line = line;
153 self.col = col;
154 self.desired_col = col;
155 self.clear_selection();
156 }
157 }
158
159 /// Multi-cursor container - manages a set of cursors
160 #[derive(Debug, Clone, Default)]
161 #[allow(dead_code)]
162 pub struct Cursors {
163 /// All cursors, sorted by position (primary cursor is always index 0)
164 cursors: Vec<Cursor>,
165 /// Index of the primary cursor (receives special treatment in some operations)
166 primary: usize,
167 }
168
169 #[allow(dead_code)]
170 impl Cursors {
171 pub fn new() -> Self {
172 Self {
173 cursors: vec![Cursor::new()],
174 primary: 0,
175 }
176 }
177
178 /// Create a Cursors container from a single cursor
179 pub fn from_cursor(cursor: Cursor) -> Self {
180 Self {
181 cursors: vec![cursor],
182 primary: 0,
183 }
184 }
185
186 /// Get the primary cursor
187 pub fn primary(&self) -> &Cursor {
188 &self.cursors[self.primary]
189 }
190
191 /// Get mutable reference to primary cursor
192 pub fn primary_mut(&mut self) -> &mut Cursor {
193 &mut self.cursors[self.primary]
194 }
195
196 /// Get all cursors
197 pub fn all(&self) -> &[Cursor] {
198 &self.cursors
199 }
200
201 /// Get mutable references to all cursors
202 pub fn all_mut(&mut self) -> &mut [Cursor] {
203 &mut self.cursors
204 }
205
206 /// Number of cursors
207 pub fn len(&self) -> usize {
208 self.cursors.len()
209 }
210
211 /// Check if we have only one cursor
212 pub fn is_single(&self) -> bool {
213 self.cursors.len() == 1
214 }
215
216 /// Get the index of the primary cursor
217 pub fn primary_index(&self) -> usize {
218 self.primary
219 }
220
221 /// Add a new cursor at the given position
222 /// Returns true if cursor was added, false if position already has a cursor
223 pub fn add(&mut self, line: usize, col: usize) -> bool {
224 let new_cursor = Cursor::at(line, col);
225
226 // Check for duplicates
227 if self.cursors.iter().any(|c| c.line == line && c.col == col) {
228 return false;
229 }
230
231 self.cursors.push(new_cursor);
232 self.sort_and_dedupe();
233 true
234 }
235
236 /// Remove cursor at the given position if it exists
237 /// Returns true if a cursor was removed
238 /// Won't remove the last cursor
239 pub fn remove_at(&mut self, line: usize, col: usize) -> bool {
240 if self.cursors.len() <= 1 {
241 return false; // Don't remove the last cursor
242 }
243
244 if let Some(idx) = self.cursors.iter().position(|c| c.line == line && c.col == col) {
245 self.cursors.remove(idx);
246 // Adjust primary index if needed
247 if self.primary >= self.cursors.len() {
248 self.primary = self.cursors.len() - 1;
249 } else if self.primary > idx {
250 self.primary -= 1;
251 }
252 return true;
253 }
254 false
255 }
256
257 /// Toggle cursor at position: add if not present, remove if present
258 /// Returns true if cursor was added, false if removed
259 pub fn toggle_at(&mut self, line: usize, col: usize) -> bool {
260 if self.cursors.iter().any(|c| c.line == line && c.col == col) {
261 self.remove_at(line, col);
262 false
263 } else {
264 self.add(line, col);
265 true
266 }
267 }
268
269 /// Add a cursor with selection
270 pub fn add_with_selection(&mut self, line: usize, col: usize, anchor_line: usize, anchor_col: usize) -> bool {
271 // Check for duplicates at cursor position
272 if self.cursors.iter().any(|c| c.line == line && c.col == col) {
273 return false;
274 }
275
276 let mut new_cursor = Cursor::at(line, col);
277 new_cursor.anchor_line = anchor_line;
278 new_cursor.anchor_col = anchor_col;
279 new_cursor.selecting = true;
280
281 self.cursors.push(new_cursor);
282 self.sort_and_dedupe();
283 true
284 }
285
286 /// Remove secondary cursors, keeping only the primary
287 pub fn collapse_to_primary(&mut self) {
288 let primary = self.cursors[self.primary].clone();
289 self.cursors.clear();
290 self.cursors.push(primary);
291 self.primary = 0;
292 }
293
294 /// Remove cursor at the given index
295 pub fn remove(&mut self, index: usize) {
296 if self.cursors.len() > 1 && index < self.cursors.len() {
297 self.cursors.remove(index);
298 if self.primary >= self.cursors.len() {
299 self.primary = self.cursors.len() - 1;
300 }
301 }
302 }
303
304 /// Sort cursors by position and remove duplicates
305 pub fn sort_and_dedupe(&mut self) {
306 // Remember primary cursor's position
307 let primary_pos = (self.cursors[self.primary].line, self.cursors[self.primary].col);
308
309 // Sort by line, then by column
310 self.cursors.sort_by(|a, b| {
311 match a.line.cmp(&b.line) {
312 std::cmp::Ordering::Equal => a.col.cmp(&b.col),
313 ord => ord,
314 }
315 });
316
317 // Remove duplicates (same position)
318 self.cursors.dedup_by(|a, b| a.line == b.line && a.col == b.col);
319
320 // Find primary cursor's new index
321 self.primary = self.cursors.iter()
322 .position(|c| c.line == primary_pos.0 && c.col == primary_pos.1)
323 .unwrap_or(0);
324 }
325
326 /// Apply a function to all cursors
327 pub fn for_each<F: FnMut(&mut Cursor)>(&mut self, mut f: F) {
328 for cursor in &mut self.cursors {
329 f(cursor);
330 }
331 }
332
333 /// Get iterator over cursors sorted by position (bottom to top for editing)
334 /// This ordering is important for edits that change buffer positions
335 pub fn iter_for_edit(&self) -> impl Iterator<Item = (usize, &Cursor)> {
336 // Return indices with cursors, sorted from bottom-right to top-left
337 let mut indices: Vec<usize> = (0..self.cursors.len()).collect();
338 indices.sort_by(|&a, &b| {
339 let ca = &self.cursors[a];
340 let cb = &self.cursors[b];
341 match cb.line.cmp(&ca.line) {
342 std::cmp::Ordering::Equal => cb.col.cmp(&ca.col),
343 ord => ord,
344 }
345 });
346 indices.into_iter().map(move |i| (i, &self.cursors[i]))
347 }
348
349 /// Clear selection on all cursors
350 pub fn clear_selections(&mut self) {
351 for cursor in &mut self.cursors {
352 cursor.clear_selection();
353 }
354 }
355
356 /// Check if any cursor has a selection
357 pub fn has_selection(&self) -> bool {
358 self.cursors.iter().any(|c| c.has_selection())
359 }
360
361 /// Get selection bounds for primary cursor
362 pub fn selection_bounds(&self) -> Option<(Position, Position)> {
363 self.primary().selection_bounds()
364 }
365
366 /// Merge overlapping selections and cursors at same position
367 pub fn merge_overlapping(&mut self) {
368 self.sort_and_dedupe();
369 // TODO: Merge overlapping selections (for now, just dedupe)
370 }
371
372 /// Set cursors from a list of positions (for undo/redo)
373 /// Primary cursor becomes the first position in the list
374 pub fn set_from_positions(&mut self, positions: &[Position]) {
375 if positions.is_empty() {
376 return;
377 }
378
379 self.cursors.clear();
380 for pos in positions {
381 self.cursors.push(Cursor::at(pos.line, pos.col));
382 }
383 self.primary = 0;
384 self.sort_and_dedupe();
385 }
386 }
387