floem_editor_core/
paragraph.rs

1use lapce_xi_rope::{Cursor, Rope, RopeInfo};
2
3/// Describe char classifications used to compose word boundaries
4#[derive(Copy, Clone, PartialEq, Eq)]
5pub enum CharClassification {
6    /// Carriage Return (`r`)
7    Cr,
8    /// Line feed (`\n`)
9    Lf,
10    /// Includes letters and all of non-ascii unicode
11    Other,
12}
13
14/// A word boundary can be the start of a word, its end or both for punctuation
15#[derive(PartialEq, Eq)]
16enum ParagraphBoundary {
17    /// Denote that this is not a boundary
18    Interior,
19    /// A boundary indicating the end of a new-line sequence
20    Start,
21    /// A boundary indicating the start of a new-line sequence
22    End,
23    /// Both start and end boundaries (when we have only one empty
24    /// line)
25    Both,
26}
27
28impl ParagraphBoundary {
29    fn is_start(&self) -> bool {
30        *self == ParagraphBoundary::Start || *self == ParagraphBoundary::Both
31    }
32
33    fn is_end(&self) -> bool {
34        *self == ParagraphBoundary::End || *self == ParagraphBoundary::Both
35    }
36
37    #[allow(unused)]
38    fn is_boundary(&self) -> bool {
39        *self != ParagraphBoundary::Interior
40    }
41}
42
43/// A cursor providing utility function to navigate the rope
44/// by parahraphs boundaries.
45/// Boundaries can be the start of a word, its end, punctuation etc.
46pub struct ParagraphCursor<'a> {
47    pub(crate) inner: Cursor<'a, RopeInfo>,
48}
49
50impl<'a> ParagraphCursor<'a> {
51    pub fn new(text: &'a Rope, pos: usize) -> ParagraphCursor<'a> {
52        let inner = Cursor::new(text, pos);
53        ParagraphCursor { inner }
54    }
55
56    pub fn prev_boundary(&mut self) -> Option<usize> {
57        if let (Some(ch1), Some(ch2), Some(ch3)) = (
58            self.inner.prev_codepoint(),
59            self.inner.prev_codepoint(),
60            self.inner.prev_codepoint(),
61        ) {
62            let mut prop1 = get_char_property(ch1);
63            let mut prop2 = get_char_property(ch2);
64            let mut prop3 = get_char_property(ch3);
65            let mut candidate = self.inner.pos();
66
67            while let Some(prev) = self.inner.prev_codepoint() {
68                let prop_prev = get_char_property(prev);
69                if classify_boundary(prop_prev, prop3, prop2, prop1).is_start() {
70                    break;
71                }
72                (prop3, prop2, prop1) = (prop_prev, prop3, prop2);
73                candidate = self.inner.pos();
74            }
75
76            self.inner.set(candidate + 1);
77            return Some(candidate + 1);
78        }
79
80        None
81    }
82
83    pub fn next_boundary(&mut self) -> Option<usize> {
84        if let (Some(ch1), Some(ch2), Some(ch3)) = (
85            self.inner.next_codepoint(),
86            self.inner.next_codepoint(),
87            self.inner.next_codepoint(),
88        ) {
89            let mut prop1 = get_char_property(ch1);
90            let mut prop2 = get_char_property(ch2);
91            let mut prop3 = get_char_property(ch3);
92            let mut candidate = self.inner.pos();
93
94            while let Some(next) = self.inner.next_codepoint() {
95                let prop_next = get_char_property(next);
96                if classify_boundary(prop1, prop2, prop3, prop_next).is_end() {
97                    break;
98                }
99
100                (prop1, prop2, prop3) = (prop2, prop3, prop_next);
101                candidate = self.inner.pos();
102            }
103            self.inner.set(candidate - 1);
104            return Some(candidate - 1);
105        }
106        None
107    }
108}
109
110/// Return the [`CharClassification`] of the input character
111pub fn get_char_property(codepoint: char) -> CharClassification {
112    match codepoint {
113        '\r' => CharClassification::Cr,
114        '\n' => CharClassification::Lf,
115        _ => CharClassification::Other,
116    }
117}
118
119fn classify_boundary(
120    before_prev: CharClassification,
121    prev: CharClassification,
122    next: CharClassification,
123    after_next: CharClassification,
124) -> ParagraphBoundary {
125    use self::{CharClassification::*, ParagraphBoundary::*};
126
127    match (before_prev, prev, next, after_next) {
128        (Other, Lf, Lf, Other) => Both,
129        (_, Lf, Lf, Other) => Start,
130        (Lf, Cr, Lf, Other) => Start,
131        (Other, Lf, Lf, _) => End,
132        (Other, Cr, Lf, Cr) => End,
133        _ => Interior,
134    }
135}