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#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
16pub enum TextEditKind {
17 Insert,
19 DeleteBackspace,
21 DeleteForward,
23 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#[derive(Clone, Debug, PartialEq, Eq, Hash)]
71pub struct TextEditEvent {
72 pub start: usize,
74 pub deleted: Arc<str>,
76 pub inserted: Arc<str>,
78 pub cursor_before: usize,
80 pub anchor_before: Option<usize>,
82 pub cursor_after: usize,
84 pub anchor_after: Option<usize>,
86 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}