cranpose_foundation/text/
buffer.rs1use smallvec::SmallVec;
2
3use super::{TextFieldValue, TextRange, next_grapheme_boundary, previous_grapheme_boundary};
4
5#[derive(Debug, Clone)]
34pub struct TextFieldBuffer {
35 text: String,
36 selection: TextRange,
37 composition: Option<TextRange>,
38 has_changes: bool,
39}
40
41impl TextFieldBuffer {
42 pub fn new(initial_text: impl Into<String>) -> Self {
45 let text: String = initial_text.into();
46 let len = text.len();
47 Self {
48 text,
49 selection: TextRange::cursor(len),
50 composition: None,
51 has_changes: false,
52 }
53 }
54
55 pub fn with_selection(text: impl Into<String>, selection: TextRange) -> Self {
57 let text: String = text.into();
58 let selection = selection.coerce_in(text.len());
59 Self {
60 text,
61 selection,
62 composition: None,
63 has_changes: false,
64 }
65 }
66
67 pub fn text(&self) -> &str {
69 &self.text
70 }
71
72 pub fn len(&self) -> usize {
74 self.text.len()
75 }
76
77 pub fn is_empty(&self) -> bool {
79 self.text.is_empty()
80 }
81
82 pub fn selection(&self) -> TextRange {
84 self.selection
85 }
86
87 pub fn composition(&self) -> Option<TextRange> {
89 self.composition
90 }
91
92 pub fn has_selection(&self) -> bool {
94 !self.selection.collapsed()
95 }
96
97 pub fn has_changes(&self) -> bool {
99 self.has_changes
100 }
101
102 pub fn replace(&mut self, range: TextRange, replacement: &str) {
108 let min = range.min().min(self.text.len());
109 let max = range.max().min(self.text.len());
110
111 self.text.replace_range(min..max, replacement);
112
113 let new_end = min + replacement.len();
114 self.selection = TextRange::cursor(new_end);
115
116 self.composition = None;
117 self.has_changes = true;
118 }
119
120 pub fn insert(&mut self, text: &str) {
122 if self.has_selection() {
123 self.replace(self.selection, text);
124 } else {
125 let pos = self.selection.start.min(self.text.len());
126 self.text.insert_str(pos, text);
127 self.selection = TextRange::cursor(pos + text.len());
128 self.composition = None;
129 self.has_changes = true;
130 }
131 }
132
133 pub fn append(&mut self, text: &str) {
135 self.text.push_str(text);
136 self.has_changes = true;
137 }
138
139 pub fn delete(&mut self, range: TextRange) {
141 self.replace(range, "");
142 }
143
144 pub fn delete_before_cursor(&mut self) {
146 if self.has_selection() {
147 self.delete(self.selection);
148 } else if self.selection.start > 0 {
149 let pos = self.selection.start;
150 let prev_pos = previous_grapheme_boundary(&self.text, pos);
151 self.delete(TextRange::new(prev_pos, pos));
152 }
153 }
154
155 pub fn delete_after_cursor(&mut self) {
157 if self.has_selection() {
158 self.delete(self.selection);
159 } else if self.selection.start < self.text.len() {
160 let pos = self.selection.start;
161 let next_pos = next_grapheme_boundary(&self.text, pos);
162 self.delete(TextRange::new(pos, next_pos));
163 }
164 }
165
166 pub fn delete_surrounding(&mut self, before_bytes: usize, after_bytes: usize) {
171 if self.text.is_empty() || (before_bytes == 0 && after_bytes == 0) {
172 return;
173 }
174
175 let selection = self.selection;
176 let mut start = selection.min().saturating_sub(before_bytes);
177 let mut end = selection
178 .max()
179 .saturating_add(after_bytes)
180 .min(self.text.len());
181
182 start = self.text.floor_char_boundary(start);
183 end = self.text.ceil_char_boundary(end);
184
185 if start >= end {
186 return;
187 }
188
189 let mut ranges: SmallVec<[(usize, usize); 2]> = SmallVec::new();
191 if let Some(comp) = self.composition {
192 let comp_start = comp.min();
193 let comp_end = comp.max();
194
195 if end <= comp_start || start >= comp_end {
196 ranges.push((start, end));
197 } else {
198 if start < comp_start {
199 ranges.push((start, comp_start));
200 }
201 if end > comp_end {
202 ranges.push((comp_end, end));
203 }
204 }
205 } else {
206 ranges.push((start, end));
207 }
208
209 if ranges.is_empty() {
210 return;
211 }
212
213 for &(range_start, range_end) in ranges.iter().rev() {
214 self.text.replace_range(range_start..range_end, "");
215 }
216
217 let removed_before = |pos: usize| -> usize {
218 let mut removed = 0usize;
219 for (range_start, range_end) in &ranges {
220 if pos <= *range_start {
221 break;
222 }
223 let clamped_end = pos.min(*range_end);
224 if clamped_end > *range_start {
225 removed += clamped_end - *range_start;
226 }
227 }
228 removed
229 };
230
231 let cursor_pos = selection.min();
232 let new_cursor = cursor_pos
233 .saturating_sub(removed_before(cursor_pos))
234 .min(self.text.len());
235
236 self.selection = TextRange::cursor(new_cursor);
237 self.composition = self.composition.map(|comp| {
238 let comp_start = comp.min().saturating_sub(removed_before(comp.min()));
239 let comp_end = comp.max().saturating_sub(removed_before(comp.max()));
240 TextRange::new(comp_start, comp_end).coerce_in(self.text.len())
241 });
242 self.has_changes = true;
243 }
244
245 pub fn reset_to(&mut self, value: &TextFieldValue) {
248 self.text.clear();
249 self.text.push_str(&value.text);
250 self.selection = value.selection;
251 self.composition = value.composition;
252 }
253
254 pub fn clear(&mut self) {
256 self.text.clear();
257 self.selection = TextRange::zero();
258 self.composition = None;
259 self.has_changes = true;
260 }
261
262 pub fn place_cursor_at_end(&mut self) {
264 self.selection = TextRange::cursor(self.text.len());
265 }
266
267 pub fn place_cursor_at_start(&mut self) {
269 self.selection = TextRange::zero();
270 }
271
272 pub fn place_cursor_before_char(&mut self, index: usize) {
274 let pos = index.min(self.text.len());
275 self.selection = TextRange::cursor(pos);
276 }
277
278 pub fn move_cursor_left(&mut self) {
281 let target = if self.has_selection() {
282 self.selection.min()
283 } else {
284 previous_grapheme_boundary(&self.text, self.selection.start)
285 };
286 self.selection = TextRange::cursor(target);
287 }
288
289 pub fn move_cursor_right(&mut self) {
292 let target = if self.has_selection() {
293 self.selection.max()
294 } else {
295 next_grapheme_boundary(&self.text, self.selection.end)
296 };
297 self.selection = TextRange::cursor(target);
298 }
299
300 pub fn select_all(&mut self) {
302 self.selection = TextRange::all(self.text.len());
303 }
304
305 pub fn extend_selection_left(&mut self) {
317 if self.selection.end > 0 {
318 let cursor = previous_grapheme_boundary(&self.text, self.selection.end);
319 self.selection = TextRange::new(self.selection.start, cursor);
320 }
321 }
322
323 pub fn extend_selection_right(&mut self) {
328 if self.selection.end < self.text.len() {
329 let cursor = next_grapheme_boundary(&self.text, self.selection.end);
330 self.selection = TextRange::new(self.selection.start, cursor);
331 }
332 }
333
334 pub fn select(&mut self, range: TextRange) {
336 self.selection = range.coerce_in(self.text.len());
337 }
338
339 pub fn set_composition(&mut self, range: Option<TextRange>) {
341 self.composition = range.map(|r| r.coerce_in(self.text.len()));
342 }
343
344 pub(crate) fn into_value(self) -> TextFieldValue {
345 TextFieldValue {
346 text: self.text,
347 selection: self.selection,
348 composition: self.composition,
349 }
350 }
351
352 pub fn copy_selection(&self) -> Option<String> {
355 if !self.has_selection() {
356 return None;
357 }
358
359 let sel_start = self.selection.min();
360 let sel_end = self.selection.max();
361 Some(self.text[sel_start..sel_end].to_string())
362 }
363
364 pub fn cut_selection(&mut self) -> Option<String> {
367 let copied = self.copy_selection();
368 if copied.is_some() {
369 self.delete(self.selection);
370 self.has_changes = true;
371 }
372 copied
373 }
374}
375
376impl Default for TextFieldBuffer {
377 fn default() -> Self {
378 Self::new("")
379 }
380}
381
382#[cfg(test)]
383#[path = "tests/buffer_tests.rs"]
384mod tests;