egui/widgets/text_edit/
text_buffer.rs

1use std::{borrow::Cow, ops::Range};
2
3use epaint::{
4    text::{
5        cursor::{CCursor, PCursor},
6        TAB_SIZE,
7    },
8    Galley,
9};
10
11use crate::text_selection::{
12    text_cursor_state::{
13        byte_index_from_char_index, ccursor_next_word, ccursor_previous_word, find_line_start,
14        slice_char_range,
15    },
16    CursorRange,
17};
18
19/// Trait constraining what types [`crate::TextEdit`] may use as
20/// an underlying buffer.
21///
22/// Most likely you will use a [`String`] which implements [`TextBuffer`].
23pub trait TextBuffer {
24    /// Can this text be edited?
25    fn is_mutable(&self) -> bool;
26
27    /// Returns this buffer as a `str`.
28    fn as_str(&self) -> &str;
29
30    /// Inserts text `text` into this buffer at character index `char_index`.
31    ///
32    /// # Notes
33    /// `char_index` is a *character index*, not a byte index.
34    ///
35    /// # Return
36    /// Returns how many *characters* were successfully inserted
37    fn insert_text(&mut self, text: &str, char_index: usize) -> usize;
38
39    /// Deletes a range of text `char_range` from this buffer.
40    ///
41    /// # Notes
42    /// `char_range` is a *character range*, not a byte range.
43    fn delete_char_range(&mut self, char_range: Range<usize>);
44
45    /// Reads the given character range.
46    fn char_range(&self, char_range: Range<usize>) -> &str {
47        slice_char_range(self.as_str(), char_range)
48    }
49
50    fn byte_index_from_char_index(&self, char_index: usize) -> usize {
51        byte_index_from_char_index(self.as_str(), char_index)
52    }
53
54    /// Clears all characters in this buffer
55    fn clear(&mut self) {
56        self.delete_char_range(0..self.as_str().len());
57    }
58
59    /// Replaces all contents of this string with `text`
60    fn replace_with(&mut self, text: &str) {
61        self.clear();
62        self.insert_text(text, 0);
63    }
64
65    /// Clears all characters in this buffer and returns a string of the contents.
66    fn take(&mut self) -> String {
67        let s = self.as_str().to_owned();
68        self.clear();
69        s
70    }
71
72    fn insert_text_at(&mut self, ccursor: &mut CCursor, text_to_insert: &str, char_limit: usize) {
73        if char_limit < usize::MAX {
74            let mut new_string = text_to_insert;
75            // Avoid subtract with overflow panic
76            let cutoff = char_limit.saturating_sub(self.as_str().chars().count());
77
78            new_string = match new_string.char_indices().nth(cutoff) {
79                None => new_string,
80                Some((idx, _)) => &new_string[..idx],
81            };
82
83            ccursor.index += self.insert_text(new_string, ccursor.index);
84        } else {
85            ccursor.index += self.insert_text(text_to_insert, ccursor.index);
86        }
87    }
88
89    fn decrease_indentation(&mut self, ccursor: &mut CCursor) {
90        let line_start = find_line_start(self.as_str(), *ccursor);
91
92        let remove_len = if self.as_str().chars().nth(line_start.index) == Some('\t') {
93            Some(1)
94        } else if self
95            .as_str()
96            .chars()
97            .skip(line_start.index)
98            .take(TAB_SIZE)
99            .all(|c| c == ' ')
100        {
101            Some(TAB_SIZE)
102        } else {
103            None
104        };
105
106        if let Some(len) = remove_len {
107            self.delete_char_range(line_start.index..(line_start.index + len));
108            if *ccursor != line_start {
109                *ccursor -= len;
110            }
111        }
112    }
113
114    fn delete_selected(&mut self, cursor_range: &CursorRange) -> CCursor {
115        let [min, max] = cursor_range.sorted_cursors();
116        self.delete_selected_ccursor_range([min.ccursor, max.ccursor])
117    }
118
119    fn delete_selected_ccursor_range(&mut self, [min, max]: [CCursor; 2]) -> CCursor {
120        self.delete_char_range(min.index..max.index);
121        CCursor {
122            index: min.index,
123            prefer_next_row: true,
124        }
125    }
126
127    fn delete_previous_char(&mut self, ccursor: CCursor) -> CCursor {
128        if ccursor.index > 0 {
129            let max_ccursor = ccursor;
130            let min_ccursor = max_ccursor - 1;
131            self.delete_selected_ccursor_range([min_ccursor, max_ccursor])
132        } else {
133            ccursor
134        }
135    }
136
137    fn delete_next_char(&mut self, ccursor: CCursor) -> CCursor {
138        self.delete_selected_ccursor_range([ccursor, ccursor + 1])
139    }
140
141    fn delete_previous_word(&mut self, max_ccursor: CCursor) -> CCursor {
142        let min_ccursor = ccursor_previous_word(self.as_str(), max_ccursor);
143        self.delete_selected_ccursor_range([min_ccursor, max_ccursor])
144    }
145
146    fn delete_next_word(&mut self, min_ccursor: CCursor) -> CCursor {
147        let max_ccursor = ccursor_next_word(self.as_str(), min_ccursor);
148        self.delete_selected_ccursor_range([min_ccursor, max_ccursor])
149    }
150
151    fn delete_paragraph_before_cursor(
152        &mut self,
153        galley: &Galley,
154        cursor_range: &CursorRange,
155    ) -> CCursor {
156        let [min, max] = cursor_range.sorted_cursors();
157        let min = galley.from_pcursor(PCursor {
158            paragraph: min.pcursor.paragraph,
159            offset: 0,
160            prefer_next_row: true,
161        });
162        if min.ccursor == max.ccursor {
163            self.delete_previous_char(min.ccursor)
164        } else {
165            self.delete_selected(&CursorRange::two(min, max))
166        }
167    }
168
169    fn delete_paragraph_after_cursor(
170        &mut self,
171        galley: &Galley,
172        cursor_range: &CursorRange,
173    ) -> CCursor {
174        let [min, max] = cursor_range.sorted_cursors();
175        let max = galley.from_pcursor(PCursor {
176            paragraph: max.pcursor.paragraph,
177            offset: usize::MAX, // end of paragraph
178            prefer_next_row: false,
179        });
180        if min.ccursor == max.ccursor {
181            self.delete_next_char(min.ccursor)
182        } else {
183            self.delete_selected(&CursorRange::two(min, max))
184        }
185    }
186}
187
188impl TextBuffer for String {
189    fn is_mutable(&self) -> bool {
190        true
191    }
192
193    fn as_str(&self) -> &str {
194        self.as_ref()
195    }
196
197    fn insert_text(&mut self, text: &str, char_index: usize) -> usize {
198        // Get the byte index from the character index
199        let byte_idx = byte_index_from_char_index(self.as_str(), char_index);
200
201        // Then insert the string
202        self.insert_str(byte_idx, text);
203
204        text.chars().count()
205    }
206
207    fn delete_char_range(&mut self, char_range: Range<usize>) {
208        assert!(char_range.start <= char_range.end);
209
210        // Get both byte indices
211        let byte_start = byte_index_from_char_index(self.as_str(), char_range.start);
212        let byte_end = byte_index_from_char_index(self.as_str(), char_range.end);
213
214        // Then drain all characters within this range
215        self.drain(byte_start..byte_end);
216    }
217
218    fn clear(&mut self) {
219        self.clear();
220    }
221
222    fn replace_with(&mut self, text: &str) {
223        text.clone_into(self);
224    }
225
226    fn take(&mut self) -> String {
227        std::mem::take(self)
228    }
229}
230
231impl TextBuffer for Cow<'_, str> {
232    fn is_mutable(&self) -> bool {
233        true
234    }
235
236    fn as_str(&self) -> &str {
237        self.as_ref()
238    }
239
240    fn insert_text(&mut self, text: &str, char_index: usize) -> usize {
241        <String as TextBuffer>::insert_text(self.to_mut(), text, char_index)
242    }
243
244    fn delete_char_range(&mut self, char_range: Range<usize>) {
245        <String as TextBuffer>::delete_char_range(self.to_mut(), char_range);
246    }
247
248    fn clear(&mut self) {
249        <String as TextBuffer>::clear(self.to_mut());
250    }
251
252    fn replace_with(&mut self, text: &str) {
253        *self = Cow::Owned(text.to_owned());
254    }
255
256    fn take(&mut self) -> String {
257        std::mem::take(self).into_owned()
258    }
259}
260
261/// Immutable view of a `&str`!
262impl TextBuffer for &str {
263    fn is_mutable(&self) -> bool {
264        false
265    }
266
267    fn as_str(&self) -> &str {
268        self
269    }
270
271    fn insert_text(&mut self, _text: &str, _ch_idx: usize) -> usize {
272        0
273    }
274
275    fn delete_char_range(&mut self, _ch_range: Range<usize>) {}
276}