Skip to main content

tui_lipan/text/
edit.rs

1use std::marker::PhantomData;
2
3use std::sync::Arc;
4
5use undo::{Edit, Merged};
6
7pub(crate) trait TextTarget {
8    fn with_parts_mut<R>(
9        &mut self,
10        f: impl FnOnce(&mut String, &mut usize, &mut Option<usize>) -> R,
11    ) -> R;
12}
13
14/// The kind of text edit that occurred.
15#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
16pub enum TextEditKind {
17    /// Inserted text at the cursor.
18    Insert,
19    /// Deleted text using backspace.
20    DeleteBackspace,
21    /// Deleted text using forward delete.
22    DeleteForward,
23    /// Replaced a selection or range with new text.
24    Replace,
25}
26
27#[derive(Clone, Copy, Debug, PartialEq, Eq)]
28enum InsertGroup {
29    Whitespace,
30    NonWhitespace,
31}
32
33fn insert_group_for_char(ch: char) -> Option<InsertGroup> {
34    if ch == '\n' || ch == '\r' {
35        return None;
36    }
37    if ch.is_whitespace() {
38        return Some(InsertGroup::Whitespace);
39    }
40    Some(InsertGroup::NonWhitespace)
41}
42
43fn insert_group_last(text: &str) -> Option<InsertGroup> {
44    text.chars().last().and_then(insert_group_for_char)
45}
46
47fn insert_group_single(text: &str) -> Option<InsertGroup> {
48    let mut chars = text.chars();
49    let ch = chars.next()?;
50    if chars.next().is_some() {
51        return None;
52    }
53    insert_group_for_char(ch)
54}
55
56#[derive(Clone, Debug)]
57pub(crate) struct TextEdit<T> {
58    pub(crate) start: usize,
59    pub(crate) deleted: String,
60    pub(crate) inserted: String,
61    pub(crate) cursor_before: usize,
62    pub(crate) anchor_before: Option<usize>,
63    pub(crate) cursor_after: usize,
64    pub(crate) anchor_after: Option<usize>,
65    pub(crate) kind: TextEditKind,
66    _target: PhantomData<T>,
67}
68
69/// A text edit description for external consumers (e.g. LSP clients).
70#[derive(Clone, Debug, PartialEq, Eq, Hash)]
71pub struct TextEditEvent {
72    /// Start byte index of the edit.
73    pub start: usize,
74    /// Deleted text.
75    pub deleted: Arc<str>,
76    /// Inserted text.
77    pub inserted: Arc<str>,
78    /// Cursor before the edit.
79    pub cursor_before: usize,
80    /// Selection anchor before the edit.
81    pub anchor_before: Option<usize>,
82    /// Cursor after the edit.
83    pub cursor_after: usize,
84    /// Selection anchor after the edit.
85    pub anchor_after: Option<usize>,
86    /// Edit kind.
87    pub kind: TextEditKind,
88}
89
90impl<T> From<&TextEdit<T>> for TextEditEvent {
91    fn from(edit: &TextEdit<T>) -> Self {
92        Self {
93            start: edit.start,
94            deleted: Arc::from(edit.deleted.as_str()),
95            inserted: Arc::from(edit.inserted.as_str()),
96            cursor_before: edit.cursor_before,
97            anchor_before: edit.anchor_before,
98            cursor_after: edit.cursor_after,
99            anchor_after: edit.anchor_after,
100            kind: edit.kind,
101        }
102    }
103}
104
105pub(crate) struct TextEditFields {
106    pub start: usize,
107    pub deleted: String,
108    pub inserted: String,
109    pub cursor_before: usize,
110    pub anchor_before: Option<usize>,
111    pub cursor_after: usize,
112    pub anchor_after: Option<usize>,
113    pub kind: TextEditKind,
114}
115
116impl<T> TextEdit<T> {
117    pub(crate) fn new(fields: TextEditFields) -> Self {
118        Self {
119            start: fields.start,
120            deleted: fields.deleted,
121            inserted: fields.inserted,
122            cursor_before: fields.cursor_before,
123            anchor_before: fields.anchor_before,
124            cursor_after: fields.cursor_after,
125            anchor_after: fields.anchor_after,
126            kind: fields.kind,
127            _target: PhantomData,
128        }
129    }
130
131    pub(crate) fn apply_to(
132        &self,
133        text: &mut String,
134        cursor: &mut usize,
135        anchor: &mut Option<usize>,
136    ) {
137        let end = self.start.saturating_add(self.deleted.len());
138        text.replace_range(self.start..end, &self.inserted);
139        *cursor = self.cursor_after;
140        *anchor = self.anchor_after;
141    }
142
143    pub(crate) fn undo_on(
144        &self,
145        text: &mut String,
146        cursor: &mut usize,
147        anchor: &mut Option<usize>,
148    ) {
149        let end = self.start.saturating_add(self.inserted.len());
150        text.replace_range(self.start..end, &self.deleted);
151        *cursor = self.cursor_before;
152        *anchor = self.anchor_before;
153    }
154
155    pub(crate) fn merge_with(&mut self, other: Self) -> Merged<Self> {
156        if self.kind == TextEditKind::DeleteBackspace
157            && other.kind == TextEditKind::DeleteBackspace
158            && self.inserted.is_empty()
159            && other.inserted.is_empty()
160            && self.anchor_before.is_none()
161            && self.anchor_after.is_none()
162            && other.anchor_before.is_none()
163            && other.anchor_after.is_none()
164            && other.cursor_before == self.cursor_after
165            && other.start.saturating_add(other.deleted.len()) == self.start
166        {
167            self.start = other.start;
168            self.deleted.reserve(other.deleted.len());
169            self.deleted.insert_str(0, &other.deleted);
170            self.cursor_after = other.cursor_after;
171            self.anchor_after = other.anchor_after;
172            return Merged::Yes;
173        }
174
175        if self.kind == TextEditKind::DeleteForward
176            && other.kind == TextEditKind::DeleteForward
177            && self.inserted.is_empty()
178            && other.inserted.is_empty()
179            && self.anchor_before.is_none()
180            && self.anchor_after.is_none()
181            && other.anchor_before.is_none()
182            && other.anchor_after.is_none()
183            && self.start == other.start
184            && self.cursor_before == other.cursor_before
185            && self.cursor_after == other.cursor_after
186        {
187            self.deleted.push_str(&other.deleted);
188            return Merged::Yes;
189        }
190
191        if self.kind == TextEditKind::Insert
192            && other.kind == TextEditKind::Insert
193            && self.deleted.is_empty()
194            && other.deleted.is_empty()
195            && self.anchor_before.is_none()
196            && self.anchor_after.is_none()
197            && other.anchor_before.is_none()
198            && other.anchor_after.is_none()
199            && other.cursor_before == self.cursor_after
200            && other.start == self.start.saturating_add(self.inserted.len())
201        {
202            let group = insert_group_last(&self.inserted);
203            let other_group = insert_group_single(&other.inserted);
204            let should_merge = matches!(
205                (group, other_group),
206                (Some(InsertGroup::Whitespace), Some(InsertGroup::Whitespace))
207                    | (
208                        Some(InsertGroup::NonWhitespace),
209                        Some(InsertGroup::NonWhitespace)
210                    )
211                    | (
212                        Some(InsertGroup::NonWhitespace),
213                        Some(InsertGroup::Whitespace)
214                    )
215            );
216            if should_merge {
217                self.inserted.push_str(&other.inserted);
218                self.cursor_after = other.cursor_after;
219                self.anchor_after = other.anchor_after;
220                return Merged::Yes;
221            }
222        }
223
224        Merged::No(other)
225    }
226}
227
228impl<T: TextTarget> Edit for TextEdit<T> {
229    type Target = T;
230    type Output = ();
231
232    fn edit(&mut self, target: &mut Self::Target) -> Self::Output {
233        target.with_parts_mut(|text, cursor, anchor| {
234            self.apply_to(text, cursor, anchor);
235        });
236    }
237
238    fn undo(&mut self, target: &mut Self::Target) -> Self::Output {
239        target.with_parts_mut(|text, cursor, anchor| {
240            self.undo_on(text, cursor, anchor);
241        });
242    }
243
244    fn merge(&mut self, other: Self) -> Merged<Self> {
245        self.merge_with(other)
246    }
247}