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