floem_editor_core/
cursor.rs

1use lapce_xi_rope::{RopeDelta, Transformer};
2#[cfg(feature = "serde")]
3use serde::{Deserialize, Serialize};
4
5use crate::{
6    buffer::{rope_text::RopeText, Buffer},
7    mode::{Mode, MotionMode, VisualMode},
8    register::RegisterData,
9    selection::{InsertDrift, SelRegion, Selection},
10};
11
12#[derive(Clone, Copy, PartialEq, Debug)]
13#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
14pub enum ColPosition {
15    FirstNonBlank,
16    Start,
17    End,
18    Col(f64),
19}
20
21#[derive(Clone, Debug, PartialEq)]
22#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
23pub struct Cursor {
24    pub mode: CursorMode,
25    pub horiz: Option<ColPosition>,
26    pub motion_mode: Option<MotionMode>,
27    pub history_selections: Vec<Selection>,
28}
29
30#[derive(Clone, Debug, PartialEq)]
31#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
32pub enum CursorMode {
33    Normal {
34        offset: usize,
35        affinity: CursorAffinity,
36    },
37    Visual {
38        start: usize,
39        end: usize,
40        mode: VisualMode,
41        affinity: CursorAffinity,
42    },
43    Insert(Selection),
44}
45
46struct RegionsIter<'c> {
47    cursor_mode: &'c CursorMode,
48    idx: usize,
49}
50
51impl Iterator for RegionsIter<'_> {
52    type Item = (usize, usize, CursorAffinity);
53
54    fn next(&mut self) -> Option<Self::Item> {
55        match self.cursor_mode {
56            &CursorMode::Normal { offset, affinity } => (self.idx == 0).then(|| {
57                self.idx = 1;
58                (offset, offset, affinity)
59            }),
60            &CursorMode::Visual {
61                start,
62                end,
63                affinity,
64                ..
65            } => (self.idx == 0).then(|| {
66                self.idx = 1;
67                (start, end, affinity)
68            }),
69            CursorMode::Insert(selection) => {
70                let next = selection.regions().get(self.idx).map(
71                    |&SelRegion {
72                         start,
73                         end,
74                         affinity,
75                         ..
76                     }| (start, end, affinity),
77                );
78
79                if next.is_some() {
80                    self.idx += 1;
81                }
82
83                next
84            }
85        }
86    }
87
88    fn size_hint(&self) -> (usize, Option<usize>) {
89        let total_len = match self.cursor_mode {
90            CursorMode::Normal { .. } | CursorMode::Visual { .. } => 1,
91            CursorMode::Insert(selection) => selection.len(),
92        };
93        let len = total_len - self.idx;
94
95        (len, Some(len))
96    }
97}
98
99impl ExactSizeIterator for RegionsIter<'_> {}
100
101impl CursorMode {
102    pub fn offset(&self) -> usize {
103        match &self {
104            CursorMode::Normal { offset, .. } => *offset,
105            CursorMode::Visual { end, .. } => *end,
106            CursorMode::Insert(selection) => selection.get_cursor_offset(),
107        }
108    }
109
110    pub fn start_offset(&self) -> usize {
111        match &self {
112            CursorMode::Normal { offset, .. } => *offset,
113            CursorMode::Visual { start, .. } => *start,
114            CursorMode::Insert(selection) => selection.first().map(|s| s.start).unwrap_or(0),
115        }
116    }
117
118    pub fn affinity(&self) -> CursorAffinity {
119        match &self {
120            CursorMode::Normal { affinity, .. } => *affinity,
121            CursorMode::Visual { affinity, .. } => *affinity,
122            CursorMode::Insert(selection) => selection.get_cursor_affinity(),
123        }
124    }
125
126    pub fn regions_iter(
127        &self,
128    ) -> impl ExactSizeIterator<Item = (usize, usize, CursorAffinity)> + '_ {
129        RegionsIter {
130            cursor_mode: self,
131            idx: 0,
132        }
133    }
134}
135
136/// Decides how the cursor should be placed around special areas of text.  
137/// Ex:
138/// ```rust,ignore
139/// let j =            // soft linewrap
140/// 1 + 2 + 3;
141/// ```
142/// where `let j = ` has the issue that there's two positions you might want your cursor to be:  
143/// `let j = |` or `|1 + 2 + 3;`  
144/// These are the same offset in the text, but it feels more natural to have it move in a certain
145/// way.  
146/// If you're at `let j =| ` and you press the right-arrow key, then it uses your backwards
147/// affinity to keep you on the line at `let j = |`.  
148/// If you're at `1| + 2 + 3;` and you press the left-arrow key, then it uses your forwards affinity
149/// to keep you on the line at `|1 + 2 + 3;`.  
150///
151/// For other special text, like inlay hints, this can also apply.  
152/// ```rust,ignore
153/// let j<: String> = ...
154/// ```
155/// where `<: String>` is our inlay hint, then  
156/// `let |j<: String> =` and you press the right-arrow key, then it uses your backwards affinity to
157/// keep you on the same side of the hint, `let j|<: String>`.  
158/// `let j<: String> |=` and you press the right-arrow key, then it uses your forwards affinity to
159/// keep you on the same side of the hint, `let j<: String>| =`.
160#[derive(Debug, Clone, Copy, PartialEq, Eq)]
161#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
162pub enum CursorAffinity {
163    /// `<: String>|`
164    Forward,
165    /// `|<: String>`
166    Backward,
167}
168impl CursorAffinity {
169    pub fn invert(&self) -> Self {
170        match self {
171            CursorAffinity::Forward => CursorAffinity::Backward,
172            CursorAffinity::Backward => CursorAffinity::Forward,
173        }
174    }
175}
176
177impl Cursor {
178    pub fn new(
179        mode: CursorMode,
180        horiz: Option<ColPosition>,
181        motion_mode: Option<MotionMode>,
182    ) -> Self {
183        Self {
184            mode,
185            horiz,
186            motion_mode,
187            history_selections: Vec::new(),
188        }
189    }
190
191    pub fn origin(modal: bool) -> Self {
192        Self::new(
193            if modal {
194                CursorMode::Normal {
195                    offset: 0,
196                    affinity: CursorAffinity::Backward,
197                }
198            } else {
199                CursorMode::Insert(Selection::caret(0, CursorAffinity::Backward))
200            },
201            None,
202            None,
203        )
204    }
205
206    pub fn offset(&self) -> usize {
207        self.mode.offset()
208    }
209
210    pub fn start_offset(&self) -> usize {
211        self.mode.start_offset()
212    }
213
214    pub fn affinity(&self) -> CursorAffinity {
215        self.mode.affinity()
216    }
217
218    pub fn set_latest_affinity(&mut self, affinity: CursorAffinity) {
219        match &mut self.mode {
220            CursorMode::Normal { affinity: aff, .. } => {
221                *aff = affinity;
222            }
223            CursorMode::Visual { affinity: aff, .. } => {
224                *aff = affinity;
225            }
226            CursorMode::Insert(selection) => {
227                if let Some(region) = selection.last_inserted_mut() {
228                    region.affinity = affinity;
229                }
230            }
231        }
232    }
233
234    pub fn regions_iter(
235        &self,
236    ) -> impl ExactSizeIterator<Item = (usize, usize, CursorAffinity)> + '_ {
237        self.mode.regions_iter()
238    }
239
240    pub fn is_normal(&self) -> bool {
241        matches!(&self.mode, CursorMode::Normal { .. })
242    }
243
244    pub fn is_insert(&self) -> bool {
245        matches!(&self.mode, CursorMode::Insert(_))
246    }
247
248    pub fn is_visual(&self) -> bool {
249        matches!(&self.mode, CursorMode::Visual { .. })
250    }
251
252    pub fn get_mode(&self) -> Mode {
253        match &self.mode {
254            CursorMode::Normal { .. } => Mode::Normal,
255            CursorMode::Visual { mode, .. } => Mode::Visual(*mode),
256            CursorMode::Insert(_) => Mode::Insert,
257        }
258    }
259
260    pub fn set_mode(&mut self, mode: CursorMode) {
261        if let CursorMode::Insert(selection) = &self.mode {
262            self.history_selections.push(selection.clone());
263        }
264        self.mode = mode;
265    }
266
267    pub fn set_insert(&mut self, selection: Selection) {
268        self.set_mode(CursorMode::Insert(selection));
269    }
270
271    pub fn update_selection(&mut self, buffer: &Buffer, selection: Selection) {
272        match self.mode {
273            CursorMode::Normal { .. } | CursorMode::Visual { .. } => {
274                let offset = selection.min_offset();
275                let offset = buffer.offset_line_end(offset, false).min(offset);
276                self.mode = CursorMode::Normal {
277                    offset,
278                    affinity: CursorAffinity::Forward,
279                };
280            }
281            CursorMode::Insert(_) => {
282                self.mode = CursorMode::Insert(selection);
283            }
284        }
285    }
286
287    pub fn edit_selection(&self, text: &impl RopeText) -> Selection {
288        match &self.mode {
289            CursorMode::Insert(selection) => selection.clone(),
290            CursorMode::Normal { offset, .. } => Selection::region(
291                *offset,
292                text.next_grapheme_offset(*offset, 1, text.len()),
293                CursorAffinity::Backward,
294            ),
295            CursorMode::Visual {
296                start, end, mode, ..
297            } => match mode {
298                VisualMode::Normal => Selection::region(
299                    *start.min(end),
300                    text.next_grapheme_offset(*start.max(end), 1, text.len()),
301                    CursorAffinity::Backward,
302                ),
303                VisualMode::Linewise => {
304                    let start_offset = text.offset_of_line(text.line_of_offset(*start.min(end)));
305                    let end_offset = text.offset_of_line(text.line_of_offset(*start.max(end)) + 1);
306                    Selection::region(start_offset, end_offset, CursorAffinity::Backward)
307                }
308                VisualMode::Blockwise => {
309                    let mut selection = Selection::new();
310                    let (start_line, start_col) = text.offset_to_line_col(*start.min(end));
311                    let (end_line, end_col) = text.offset_to_line_col(*start.max(end));
312                    let left = start_col.min(end_col);
313                    let right = start_col.max(end_col) + 1;
314                    for line in start_line..end_line + 1 {
315                        let max_col = text.line_end_col(line, true);
316                        if left > max_col {
317                            continue;
318                        }
319                        let right = match &self.horiz {
320                            Some(ColPosition::End) => max_col,
321                            _ => {
322                                if right > max_col {
323                                    max_col
324                                } else {
325                                    right
326                                }
327                            }
328                        };
329                        let left = text.offset_of_line_col(line, left);
330                        let right = text.offset_of_line_col(line, right);
331                        selection.add_region(SelRegion::new(
332                            left,
333                            right,
334                            CursorAffinity::Backward,
335                            None,
336                        ));
337                    }
338                    selection
339                }
340            },
341        }
342    }
343
344    pub fn apply_delta(&mut self, delta: &RopeDelta) {
345        match &self.mode {
346            CursorMode::Normal { offset, affinity } => {
347                let mut transformer = Transformer::new(delta);
348                let new_offset = transformer.transform(*offset, true);
349                self.mode = CursorMode::Normal {
350                    offset: new_offset,
351                    affinity: *affinity,
352                };
353            }
354            CursorMode::Visual {
355                start,
356                end,
357                mode,
358                affinity,
359            } => {
360                let mut transformer = Transformer::new(delta);
361                let start = transformer.transform(*start, false);
362                let end = transformer.transform(*end, true);
363                self.mode = CursorMode::Visual {
364                    start,
365                    end,
366                    mode: *mode,
367                    affinity: *affinity,
368                };
369            }
370            CursorMode::Insert(selection) => {
371                let selection = selection.apply_delta(delta, true, InsertDrift::Default);
372                self.mode = CursorMode::Insert(selection);
373            }
374        }
375        self.horiz = None;
376    }
377
378    pub fn yank(&self, text: &impl RopeText) -> RegisterData {
379        let (content, mode) = match &self.mode {
380            CursorMode::Insert(selection) => {
381                let mut mode = VisualMode::Normal;
382                let mut content = "".to_string();
383                for region in selection.regions() {
384                    let region_content = if region.is_caret() {
385                        mode = VisualMode::Linewise;
386                        let line = text.line_of_offset(region.start);
387                        text.line_content(line)
388                    } else {
389                        text.slice_to_cow(region.min()..region.max())
390                    };
391                    if content.is_empty() {
392                        content = region_content.to_string();
393                    } else if content.ends_with('\n') {
394                        content += &region_content;
395                    } else {
396                        content += "\n";
397                        content += &region_content;
398                    }
399                }
400                (content, mode)
401            }
402            CursorMode::Normal { offset, .. } => {
403                let new_offset = text.next_grapheme_offset(*offset, 1, text.len());
404                (
405                    text.slice_to_cow(*offset..new_offset).to_string(),
406                    VisualMode::Normal,
407                )
408            }
409            CursorMode::Visual {
410                start, end, mode, ..
411            } => match mode {
412                VisualMode::Normal => (
413                    text.slice_to_cow(
414                        *start.min(end)..text.next_grapheme_offset(*start.max(end), 1, text.len()),
415                    )
416                    .to_string(),
417                    VisualMode::Normal,
418                ),
419                VisualMode::Linewise => {
420                    let start_offset = text.offset_of_line(text.line_of_offset(*start.min(end)));
421                    let end_offset = text.offset_of_line(text.line_of_offset(*start.max(end)) + 1);
422                    (
423                        text.slice_to_cow(start_offset..end_offset).to_string(),
424                        VisualMode::Linewise,
425                    )
426                }
427                VisualMode::Blockwise => {
428                    let mut lines = Vec::new();
429                    let (start_line, start_col) = text.offset_to_line_col(*start.min(end));
430                    let (end_line, end_col) = text.offset_to_line_col(*start.max(end));
431                    let left = start_col.min(end_col);
432                    let right = start_col.max(end_col) + 1;
433                    for line in start_line..end_line + 1 {
434                        let max_col = text.line_end_col(line, true);
435                        if left > max_col {
436                            lines.push("".to_string());
437                        } else {
438                            let right = match &self.horiz {
439                                Some(ColPosition::End) => max_col,
440                                _ => {
441                                    if right > max_col {
442                                        max_col
443                                    } else {
444                                        right
445                                    }
446                                }
447                            };
448                            let left = text.offset_of_line_col(line, left);
449                            let right = text.offset_of_line_col(line, right);
450                            lines.push(text.slice_to_cow(left..right).to_string());
451                        }
452                    }
453                    (lines.join("\n") + "\n", VisualMode::Blockwise)
454                }
455            },
456        };
457        RegisterData { content, mode }
458    }
459
460    /// Return the current selection start and end position for a
461    /// Single cursor selection
462    pub fn get_selection(&self) -> Option<(usize, usize)> {
463        match &self.mode {
464            CursorMode::Visual { start, end, .. } => Some((*start, *end)),
465            CursorMode::Insert(selection) => selection
466                .regions()
467                .first()
468                .map(|region| (region.start, region.end)),
469            _ => None,
470        }
471    }
472
473    pub fn get_line_col_char(&self, buffer: &Buffer) -> Option<(usize, usize, usize)> {
474        match &self.mode {
475            CursorMode::Normal { offset, .. } => {
476                let ln_col = buffer.offset_to_line_col(*offset);
477                Some((ln_col.0, ln_col.1, *offset))
478            }
479            CursorMode::Visual { start, end, .. } => {
480                let v = buffer.offset_to_line_col(*start.min(end));
481                Some((v.0, v.1, *start))
482            }
483            CursorMode::Insert(selection) => {
484                if selection.regions().len() > 1 {
485                    return None;
486                }
487
488                let x = selection.regions().first().unwrap();
489                let v = buffer.offset_to_line_col(x.start);
490
491                Some((v.0, v.1, x.start))
492            }
493        }
494    }
495
496    pub fn get_selection_count(&self) -> usize {
497        match &self.mode {
498            CursorMode::Insert(selection) => selection.regions().len(),
499            _ => 0,
500        }
501    }
502
503    pub fn set_offset(
504        &mut self,
505        offset: usize,
506        affinity: CursorAffinity,
507        modify: bool,
508        new_cursor: bool,
509    ) {
510        match &self.mode {
511            CursorMode::Normal {
512                offset: old_offset, ..
513            } => {
514                if modify && *old_offset != offset {
515                    self.mode = CursorMode::Visual {
516                        start: *old_offset,
517                        end: offset,
518                        mode: VisualMode::Normal,
519                        affinity,
520                    };
521                } else {
522                    self.mode = CursorMode::Normal { offset, affinity };
523                }
524            }
525            CursorMode::Visual { start, .. } => {
526                if modify {
527                    self.mode = CursorMode::Visual {
528                        start: *start,
529                        end: offset,
530                        mode: VisualMode::Normal,
531                        affinity,
532                    };
533                } else {
534                    self.mode = CursorMode::Normal { offset, affinity };
535                }
536            }
537            CursorMode::Insert(selection) => {
538                if new_cursor {
539                    let mut new_selection = selection.clone();
540                    if modify {
541                        if let Some(mut region) = new_selection.last_inserted().cloned() {
542                            region.end = offset;
543                            region.affinity = affinity;
544
545                            // remove overlapping selections
546                            new_selection.delete_range(region.min(), region.max());
547
548                            // remove carets on the edges
549                            let left = region.min().saturating_sub(1);
550                            let right = region.max() + 1;
551                            let neighbors = new_selection.regions_in_range(left, right);
552                            let left_has_caret = neighbors.first().is_some_and(|r| r.is_caret());
553                            let right_has_caret = neighbors.last().is_some_and(|r| r.is_caret());
554
555                            if region.is_caret() || left_has_caret {
556                                new_selection.delete_range(left, left + 2);
557                            }
558
559                            if region.is_caret() || right_has_caret {
560                                new_selection.delete_range(left + 1, right);
561                            }
562
563                            new_selection.add_region(region);
564                        } else {
565                            new_selection.add_region(SelRegion::caret(offset, affinity));
566                        }
567                        self.set_insert(new_selection);
568                    } else {
569                        let mut new_selection = selection.clone();
570                        new_selection.add_region(SelRegion::caret(offset, affinity));
571                        self.set_insert(new_selection);
572                    }
573                } else if modify {
574                    let mut new_selection = Selection::new();
575                    if let Some(region) = selection.first() {
576                        let new_region = SelRegion::new(region.start, offset, affinity, None);
577                        new_selection.add_region(new_region);
578                    } else {
579                        new_selection.add_region(SelRegion::new(offset, offset, affinity, None));
580                    }
581                    self.set_insert(new_selection);
582                } else {
583                    self.set_insert(Selection::caret(offset, affinity));
584                }
585            }
586        }
587    }
588
589    pub fn add_region(
590        &mut self,
591        start: usize,
592        end: usize,
593        affinity: CursorAffinity,
594        modify: bool,
595        new_cursor: bool,
596    ) {
597        match &self.mode {
598            CursorMode::Normal { .. } => {
599                self.mode = CursorMode::Visual {
600                    start,
601                    end: end - 1,
602                    mode: VisualMode::Normal,
603                    affinity,
604                };
605            }
606            CursorMode::Visual {
607                start: old_start,
608                end: old_end,
609                ..
610            } => {
611                let forward = old_end >= old_start;
612                let new_start = (*old_start).min(*old_end).min(start).min(end - 1);
613                let new_end = (*old_start).max(*old_end).max(start).max(end - 1);
614                let (new_start, new_end) = if forward {
615                    (new_start, new_end)
616                } else {
617                    (new_end, new_start)
618                };
619                self.mode = CursorMode::Visual {
620                    start: new_start,
621                    end: new_end,
622                    mode: VisualMode::Normal,
623                    affinity,
624                };
625            }
626            CursorMode::Insert(selection) => {
627                let new_selection = if new_cursor {
628                    let mut new_selection = selection.clone();
629                    if modify {
630                        let new_region = if let Some(last_inserted) = selection.last_inserted() {
631                            last_inserted.merge_with(SelRegion::new(start, end, affinity, None))
632                        } else {
633                            SelRegion::new(start, end, affinity, None)
634                        };
635                        new_selection.replace_last_inserted_region(new_region);
636                    } else {
637                        new_selection.add_region(SelRegion::new(start, end, affinity, None));
638                    }
639                    new_selection
640                } else if modify {
641                    let mut new_selection = selection.clone();
642                    new_selection.add_region(SelRegion::new(start, end, affinity, None));
643                    new_selection
644                } else {
645                    Selection::region(start, end, affinity)
646                };
647                self.mode = CursorMode::Insert(new_selection);
648            }
649        }
650    }
651}
652
653pub fn get_first_selection_after(
654    cursor: &Cursor,
655    buffer: &Buffer,
656    delta: &RopeDelta,
657) -> Option<Cursor> {
658    let mut transformer = Transformer::new(delta);
659
660    let offset = cursor.offset();
661    let offset = transformer.transform(offset, false);
662    let (ins, del) = delta.clone().factor();
663    let ins = ins.transform_shrink(&del);
664    for el in ins.els.iter() {
665        match el {
666            lapce_xi_rope::DeltaElement::Copy(b, e) => {
667                // if b == e, ins.inserted_subset() will panic
668                if b == e {
669                    return None;
670                }
671            }
672            lapce_xi_rope::DeltaElement::Insert(_) => {}
673        }
674    }
675
676    // TODO it's silly to store the whole thing in memory, we only need the first element.
677    let mut positions = ins
678        .inserted_subset()
679        .complement_iter()
680        .map(|s| s.1)
681        .collect::<Vec<usize>>();
682    positions.append(
683        &mut del
684            .complement_iter()
685            .map(|s| transformer.transform(s.1, false))
686            .collect::<Vec<usize>>(),
687    );
688    positions.sort_by_key(|p| {
689        let p = *p as i32 - offset as i32;
690        if p > 0 {
691            p as usize
692        } else {
693            -p as usize
694        }
695    });
696
697    positions
698        .first()
699        .cloned()
700        .map(|offset| Selection::caret(offset, CursorAffinity::Forward))
701        .map(|selection| {
702            let cursor_mode = match cursor.mode {
703                CursorMode::Normal { .. } | CursorMode::Visual { .. } => {
704                    let offset = selection.min_offset();
705                    let offset = buffer.offset_line_end(offset, false).min(offset);
706                    CursorMode::Normal {
707                        offset,
708                        affinity: CursorAffinity::Backward,
709                    }
710                }
711                CursorMode::Insert(_) => CursorMode::Insert(selection),
712            };
713
714            Cursor::new(cursor_mode, None, None)
715        })
716}