egui/widgets/text_edit/
text_buffer.rs1use 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
19pub trait TextBuffer {
24 fn is_mutable(&self) -> bool;
26
27 fn as_str(&self) -> &str;
29
30 fn insert_text(&mut self, text: &str, char_index: usize) -> usize;
38
39 fn delete_char_range(&mut self, char_range: Range<usize>);
44
45 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 fn clear(&mut self) {
56 self.delete_char_range(0..self.as_str().len());
57 }
58
59 fn replace_with(&mut self, text: &str) {
61 self.clear();
62 self.insert_text(text, 0);
63 }
64
65 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 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, 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 let byte_idx = byte_index_from_char_index(self.as_str(), char_index);
200
201 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 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 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
261impl 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}