cranpose_foundation/text/
buffer.rs1use smallvec::SmallVec;
2use unicode_segmentation::GraphemeCursor;
3
4use super::{TextFieldValue, TextRange};
5
6#[derive(Debug, Clone)]
35pub struct TextFieldBuffer {
36 text: String,
37 selection: TextRange,
38 composition: Option<TextRange>,
39 has_changes: bool,
40}
41
42impl TextFieldBuffer {
43 pub fn new(initial_text: impl Into<String>) -> Self {
46 let text: String = initial_text.into();
47 let len = text.len();
48 Self {
49 text,
50 selection: TextRange::cursor(len),
51 composition: None,
52 has_changes: false,
53 }
54 }
55
56 pub fn with_selection(text: impl Into<String>, selection: TextRange) -> Self {
58 let text: String = text.into();
59 let selection = selection.coerce_in(text.len());
60 Self {
61 text,
62 selection,
63 composition: None,
64 has_changes: false,
65 }
66 }
67
68 pub fn text(&self) -> &str {
70 &self.text
71 }
72
73 pub fn len(&self) -> usize {
75 self.text.len()
76 }
77
78 pub fn is_empty(&self) -> bool {
80 self.text.is_empty()
81 }
82
83 pub fn selection(&self) -> TextRange {
85 self.selection
86 }
87
88 pub fn composition(&self) -> Option<TextRange> {
90 self.composition
91 }
92
93 pub fn has_selection(&self) -> bool {
95 !self.selection.collapsed()
96 }
97
98 pub fn has_changes(&self) -> bool {
100 self.has_changes
101 }
102
103 pub fn replace(&mut self, range: TextRange, replacement: &str) {
109 let min = range.min().min(self.text.len());
110 let max = range.max().min(self.text.len());
111
112 self.text.replace_range(min..max, replacement);
113
114 let new_end = min + replacement.len();
115 self.selection = TextRange::cursor(new_end);
116
117 self.composition = None;
118 self.has_changes = true;
119 }
120
121 pub fn insert(&mut self, text: &str) {
123 if self.has_selection() {
124 self.replace(self.selection, text);
125 } else {
126 let pos = self.selection.start.min(self.text.len());
127 self.text.insert_str(pos, text);
128 self.selection = TextRange::cursor(pos + text.len());
129 self.composition = None;
130 self.has_changes = true;
131 }
132 }
133
134 pub fn append(&mut self, text: &str) {
136 self.text.push_str(text);
137 self.has_changes = true;
138 }
139
140 pub fn delete(&mut self, range: TextRange) {
142 self.replace(range, "");
143 }
144
145 pub fn delete_before_cursor(&mut self) {
147 if self.has_selection() {
148 self.delete(self.selection);
149 } else if self.selection.start > 0 {
150 let pos = self.selection.start;
151 let prev_pos = self.prev_grapheme_boundary(pos);
152 self.delete(TextRange::new(prev_pos, pos));
153 }
154 }
155
156 pub fn delete_after_cursor(&mut self) {
158 if self.has_selection() {
159 self.delete(self.selection);
160 } else if self.selection.start < self.text.len() {
161 let pos = self.selection.start;
162 let next_pos = self.next_grapheme_boundary(pos);
163 self.delete(TextRange::new(pos, next_pos));
164 }
165 }
166
167 pub fn delete_surrounding(&mut self, before_bytes: usize, after_bytes: usize) {
172 if self.text.is_empty() || (before_bytes == 0 && after_bytes == 0) {
173 return;
174 }
175
176 let selection = self.selection;
177 let mut start = selection.min().saturating_sub(before_bytes);
178 let mut end = selection
179 .max()
180 .saturating_add(after_bytes)
181 .min(self.text.len());
182
183 start = self.text.floor_char_boundary(start);
184 end = self.text.ceil_char_boundary(end);
185
186 if start >= end {
187 return;
188 }
189
190 let mut ranges: SmallVec<[(usize, usize); 2]> = SmallVec::new();
192 if let Some(comp) = self.composition {
193 let comp_start = comp.min();
194 let comp_end = comp.max();
195
196 if end <= comp_start || start >= comp_end {
197 ranges.push((start, end));
198 } else {
199 if start < comp_start {
200 ranges.push((start, comp_start));
201 }
202 if end > comp_end {
203 ranges.push((comp_end, end));
204 }
205 }
206 } else {
207 ranges.push((start, end));
208 }
209
210 if ranges.is_empty() {
211 return;
212 }
213
214 for &(range_start, range_end) in ranges.iter().rev() {
215 self.text.replace_range(range_start..range_end, "");
216 }
217
218 let removed_before = |pos: usize| -> usize {
219 let mut removed = 0usize;
220 for (range_start, range_end) in &ranges {
221 if pos <= *range_start {
222 break;
223 }
224 let clamped_end = pos.min(*range_end);
225 if clamped_end > *range_start {
226 removed += clamped_end - *range_start;
227 }
228 }
229 removed
230 };
231
232 let cursor_pos = selection.min();
233 let new_cursor = cursor_pos
234 .saturating_sub(removed_before(cursor_pos))
235 .min(self.text.len());
236
237 self.selection = TextRange::cursor(new_cursor);
238 self.composition = self.composition.map(|comp| {
239 let comp_start = comp.min().saturating_sub(removed_before(comp.min()));
240 let comp_end = comp.max().saturating_sub(removed_before(comp.max()));
241 TextRange::new(comp_start, comp_end).coerce_in(self.text.len())
242 });
243 self.has_changes = true;
244 }
245
246 pub fn clear(&mut self) {
248 self.text.clear();
249 self.selection = TextRange::zero();
250 self.composition = None;
251 self.has_changes = true;
252 }
253
254 pub fn place_cursor_at_end(&mut self) {
256 self.selection = TextRange::cursor(self.text.len());
257 }
258
259 pub fn place_cursor_at_start(&mut self) {
261 self.selection = TextRange::zero();
262 }
263
264 pub fn place_cursor_before_char(&mut self, index: usize) {
266 let pos = index.min(self.text.len());
267 self.selection = TextRange::cursor(pos);
268 }
269
270 pub fn move_cursor_left(&mut self) {
273 let target = if self.has_selection() {
274 self.selection.min()
275 } else {
276 self.prev_grapheme_boundary(self.selection.start)
277 };
278 self.selection = TextRange::cursor(target);
279 }
280
281 pub fn move_cursor_right(&mut self) {
284 let target = if self.has_selection() {
285 self.selection.max()
286 } else {
287 self.next_grapheme_boundary(self.selection.end)
288 };
289 self.selection = TextRange::cursor(target);
290 }
291
292 pub fn select_all(&mut self) {
294 self.selection = TextRange::all(self.text.len());
295 }
296
297 pub fn extend_selection_left(&mut self) {
309 if self.selection.end > 0 {
310 let cursor = self.prev_grapheme_boundary(self.selection.end);
311 self.selection = TextRange::new(self.selection.start, cursor);
312 }
313 }
314
315 pub fn extend_selection_right(&mut self) {
320 if self.selection.end < self.text.len() {
321 let cursor = self.next_grapheme_boundary(self.selection.end);
322 self.selection = TextRange::new(self.selection.start, cursor);
323 }
324 }
325
326 pub fn select(&mut self, range: TextRange) {
328 self.selection = range.coerce_in(self.text.len());
329 }
330
331 pub fn set_composition(&mut self, range: Option<TextRange>) {
333 self.composition = range.map(|r| r.coerce_in(self.text.len()));
334 }
335
336 fn grapheme_cursor(&self, from: usize) -> GraphemeCursor {
339 let from = self.text.floor_char_boundary(from);
340 GraphemeCursor::new(from, self.text.len(), true)
341 }
342
343 fn prev_grapheme_boundary(&self, from: usize) -> usize {
344 self.grapheme_cursor(from)
345 .prev_boundary(&self.text, 0)
346 .ok()
347 .flatten()
348 .unwrap_or(0)
349 }
350
351 fn next_grapheme_boundary(&self, from: usize) -> usize {
352 self.grapheme_cursor(from)
353 .next_boundary(&self.text, 0)
354 .ok()
355 .flatten()
356 .unwrap_or(self.text.len())
357 }
358
359 pub(crate) fn into_value(self) -> TextFieldValue {
360 TextFieldValue {
361 text: self.text,
362 selection: self.selection,
363 composition: self.composition,
364 }
365 }
366
367 pub fn copy_selection(&self) -> Option<String> {
370 if !self.has_selection() {
371 return None;
372 }
373
374 let sel_start = self.selection.min();
375 let sel_end = self.selection.max();
376 Some(self.text[sel_start..sel_end].to_string())
377 }
378
379 pub fn cut_selection(&mut self) -> Option<String> {
382 let copied = self.copy_selection();
383 if copied.is_some() {
384 self.delete(self.selection);
385 self.has_changes = true;
386 }
387 copied
388 }
389}
390
391impl Default for TextFieldBuffer {
392 fn default() -> Self {
393 Self::new("")
394 }
395}
396
397#[cfg(test)]
398#[path = "tests/buffer_tests.rs"]
399mod tests;