floem_editor_core/
paragraph.rs1use lapce_xi_rope::{Cursor, Rope, RopeInfo};
2
3#[derive(Copy, Clone, PartialEq, Eq)]
5pub enum CharClassification {
6 Cr,
8 Lf,
10 Other,
12}
13
14#[derive(PartialEq, Eq)]
16enum ParagraphBoundary {
17 Interior,
19 Start,
21 End,
23 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
43pub 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
110pub 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}