Skip to main content

egui/widgets/text_edit/
text_buffer.rs

1use core::ops::Range;
2use std::borrow::Cow;
3
4use epaint::{
5    Galley,
6    text::{
7        ByteIndex, ByteRangeExt as _, CharIndex, CharRange, CharRangeExt as _, cursor::CCursor,
8    },
9};
10
11/// One `\t` character is this many spaces wide (for indentation purposes).
12const TAB_SIZE: usize = 4;
13
14use crate::{
15    text::CCursorRange,
16    text_selection::text_cursor_state::{
17        byte_index_from_char_index, ccursor_next_word, ccursor_previous_word,
18        char_index_from_byte_index, find_line_start, slice_char_range,
19    },
20};
21
22/// Trait constraining what types [`crate::TextEdit`] may use as
23/// an underlying buffer.
24///
25/// Most likely you will use a [`String`] which implements [`TextBuffer`].
26pub trait TextBuffer {
27    /// Can this text be edited?
28    fn is_mutable(&self) -> bool;
29
30    /// Returns this buffer as a `str`.
31    fn as_str(&self) -> &str;
32
33    /// Inserts text `text` into this buffer at character index `char_index`.
34    ///
35    /// # Notes
36    /// `char_index` is a *character index*, not a byte index.
37    ///
38    /// # Return
39    /// Returns how many *characters* were successfully inserted
40    fn insert_text(&mut self, text: &str, char_index: CharIndex) -> usize;
41
42    /// Deletes a range of text `char_range` from this buffer.
43    ///
44    /// # Notes
45    /// `char_range` is a *character range*, not a byte range.
46    fn delete_char_range(&mut self, char_range: Range<CharIndex>);
47
48    /// Reads the given character range.
49    fn char_range(&self, char_range: Range<CharIndex>) -> &str {
50        slice_char_range(self.as_str(), char_range)
51    }
52
53    fn byte_index_from_char_index(&self, char_index: CharIndex) -> ByteIndex {
54        byte_index_from_char_index(self.as_str(), char_index)
55    }
56
57    fn char_index_from_byte_index(&self, byte_index: ByteIndex) -> CharIndex {
58        char_index_from_byte_index(self.as_str(), byte_index)
59    }
60
61    /// Clears all characters in this buffer
62    fn clear(&mut self) {
63        self.delete_char_range(CharRange::full(self.as_str()));
64    }
65
66    /// Replaces all contents of this string with `text`
67    fn replace_with(&mut self, text: &str) {
68        self.clear();
69        self.insert_text(text, CharIndex(0));
70    }
71
72    /// Clears all characters in this buffer and returns a string of the contents.
73    fn take(&mut self) -> String {
74        let s = self.as_str().to_owned();
75        self.clear();
76        s
77    }
78
79    fn insert_text_at(&mut self, ccursor: &mut CCursor, text_to_insert: &str, char_limit: usize) {
80        if char_limit < usize::MAX {
81            let mut new_string = text_to_insert;
82            // Avoid subtract with overflow panic
83            let cutoff = char_limit.saturating_sub(self.as_str().chars().count());
84
85            new_string = match new_string.char_indices().nth(cutoff) {
86                None => new_string,
87                Some((idx, _)) => &new_string[..idx],
88            };
89
90            ccursor.index += self.insert_text(new_string, ccursor.index);
91        } else {
92            ccursor.index += self.insert_text(text_to_insert, ccursor.index);
93        }
94    }
95
96    fn decrease_indentation(&mut self, ccursor: &mut CCursor) {
97        let line_start = find_line_start(self.as_str(), *ccursor);
98
99        let remove_len = if self.as_str().chars().nth(line_start.index.0) == Some('\t') {
100            Some(1)
101        } else if self
102            .as_str()
103            .chars()
104            .skip(line_start.index.0)
105            .take(TAB_SIZE)
106            .all(|c| c == ' ')
107        {
108            Some(TAB_SIZE)
109        } else {
110            None
111        };
112
113        if let Some(len) = remove_len {
114            self.delete_char_range(line_start.index..(line_start.index + len));
115            if *ccursor != line_start {
116                *ccursor -= len;
117            }
118        }
119    }
120
121    fn delete_selected(&mut self, cursor_range: &CCursorRange) -> CCursor {
122        let [min, max] = cursor_range.sorted_cursors();
123        self.delete_selected_ccursor_range([min, max])
124    }
125
126    fn delete_selected_ccursor_range(&mut self, [min, max]: [CCursor; 2]) -> CCursor {
127        self.delete_char_range(min.index..max.index);
128        CCursor {
129            index: min.index,
130            prefer_next_row: true,
131        }
132    }
133
134    fn delete_previous_char(&mut self, ccursor: CCursor) -> CCursor {
135        if CharIndex::ZERO < ccursor.index {
136            let max_ccursor = ccursor;
137            let min_ccursor = max_ccursor - 1;
138            self.delete_selected_ccursor_range([min_ccursor, max_ccursor])
139        } else {
140            ccursor
141        }
142    }
143
144    fn delete_next_char(&mut self, ccursor: CCursor) -> CCursor {
145        self.delete_selected_ccursor_range([ccursor, ccursor + 1])
146    }
147
148    fn delete_previous_word(&mut self, max_ccursor: CCursor) -> CCursor {
149        let min_ccursor = ccursor_previous_word(self.as_str(), max_ccursor);
150        self.delete_selected_ccursor_range([min_ccursor, max_ccursor])
151    }
152
153    fn delete_next_word(&mut self, min_ccursor: CCursor) -> CCursor {
154        let max_ccursor = ccursor_next_word(self.as_str(), min_ccursor);
155        self.delete_selected_ccursor_range([min_ccursor, max_ccursor])
156    }
157
158    /// Deletes characters surrounding the current cursor range.
159    ///
160    /// Removes `before_chars` characters before the selection start and
161    /// `after_chars` characters after the selection end.
162    /// The returned [`CCursorRange`] is adjusted to account for the removed
163    /// characters before the selection.
164    fn delete_surrounding_chars(
165        &mut self,
166        mut cursor_range: CCursorRange,
167        before_chars: usize,
168        after_chars: usize,
169    ) -> CCursorRange {
170        let [min, max] = cursor_range.sorted_cursors();
171        if after_chars > 0 {
172            self.delete_selected_ccursor_range([max, max + after_chars]);
173        }
174        if before_chars > 0 {
175            self.delete_selected_ccursor_range([min - before_chars, min]);
176            cursor_range.primary -= before_chars;
177            cursor_range.secondary -= before_chars;
178        }
179        cursor_range
180    }
181
182    fn delete_paragraph_before_cursor(
183        &mut self,
184        galley: &Galley,
185        cursor_range: &CCursorRange,
186    ) -> CCursor {
187        let [min, max] = cursor_range.sorted_cursors();
188        let min = galley.cursor_begin_of_paragraph(&min);
189        if min == max {
190            self.delete_previous_char(min)
191        } else {
192            self.delete_selected(&CCursorRange::two(min, max))
193        }
194    }
195
196    fn delete_paragraph_after_cursor(
197        &mut self,
198        galley: &Galley,
199        cursor_range: &CCursorRange,
200    ) -> CCursor {
201        let [min, max] = cursor_range.sorted_cursors();
202        let max = galley.cursor_end_of_paragraph(&max);
203        if min == max {
204            self.delete_next_char(min)
205        } else {
206            self.delete_selected(&CCursorRange::two(min, max))
207        }
208    }
209
210    /// Returns a unique identifier for the implementing type.
211    ///
212    /// This is useful for downcasting from this trait to the implementing type.
213    /// Here is an example usage:
214    /// ```
215    /// use egui::TextBuffer;
216    /// use std::any::TypeId;
217    ///
218    /// struct ExampleBuffer {}
219    ///
220    /// impl TextBuffer for ExampleBuffer {
221    ///     fn is_mutable(&self) -> bool { unimplemented!() }
222    ///     fn as_str(&self) -> &str { unimplemented!() }
223    ///     fn insert_text(&mut self, text: &str, char_index: egui::text::CharIndex) -> usize { unimplemented!() }
224    ///     fn delete_char_range(&mut self, char_range: std::ops::Range<egui::text::CharIndex>) { unimplemented!() }
225    ///
226    ///     // Implement it like the following:
227    ///     fn type_id(&self) -> TypeId {
228    ///         TypeId::of::<Self>()
229    ///     }
230    /// }
231    ///
232    /// // Example downcast:
233    /// pub fn downcast_example(buffer: &dyn TextBuffer) -> Option<&ExampleBuffer> {
234    ///     if buffer.type_id() == TypeId::of::<ExampleBuffer>() {
235    ///         unsafe { Some(&*(buffer as *const dyn TextBuffer as *const ExampleBuffer)) }
236    ///     } else {
237    ///         None
238    ///     }
239    /// }
240    /// ```
241    fn type_id(&self) -> core::any::TypeId;
242}
243
244impl TextBuffer for String {
245    fn is_mutable(&self) -> bool {
246        true
247    }
248
249    fn as_str(&self) -> &str {
250        self.as_ref()
251    }
252
253    fn insert_text(&mut self, text: &str, char_index: CharIndex) -> usize {
254        // Get the byte index from the character index
255        let byte_idx = byte_index_from_char_index(self.as_str(), char_index);
256
257        // Then insert the string
258        self.insert_str(byte_idx.into(), text);
259
260        text.chars().count()
261    }
262
263    fn delete_char_range(&mut self, char_range: Range<CharIndex>) {
264        assert!(
265            char_range.start <= char_range.end,
266            "start must be <= end, but got {char_range:?}"
267        );
268
269        // Get both byte indices
270        let byte_start = byte_index_from_char_index(self.as_str(), char_range.start);
271        let byte_end = byte_index_from_char_index(self.as_str(), char_range.end);
272
273        // Then drain all characters within this range
274        self.drain((byte_start..byte_end).as_usize());
275    }
276
277    fn clear(&mut self) {
278        self.clear();
279    }
280
281    fn replace_with(&mut self, text: &str) {
282        text.clone_into(self);
283    }
284
285    fn take(&mut self) -> String {
286        core::mem::take(self)
287    }
288
289    fn type_id(&self) -> core::any::TypeId {
290        core::any::TypeId::of::<Self>()
291    }
292}
293
294impl TextBuffer for Cow<'_, str> {
295    fn is_mutable(&self) -> bool {
296        true
297    }
298
299    fn as_str(&self) -> &str {
300        self.as_ref()
301    }
302
303    fn insert_text(&mut self, text: &str, char_index: CharIndex) -> usize {
304        <String as TextBuffer>::insert_text(self.to_mut(), text, char_index)
305    }
306
307    fn delete_char_range(&mut self, char_range: Range<CharIndex>) {
308        <String as TextBuffer>::delete_char_range(self.to_mut(), char_range);
309    }
310
311    fn clear(&mut self) {
312        <String as TextBuffer>::clear(self.to_mut());
313    }
314
315    fn replace_with(&mut self, text: &str) {
316        *self = Cow::Owned(text.to_owned());
317    }
318
319    fn take(&mut self) -> String {
320        core::mem::take(self).into_owned()
321    }
322
323    fn type_id(&self) -> core::any::TypeId {
324        core::any::TypeId::of::<Cow<'_, str>>()
325    }
326}
327
328/// Immutable view of a `&str`!
329impl TextBuffer for &str {
330    fn is_mutable(&self) -> bool {
331        false
332    }
333
334    fn as_str(&self) -> &str {
335        self
336    }
337
338    fn insert_text(&mut self, _text: &str, _ch_idx: CharIndex) -> usize {
339        0
340    }
341
342    fn delete_char_range(&mut self, _ch_range: Range<CharIndex>) {}
343
344    fn type_id(&self) -> core::any::TypeId {
345        core::any::TypeId::of::<&str>()
346    }
347}
348
349#[cfg(test)]
350mod tests {
351    use super::*;
352
353    fn txt_n_sel(input: &str) -> (String, CCursorRange) {
354        assert!(
355            input.matches('[').count() == 1 && input.matches(']').count() == 1,
356            "`input` must contain exactly one `[` and one `]` to indicate the selection (cursor range)"
357        );
358        let mut primary_index = input.chars().position(|c| c == ']').unwrap();
359        let mut secondary_index = input.chars().position(|c| c == '[').unwrap();
360        let text = input.replace(['[', ']'], "");
361        if primary_index > secondary_index {
362            primary_index -= 1;
363        } else {
364            secondary_index -= 1;
365        }
366        let cursor_range = CCursorRange {
367            primary: CCursor::new(primary_index),
368            secondary: CCursor::new(secondary_index),
369            h_pos: None,
370        };
371        (text, cursor_range)
372    }
373
374    #[test]
375    fn test_txt_n_sel() {
376        assert_eq!(
377            txt_n_sel("<<L[]R>>"),
378            ("<<LR>>".to_owned(), CCursorRange::one(CCursor::new(3)))
379        );
380        assert_eq!(
381            txt_n_sel("<<L[_]R>>"),
382            (
383                "<<L_R>>".to_owned(),
384                CCursorRange::two(CCursor::new(3), CCursor::new(4))
385            )
386        );
387        assert_eq!(
388            txt_n_sel("<<左[_]右>>"),
389            (
390                "<<左_右>>".to_owned(),
391                CCursorRange::two(CCursor::new(3), CCursor::new(4))
392            )
393        );
394        assert_eq!(
395            txt_n_sel("<<L]_[R>>"),
396            (
397                "<<L_R>>".to_owned(),
398                CCursorRange::two(CCursor::new(4), CCursor::new(3))
399            )
400        );
401    }
402
403    #[test]
404    fn test_delete_surrounding_chars() {
405        fn test_case(
406            (mut input_text, input_cursor_range): (String, CCursorRange),
407            before_chars: usize,
408            after_chars: usize,
409            (expected_text, expected_cursor_range): (String, CCursorRange),
410        ) {
411            let new_cursor_range =
412                input_text.delete_surrounding_chars(input_cursor_range, before_chars, after_chars);
413            assert_eq!(input_text, expected_text);
414            assert_eq!(new_cursor_range, expected_cursor_range);
415        }
416
417        // 1 byte per char
418        test_case(txt_n_sel("<<L[]R>>"), 1, 1, txt_n_sel("<<[]>>"));
419        test_case(txt_n_sel("<<L[_]R>>"), 1, 0, txt_n_sel("<<[_]R>>"));
420        test_case(txt_n_sel("<<L[_]R>>"), 0, 1, txt_n_sel("<<L[_]>>"));
421        test_case(txt_n_sel("<<L[_]R>>"), 1, 1, txt_n_sel("<<[_]>>"));
422        test_case(txt_n_sel("<<L[__]R>>"), 1, 1, txt_n_sel("<<[__]>>"));
423        test_case(txt_n_sel("<<LL[_]RR>>"), 2, 2, txt_n_sel("<<[_]>>"));
424        test_case(txt_n_sel("<<L]_[R>>"), 1, 0, txt_n_sel("<<]_[R>>"));
425        test_case(txt_n_sel("<<L]_[R>>"), 0, 1, txt_n_sel("<<L]_[>>"));
426        test_case(txt_n_sel("<<L]_[R>>"), 1, 1, txt_n_sel("<<]_[>>"));
427
428        // 2 bytes per char: `˻` = `0xCB 0xBB`, `˼` = `0xCB 0xBC`
429        test_case(txt_n_sel("<<˻[]˼>>"), 1, 1, txt_n_sel("<<[]>>"));
430        test_case(txt_n_sel("<<˻[_]˼>>"), 1, 0, txt_n_sel("<<[_]˼>>"));
431        test_case(txt_n_sel("<<˻[_]˼>>"), 0, 1, txt_n_sel("<<˻[_]>>"));
432        test_case(txt_n_sel("<<˻[_]˼>>"), 1, 1, txt_n_sel("<<[_]>>"));
433        test_case(txt_n_sel("<<˻[__]˼>>"), 1, 1, txt_n_sel("<<[__]>>"));
434        test_case(txt_n_sel("<<˻˻[_]˼˼>>"), 2, 2, txt_n_sel("<<[_]>>"));
435        test_case(txt_n_sel("<<˻]_[˼>>"), 1, 0, txt_n_sel("<<]_[˼>>"));
436        test_case(txt_n_sel("<<˻]_[˼>>"), 0, 1, txt_n_sel("<<˻]_[>>"));
437        test_case(txt_n_sel("<<˻]_[˼>>"), 1, 1, txt_n_sel("<<]_[>>"));
438
439        // 3 bytes per char: `左` = `0xE5 0xB7 0xA6`, `右` = `0xE5 0x8F 0xB3`
440        test_case(txt_n_sel("<<左[]右>>"), 1, 1, txt_n_sel("<<[]>>"));
441        test_case(txt_n_sel("<<左[_]右>>"), 1, 0, txt_n_sel("<<[_]右>>"));
442        test_case(txt_n_sel("<<左[_]右>>"), 0, 1, txt_n_sel("<<左[_]>>"));
443        test_case(txt_n_sel("<<左[_]右>>"), 1, 1, txt_n_sel("<<[_]>>"));
444        test_case(txt_n_sel("<<左[__]右>>"), 1, 1, txt_n_sel("<<[__]>>"));
445        test_case(txt_n_sel("<<左左[_]右右>>"), 2, 2, txt_n_sel("<<[_]>>"));
446        test_case(txt_n_sel("<<左]_[右>>"), 1, 0, txt_n_sel("<<]_[右>>"));
447        test_case(txt_n_sel("<<左]_[右>>"), 0, 1, txt_n_sel("<<左]_[>>"));
448        test_case(txt_n_sel("<<左]_[右>>"), 1, 1, txt_n_sel("<<]_[>>"));
449
450        // mixed
451        test_case(txt_n_sel("<<L˻左[_]R˼右>>"), 3, 3, txt_n_sel("<<[_]>>"));
452    }
453}