Skip to main content

fresh/model/
cursor.rs

1use crate::model::event::CursorId;
2use std::collections::HashMap;
3use std::ops::Range;
4
5/// Selection mode for cursors
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
7pub enum SelectionMode {
8    /// Normal character-wise selection (stream)
9    #[default]
10    Normal,
11    /// Block/rectangular selection (column-wise)
12    Block,
13}
14
15/// Position in 2D coordinates (for block selection)
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub struct Position2D {
18    pub line: usize,
19    pub column: usize,
20}
21
22/// A cursor in the buffer with optional selection
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub struct Cursor {
25    /// Primary position (where edits happen) - byte offset
26    pub position: usize,
27
28    /// Selection anchor (if any) for visual selection - byte offset
29    pub anchor: Option<usize>,
30
31    /// Desired column for vertical navigation
32    /// When moving up/down, try to stay in this column
33    pub sticky_column: usize,
34
35    /// Selection mode (normal or block)
36    pub selection_mode: SelectionMode,
37
38    /// Block selection anchor position (line, column) for rectangular selections
39    /// Only used when selection_mode is Block
40    pub block_anchor: Option<Position2D>,
41
42    /// Whether regular movement should clear the selection (default: true)
43    /// When false (e.g., after set_mark in Emacs mode), movement preserves the anchor
44    pub deselect_on_move: bool,
45}
46
47impl Cursor {
48    /// Create a new cursor at a position
49    pub fn new(position: usize) -> Self {
50        Self {
51            position,
52            anchor: None,
53            sticky_column: 0,
54            selection_mode: SelectionMode::Normal,
55            block_anchor: None,
56            deselect_on_move: true, // Default: movement clears selection
57        }
58    }
59
60    /// Create a cursor with a selection
61    pub fn with_selection(start: usize, end: usize) -> Self {
62        Self {
63            position: end,
64            anchor: Some(start),
65            sticky_column: 0,
66            selection_mode: SelectionMode::Normal,
67            block_anchor: None,
68            deselect_on_move: true, // Default: movement clears selection
69        }
70    }
71
72    /// Is the cursor collapsed (no selection)?
73    pub fn collapsed(&self) -> bool {
74        self.anchor.is_none() && self.block_anchor.is_none()
75    }
76
77    /// Get the selection range, if any (for normal selection)
78    pub fn selection_range(&self) -> Option<Range<usize>> {
79        self.anchor.map(|anchor| {
80            if anchor < self.position {
81                anchor..self.position
82            } else {
83                self.position..anchor
84            }
85        })
86    }
87
88    /// Check if this cursor has a block selection
89    pub fn has_block_selection(&self) -> bool {
90        self.selection_mode == SelectionMode::Block && self.block_anchor.is_some()
91    }
92
93    /// Get the block selection bounds (start_line, start_col, end_line, end_col)
94    /// Returns None if not in block selection mode
95    pub fn block_selection_bounds(&self) -> Option<(usize, usize, usize, usize)> {
96        if self.selection_mode != SelectionMode::Block {
97            return None;
98        }
99        self.block_anchor.map(|anchor| {
100            // We need current position as 2D coords, which requires buffer context
101            // For now, just return the anchor info
102            // The actual current position will be computed by the caller using the buffer
103            (anchor.line, anchor.column, anchor.line, anchor.column)
104        })
105    }
106
107    /// Get the start of the selection (min of position and anchor)
108    pub fn selection_start(&self) -> usize {
109        self.anchor.map_or(self.position, |a| a.min(self.position))
110    }
111
112    /// Get the end of the selection (max of position and anchor)
113    pub fn selection_end(&self) -> usize {
114        self.anchor.map_or(self.position, |a| a.max(self.position))
115    }
116
117    /// Clear the selection, keeping only the position
118    pub fn clear_selection(&mut self) {
119        self.anchor = None;
120        self.block_anchor = None;
121        self.selection_mode = SelectionMode::Normal;
122    }
123
124    /// Set the selection anchor
125    pub fn set_anchor(&mut self, anchor: usize) {
126        self.anchor = Some(anchor);
127    }
128
129    /// Start a block selection at the given 2D position
130    pub fn start_block_selection(&mut self, line: usize, column: usize) {
131        self.selection_mode = SelectionMode::Block;
132        self.block_anchor = Some(Position2D { line, column });
133        // Also set the byte anchor for compatibility
134        // (will need to be set properly by caller with buffer context)
135    }
136
137    /// Clear block selection and return to normal mode
138    pub fn clear_block_selection(&mut self) {
139        self.selection_mode = SelectionMode::Normal;
140        self.block_anchor = None;
141    }
142
143    /// Move to a position, optionally extending selection
144    pub fn move_to(&mut self, position: usize, extend_selection: bool) {
145        if extend_selection {
146            if self.anchor.is_none() {
147                self.anchor = Some(self.position);
148            }
149        } else {
150            self.anchor = None;
151            // Also clear block selection when not extending
152            if !extend_selection && self.selection_mode == SelectionMode::Block {
153                self.selection_mode = SelectionMode::Normal;
154                self.block_anchor = None;
155            }
156        }
157        self.position = position;
158    }
159
160    /// Adjust cursor position after an edit
161    /// If an edit happens before the cursor, adjust position accordingly
162    pub fn adjust_for_edit(&mut self, edit_pos: usize, old_len: usize, new_len: usize) {
163        let delta = new_len as isize - old_len as isize;
164
165        if edit_pos <= self.position {
166            if edit_pos + old_len <= self.position {
167                // Edit is completely before cursor
168                self.position = (self.position as isize + delta).max(0) as usize;
169            } else {
170                // Edit overlaps cursor position - move to end of edit
171                self.position = edit_pos + new_len;
172            }
173        }
174
175        // Adjust anchor similarly
176        if let Some(anchor) = self.anchor {
177            if edit_pos <= anchor {
178                if edit_pos + old_len <= anchor {
179                    self.anchor = Some((anchor as isize + delta).max(0) as usize);
180                } else {
181                    self.anchor = Some(edit_pos + new_len);
182                }
183            }
184        }
185    }
186}
187
188/// Collection of cursors with multi-cursor support
189#[derive(Debug, Clone)]
190pub struct Cursors {
191    /// Map from cursor ID to cursor
192    cursors: HashMap<CursorId, Cursor>,
193
194    /// Next available cursor ID
195    next_id: usize,
196
197    /// Primary cursor ID (the most recently added/active one)
198    primary_id: CursorId,
199}
200
201impl Cursors {
202    /// Create a new cursor collection with one cursor at position 0
203    pub fn new() -> Self {
204        let primary_id = CursorId(0);
205        let mut cursors = HashMap::new();
206        cursors.insert(primary_id, Cursor::new(0));
207
208        Self {
209            cursors,
210            next_id: 1,
211            primary_id,
212        }
213    }
214
215    /// Get the primary cursor
216    pub fn primary(&self) -> &Cursor {
217        self.cursors
218            .get(&self.primary_id)
219            .expect("Primary cursor should always exist")
220    }
221
222    /// Get the primary cursor mutably
223    pub fn primary_mut(&mut self) -> &mut Cursor {
224        self.cursors
225            .get_mut(&self.primary_id)
226            .expect("Primary cursor should always exist")
227    }
228
229    /// Get the primary cursor ID
230    pub fn primary_id(&self) -> CursorId {
231        self.primary_id
232    }
233
234    /// Get a cursor by ID
235    pub fn get(&self, id: CursorId) -> Option<&Cursor> {
236        self.cursors.get(&id)
237    }
238
239    /// Get a cursor by ID mutably
240    pub fn get_mut(&mut self, id: CursorId) -> Option<&mut Cursor> {
241        self.cursors.get_mut(&id)
242    }
243
244    /// Add a new cursor and return its ID
245    pub fn add(&mut self, cursor: Cursor) -> CursorId {
246        let id = CursorId(self.next_id);
247        self.next_id += 1;
248        self.cursors.insert(id, cursor);
249        self.primary_id = id; // New cursor becomes primary
250        id
251    }
252
253    /// Insert a cursor with a specific ID (for undo/redo)
254    pub fn insert_with_id(&mut self, id: CursorId, cursor: Cursor) {
255        self.cursors.insert(id, cursor);
256        self.primary_id = id; // New cursor becomes primary
257                              // Update next_id if necessary to avoid collisions
258        if id.0 >= self.next_id {
259            self.next_id = id.0 + 1;
260        }
261    }
262
263    /// Remove a cursor by ID
264    pub fn remove(&mut self, id: CursorId) -> Option<Cursor> {
265        // Can't remove the last cursor
266        if self.cursors.len() <= 1 {
267            return None;
268        }
269
270        let cursor = self.cursors.remove(&id);
271
272        // If we removed the primary cursor, pick a new primary
273        if id == self.primary_id {
274            self.primary_id = *self
275                .cursors
276                .keys()
277                .next()
278                .expect("Should have at least one cursor remaining");
279        }
280
281        cursor
282    }
283
284    /// Remove all cursors except the first one (original cursor)
285    /// When exiting multi-cursor mode, return to the original cursor position
286    pub fn remove_secondary(&mut self) {
287        // Find the cursor with the minimum ID (the original/first cursor)
288        let first_id = *self
289            .cursors
290            .keys()
291            .min_by_key(|id| id.0)
292            .expect("Should have at least one cursor");
293        let first_cursor = *self
294            .cursors
295            .get(&first_id)
296            .expect("First cursor should exist");
297
298        self.cursors.clear();
299        self.cursors.insert(first_id, first_cursor);
300        self.primary_id = first_id; // Update primary to be the first cursor
301    }
302
303    /// Get all cursor IDs
304    pub fn ids(&self) -> Vec<CursorId> {
305        self.cursors.keys().copied().collect()
306    }
307
308    /// Get all cursors as a slice
309    pub fn iter(&self) -> impl Iterator<Item = (CursorId, &Cursor)> {
310        self.cursors.iter().map(|(id, c)| (*id, c))
311    }
312
313    /// Get number of cursors
314    pub fn count(&self) -> usize {
315        self.cursors.len()
316    }
317
318    /// Apply a function to all cursors
319    pub fn map<F>(&mut self, mut f: F)
320    where
321        F: FnMut(&mut Cursor),
322    {
323        for cursor in self.cursors.values_mut() {
324            f(cursor);
325        }
326    }
327
328    /// Adjust all cursors after an edit
329    pub fn adjust_for_edit(&mut self, edit_pos: usize, old_len: usize, new_len: usize) {
330        for cursor in self.cursors.values_mut() {
331            cursor.adjust_for_edit(edit_pos, old_len, new_len);
332        }
333    }
334
335    /// Normalize cursors: merge overlapping selections, remove duplicates
336    pub fn normalize(&mut self) {
337        // Collect all cursors sorted by position
338        let mut cursor_list: Vec<(CursorId, Cursor)> =
339            self.cursors.iter().map(|(id, c)| (*id, *c)).collect();
340
341        cursor_list.sort_by_key(|(_, c)| c.selection_start());
342
343        // Remove exact duplicates
344        cursor_list.dedup_by(|(_, a), (_, b)| a.position == b.position && a.anchor == b.anchor);
345
346        // Rebuild cursors map
347        self.cursors.clear();
348        for (id, cursor) in cursor_list {
349            self.cursors.insert(id, cursor);
350        }
351
352        // Ensure primary cursor still exists
353        if !self.cursors.contains_key(&self.primary_id) {
354            if let Some(id) = self.cursors.keys().next() {
355                self.primary_id = *id;
356            }
357        }
358    }
359
360    /// Get all cursor positions (for rendering)
361    pub fn positions(&self) -> Vec<usize> {
362        self.cursors.values().map(|c| c.position).collect()
363    }
364
365    /// Get all selection ranges (for rendering)
366    pub fn selections(&self) -> Vec<Range<usize>> {
367        self.cursors
368            .values()
369            .filter_map(|c| c.selection_range())
370            .collect()
371    }
372}
373
374impl Default for Cursors {
375    fn default() -> Self {
376        Self::new()
377    }
378}
379
380#[cfg(test)]
381mod tests {
382    use super::*;
383
384    #[test]
385    fn test_cursor_new() {
386        let cursor = Cursor::new(5);
387        assert_eq!(cursor.position, 5);
388        assert!(cursor.collapsed());
389        assert_eq!(cursor.selection_range(), None);
390    }
391
392    #[test]
393    fn test_cursor_with_selection() {
394        let cursor = Cursor::with_selection(5, 10);
395        assert_eq!(cursor.position, 10);
396        assert!(!cursor.collapsed());
397        assert_eq!(cursor.selection_range(), Some(5..10));
398    }
399
400    #[test]
401    fn test_cursor_move_to() {
402        let mut cursor = Cursor::new(5);
403        cursor.move_to(10, false);
404        assert_eq!(cursor.position, 10);
405        assert!(cursor.collapsed());
406
407        cursor.move_to(15, true);
408        assert_eq!(cursor.position, 15);
409        assert_eq!(cursor.selection_range(), Some(10..15));
410    }
411
412    #[test]
413    fn test_cursor_adjust_for_edit() {
414        let mut cursor = Cursor::new(10);
415
416        // Edit before cursor
417        cursor.adjust_for_edit(5, 0, 3);
418        assert_eq!(cursor.position, 13);
419
420        // Edit after cursor
421        cursor.adjust_for_edit(20, 5, 2);
422        assert_eq!(cursor.position, 13);
423    }
424
425    #[test]
426    fn test_cursors_new() {
427        let cursors = Cursors::new();
428        assert_eq!(cursors.count(), 1);
429        assert_eq!(cursors.primary().position, 0);
430    }
431
432    #[test]
433    fn test_cursors_add_remove() {
434        let mut cursors = Cursors::new();
435        let id = cursors.add(Cursor::new(10));
436        assert_eq!(cursors.count(), 2);
437        assert_eq!(cursors.get(id).unwrap().position, 10);
438
439        cursors.remove(id);
440        assert_eq!(cursors.count(), 1);
441    }
442
443    #[test]
444    fn test_cursors_remove_secondary() {
445        let mut cursors = Cursors::new();
446        cursors.add(Cursor::new(10));
447        cursors.add(Cursor::new(20));
448        assert_eq!(cursors.count(), 3);
449
450        cursors.remove_secondary();
451        assert_eq!(cursors.count(), 1);
452    }
453
454    #[test]
455    fn test_cursors_normalize() {
456        let mut cursors = Cursors::new();
457        cursors.add(Cursor::new(10));
458        cursors.add(Cursor::new(10)); // Duplicate
459
460        cursors.normalize();
461        assert_eq!(cursors.count(), 2); // Duplicates removed
462    }
463}