egui/widgets/text_edit/
text_buffer.rs1use std::{borrow::Cow, ops::Range};
2
3use epaint::{
4 Galley,
5 text::{TAB_SIZE, cursor::CCursor},
6};
7
8use crate::{
9 text::CCursorRange,
10 text_selection::text_cursor_state::{
11 byte_index_from_char_index, ccursor_next_word, ccursor_previous_word,
12 char_index_from_byte_index, find_line_start, slice_char_range,
13 },
14};
15
16pub trait TextBuffer {
21 fn is_mutable(&self) -> bool;
23
24 fn as_str(&self) -> &str;
26
27 fn insert_text(&mut self, text: &str, char_index: usize) -> usize;
35
36 fn delete_char_range(&mut self, char_range: Range<usize>);
41
42 fn char_range(&self, char_range: Range<usize>) -> &str {
44 slice_char_range(self.as_str(), char_range)
45 }
46
47 fn byte_index_from_char_index(&self, char_index: usize) -> usize {
48 byte_index_from_char_index(self.as_str(), char_index)
49 }
50
51 fn char_index_from_byte_index(&self, char_index: usize) -> usize {
52 char_index_from_byte_index(self.as_str(), char_index)
53 }
54
55 fn clear(&mut self) {
57 self.delete_char_range(0..self.as_str().len());
58 }
59
60 fn replace_with(&mut self, text: &str) {
62 self.clear();
63 self.insert_text(text, 0);
64 }
65
66 fn take(&mut self) -> String {
68 let s = self.as_str().to_owned();
69 self.clear();
70 s
71 }
72
73 fn insert_text_at(&mut self, ccursor: &mut CCursor, text_to_insert: &str, char_limit: usize) {
74 if char_limit < usize::MAX {
75 let mut new_string = text_to_insert;
76 let cutoff = char_limit.saturating_sub(self.as_str().chars().count());
78
79 new_string = match new_string.char_indices().nth(cutoff) {
80 None => new_string,
81 Some((idx, _)) => &new_string[..idx],
82 };
83
84 ccursor.index += self.insert_text(new_string, ccursor.index);
85 } else {
86 ccursor.index += self.insert_text(text_to_insert, ccursor.index);
87 }
88 }
89
90 fn decrease_indentation(&mut self, ccursor: &mut CCursor) {
91 let line_start = find_line_start(self.as_str(), *ccursor);
92
93 let remove_len = if self.as_str().chars().nth(line_start.index) == Some('\t') {
94 Some(1)
95 } else if self
96 .as_str()
97 .chars()
98 .skip(line_start.index)
99 .take(TAB_SIZE)
100 .all(|c| c == ' ')
101 {
102 Some(TAB_SIZE)
103 } else {
104 None
105 };
106
107 if let Some(len) = remove_len {
108 self.delete_char_range(line_start.index..(line_start.index + len));
109 if *ccursor != line_start {
110 *ccursor -= len;
111 }
112 }
113 }
114
115 fn delete_selected(&mut self, cursor_range: &CCursorRange) -> CCursor {
116 let [min, max] = cursor_range.sorted_cursors();
117 self.delete_selected_ccursor_range([min, max])
118 }
119
120 fn delete_selected_ccursor_range(&mut self, [min, max]: [CCursor; 2]) -> CCursor {
121 self.delete_char_range(min.index..max.index);
122 CCursor {
123 index: min.index,
124 prefer_next_row: true,
125 }
126 }
127
128 fn delete_previous_char(&mut self, ccursor: CCursor) -> CCursor {
129 if ccursor.index > 0 {
130 let max_ccursor = ccursor;
131 let min_ccursor = max_ccursor - 1;
132 self.delete_selected_ccursor_range([min_ccursor, max_ccursor])
133 } else {
134 ccursor
135 }
136 }
137
138 fn delete_next_char(&mut self, ccursor: CCursor) -> CCursor {
139 self.delete_selected_ccursor_range([ccursor, ccursor + 1])
140 }
141
142 fn delete_previous_word(&mut self, max_ccursor: CCursor) -> CCursor {
143 let min_ccursor = ccursor_previous_word(self.as_str(), max_ccursor);
144 self.delete_selected_ccursor_range([min_ccursor, max_ccursor])
145 }
146
147 fn delete_next_word(&mut self, min_ccursor: CCursor) -> CCursor {
148 let max_ccursor = ccursor_next_word(self.as_str(), min_ccursor);
149 self.delete_selected_ccursor_range([min_ccursor, max_ccursor])
150 }
151
152 fn delete_paragraph_before_cursor(
153 &mut self,
154 galley: &Galley,
155 cursor_range: &CCursorRange,
156 ) -> CCursor {
157 let [min, max] = cursor_range.sorted_cursors();
158 let min = galley.cursor_begin_of_paragraph(&min);
159 if min == max {
160 self.delete_previous_char(min)
161 } else {
162 self.delete_selected(&CCursorRange::two(min, max))
163 }
164 }
165
166 fn delete_paragraph_after_cursor(
167 &mut self,
168 galley: &Galley,
169 cursor_range: &CCursorRange,
170 ) -> CCursor {
171 let [min, max] = cursor_range.sorted_cursors();
172 let max = galley.cursor_end_of_paragraph(&max);
173 if min == max {
174 self.delete_next_char(min)
175 } else {
176 self.delete_selected(&CCursorRange::two(min, max))
177 }
178 }
179
180 fn type_id(&self) -> std::any::TypeId;
212}
213
214impl TextBuffer for String {
215 fn is_mutable(&self) -> bool {
216 true
217 }
218
219 fn as_str(&self) -> &str {
220 self.as_ref()
221 }
222
223 fn insert_text(&mut self, text: &str, char_index: usize) -> usize {
224 let byte_idx = byte_index_from_char_index(self.as_str(), char_index);
226
227 self.insert_str(byte_idx, text);
229
230 text.chars().count()
231 }
232
233 fn delete_char_range(&mut self, char_range: Range<usize>) {
234 assert!(
235 char_range.start <= char_range.end,
236 "start must be <= end, but got {char_range:?}"
237 );
238
239 let byte_start = byte_index_from_char_index(self.as_str(), char_range.start);
241 let byte_end = byte_index_from_char_index(self.as_str(), char_range.end);
242
243 self.drain(byte_start..byte_end);
245 }
246
247 fn clear(&mut self) {
248 self.clear();
249 }
250
251 fn replace_with(&mut self, text: &str) {
252 text.clone_into(self);
253 }
254
255 fn take(&mut self) -> String {
256 std::mem::take(self)
257 }
258
259 fn type_id(&self) -> std::any::TypeId {
260 std::any::TypeId::of::<Self>()
261 }
262}
263
264impl TextBuffer for Cow<'_, str> {
265 fn is_mutable(&self) -> bool {
266 true
267 }
268
269 fn as_str(&self) -> &str {
270 self.as_ref()
271 }
272
273 fn insert_text(&mut self, text: &str, char_index: usize) -> usize {
274 <String as TextBuffer>::insert_text(self.to_mut(), text, char_index)
275 }
276
277 fn delete_char_range(&mut self, char_range: Range<usize>) {
278 <String as TextBuffer>::delete_char_range(self.to_mut(), char_range);
279 }
280
281 fn clear(&mut self) {
282 <String as TextBuffer>::clear(self.to_mut());
283 }
284
285 fn replace_with(&mut self, text: &str) {
286 *self = Cow::Owned(text.to_owned());
287 }
288
289 fn take(&mut self) -> String {
290 std::mem::take(self).into_owned()
291 }
292
293 fn type_id(&self) -> std::any::TypeId {
294 std::any::TypeId::of::<Cow<'_, str>>()
295 }
296}
297
298impl TextBuffer for &str {
300 fn is_mutable(&self) -> bool {
301 false
302 }
303
304 fn as_str(&self) -> &str {
305 self
306 }
307
308 fn insert_text(&mut self, _text: &str, _ch_idx: usize) -> usize {
309 0
310 }
311
312 fn delete_char_range(&mut self, _ch_range: Range<usize>) {}
313
314 fn type_id(&self) -> std::any::TypeId {
315 std::any::TypeId::of::<&str>()
316 }
317}