redox_core/buffer/text_buffer/
editing.rs1use crate::buffer::{Edit, EditBatchSummary, Pos, Selection, TextBuffer};
13
14impl TextBuffer {
15 pub fn insert(&mut self, pos: Pos, text: &str) -> Pos {
21 let at = self.pos_to_char(pos);
22 self.rope.insert(at, text);
23
24 let inserted_chars = text.chars().count();
25 self.char_to_pos(at + inserted_chars)
26 }
27
28 pub fn delete_range(&mut self, a: Pos, b: Pos) -> Pos {
32 let start = self.pos_to_char(crate::buffer::util::min_pos(self, a, b));
33 let end = self.pos_to_char(crate::buffer::util::max_pos(self, a, b));
34
35 if start < end {
36 self.rope.remove(start..end);
37 }
38
39 self.char_to_pos(start)
40 }
41
42 pub fn delete_selection(&mut self, sel: Selection) -> (Pos, bool) {
44 if sel.is_empty() {
45 return (self.clamp_pos(sel.cursor), false);
46 }
47
48 let (start, end) = sel.ordered();
49 let new_cursor = self.delete_range(start, end);
50 (new_cursor, true)
51 }
52
53 pub fn backspace(&mut self, sel: Selection) -> Selection {
59 if !sel.is_empty() {
60 let (cursor, _) = self.delete_selection(sel);
61 return Selection::empty(cursor);
62 }
63
64 let cursor = self.clamp_pos(sel.cursor);
65 let at = self.pos_to_char(cursor);
66 if at == 0 {
67 return Selection::empty(cursor);
68 }
69
70 let start = at - 1;
71 self.rope.remove(start..at);
72 let new_cursor = self.char_to_pos(start);
73 Selection::empty(new_cursor)
74 }
75
76 pub fn delete(&mut self, sel: Selection) -> Selection {
82 if !sel.is_empty() {
83 let (cursor, _) = self.delete_selection(sel);
84 return Selection::empty(cursor);
85 }
86
87 let cursor = self.clamp_pos(sel.cursor);
88 let at = self.pos_to_char(cursor);
89 let maxc = self.len_chars();
90
91 if at >= maxc {
92 return Selection::empty(cursor);
93 }
94
95 self.rope.remove(at..at + 1);
96 let new_cursor = self.char_to_pos(at);
97 Selection::empty(new_cursor)
98 }
99
100 pub fn insert_newline(&mut self, sel: Selection) -> Selection {
104 if !sel.is_empty() {
105 let (start, end) = sel.ordered();
106 let cursor = self.delete_range(start, end);
107 let new_cursor = self.insert(cursor, "\n");
108 return Selection::empty(new_cursor);
109 }
110
111 let cursor = self.clamp_pos(sel.cursor);
112 let new_cursor = self.insert(cursor, "\n");
113 Selection::empty(new_cursor)
114 }
115
116 pub fn apply_edit(&mut self, edit: Edit) -> Pos {
120 let maxc = self.len_chars();
121 let start = edit.range.start.min(maxc);
122 let end = edit.range.end.min(maxc);
123 let (start, end) = if start <= end {
124 (start, end)
125 } else {
126 (end, start)
127 };
128
129 if start < end {
130 self.rope.remove(start..end);
131 }
132
133 if !edit.insert.is_empty() {
134 self.rope.insert(start, &edit.insert);
135 let inserted_chars = edit.insert.chars().count();
136 self.char_to_pos(start + inserted_chars)
137 } else {
138 self.char_to_pos(start)
139 }
140 }
141
142 pub fn apply_edits(&mut self, edits: &[Edit]) -> EditBatchSummary {
146 let mut changed_start = usize::MAX;
147 let mut changed_end = 0usize;
148 let mut cursor = self.char_to_pos(self.len_chars());
149
150 for edit in edits {
151 let maxc = self.len_chars();
152 let start = edit.range.start.min(maxc);
153 let end = edit.range.end.min(maxc);
154 let (start, _) = if start <= end {
155 (start, end)
156 } else {
157 (end, start)
158 };
159
160 cursor = self.apply_edit(edit.clone());
161 let cursor_char = self.pos_to_char(cursor);
162
163 changed_start = changed_start.min(start);
164 changed_end = changed_end.max(cursor_char.max(start));
165 }
166
167 if edits.is_empty() {
168 let cursor = self.char_to_pos(self.len_chars());
169 let at = self.pos_to_char(cursor);
170 return EditBatchSummary {
171 changed_range: at..at,
172 cursor,
173 edits_applied: 0,
174 };
175 }
176
177 EditBatchSummary {
178 changed_range: changed_start..changed_end,
179 cursor,
180 edits_applied: edits.len(),
181 }
182 }
183
184 pub fn replace_selection(&mut self, sel: Selection, text: &str) -> Selection {
189 if !sel.is_empty() {
190 let (start, end) = sel.ordered();
191 let cursor = self.delete_range(start, end);
192 let cursor = self.insert(cursor, text);
193 Selection::empty(cursor)
194 } else {
195 let cursor = self.insert(sel.cursor, text);
196 Selection::empty(cursor)
197 }
198 }
199
200 pub fn paste_before(&mut self, cursor: Pos, text: &str, linewise: bool) -> Pos {
207 let insert_pos = if linewise {
208 let line = self.clamp_line(cursor.line);
209 self.clamp_pos(Pos::new(line, 0))
210 } else {
211 self.clamp_pos(cursor)
212 };
213
214 let end_pos = self.insert(insert_pos, text);
215 if linewise { insert_pos } else { end_pos }
216 }
217
218 pub fn paste_after(&mut self, cursor: Pos, text: &str, linewise: bool) -> Pos {
227 let insert_pos = if linewise {
228 let line = self.clamp_line(cursor.line);
229 let target_line = (line + 1).min(self.len_lines());
230 self.clamp_pos(Pos::new(target_line, 0))
231 } else {
232 let line = self.clamp_line(cursor.line);
233 let line_len = self.line_len_chars(line);
234 let col = if cursor.col < line_len {
235 cursor.col.saturating_add(1)
236 } else {
237 line_len
238 };
239 Pos::new(line, col)
240 };
241
242 let end_pos = self.insert(insert_pos, text);
243 if linewise { insert_pos } else { end_pos }
244 }
245
246 pub fn move_line_range_up_once(
251 &mut self,
252 start_line: usize,
253 end_line_inclusive: usize,
254 ) -> Option<(usize, usize)> {
255 let (start, end) = self.normalized_line_range(start_line, end_line_inclusive);
256 if start == 0 {
257 return None;
258 }
259
260 let first = start - 1;
261 let last = end;
262 let mut entries = self.collect_line_entries(first, last);
263 entries.rotate_left(1);
264 let mut replacement = entries.join("\n");
265 if last + 1 < self.len_lines() {
266 replacement.push('\n');
267 }
268
269 let replace_start = self.line_to_char(first);
270 let replace_end = self.line_full_end_char(last);
271 self.rope.remove(replace_start..replace_end);
272 self.rope.insert(replace_start, &replacement);
273
274 Some((start - 1, end - 1))
275 }
276
277 pub fn move_line_range_up(
282 &mut self,
283 start_line: usize,
284 end_line_inclusive: usize,
285 count: usize,
286 ) -> Option<(usize, usize)> {
287 if count == 0 {
288 return None;
289 }
290
291 let mut current = self.normalized_line_range(start_line, end_line_inclusive);
292 let mut moved = false;
293 for _ in 0..count {
294 let Some(next) = self.move_line_range_up_once(current.0, current.1) else {
295 break;
296 };
297 current = next;
298 moved = true;
299 }
300
301 if moved { Some(current) } else { None }
302 }
303
304 pub fn move_line_range_down_once(
309 &mut self,
310 start_line: usize,
311 end_line_inclusive: usize,
312 ) -> Option<(usize, usize)> {
313 let (start, end) = self.normalized_line_range(start_line, end_line_inclusive);
314 if end + 1 >= self.len_lines() {
315 return None;
316 }
317
318 let first = start;
319 let last = end + 1;
320 let mut entries = self.collect_line_entries(first, last);
321 entries.rotate_right(1);
322 let mut replacement = entries.join("\n");
323 if last + 1 < self.len_lines() {
324 replacement.push('\n');
325 }
326
327 let replace_start = self.line_to_char(first);
328 let replace_end = self.line_full_end_char(last);
329 self.rope.remove(replace_start..replace_end);
330 self.rope.insert(replace_start, &replacement);
331
332 Some((start + 1, end + 1))
333 }
334
335 pub fn move_line_range_down(
340 &mut self,
341 start_line: usize,
342 end_line_inclusive: usize,
343 count: usize,
344 ) -> Option<(usize, usize)> {
345 if count == 0 {
346 return None;
347 }
348
349 let mut current = self.normalized_line_range(start_line, end_line_inclusive);
350 let mut moved = false;
351 for _ in 0..count {
352 let Some(next) = self.move_line_range_down_once(current.0, current.1) else {
353 break;
354 };
355 current = next;
356 moved = true;
357 }
358
359 if moved { Some(current) } else { None }
360 }
361
362 pub fn indent_line_span(
366 &mut self,
367 start_line: usize,
368 end_line_inclusive: usize,
369 count: usize,
370 ) -> Vec<(usize, usize)> {
371 if count == 0 {
372 return Vec::new();
373 }
374
375 let (start, end) = self.normalized_line_range(start_line, end_line_inclusive);
376 let indent = "\t".repeat(count);
377 let mut added_by_line = Vec::with_capacity(end.saturating_sub(start) + 1);
378 for line in start..=end {
379 let _ = self.insert(Pos::new(line, 0), &indent);
380 added_by_line.push((line, count));
381 }
382 added_by_line
383 }
384
385 pub fn outdent_line_span(
390 &mut self,
391 start_line: usize,
392 end_line_inclusive: usize,
393 count: usize,
394 ) -> Vec<(usize, usize)> {
395 const TAB_STOP: usize = 4;
396
397 if count == 0 {
398 return Vec::new();
399 }
400
401 let (start, end) = self.normalized_line_range(start_line, end_line_inclusive);
402 let mut removed_by_line = Vec::with_capacity(end.saturating_sub(start) + 1);
403
404 for line in start..=end {
405 let text = self.line_string(line);
406 let chars: Vec<char> = text.chars().collect();
407 let mut idx = 0usize;
408 let mut levels_left = count;
409 while levels_left > 0 && idx < chars.len() {
410 if chars[idx] == '\t' {
411 idx += 1;
412 levels_left -= 1;
413 continue;
414 }
415
416 let mut spaces = 0usize;
417 while idx + spaces < chars.len() && chars[idx + spaces] == ' ' && spaces < TAB_STOP
418 {
419 spaces += 1;
420 }
421 if spaces == 0 {
422 break;
423 }
424
425 idx += spaces;
426 levels_left -= 1;
427 }
428
429 if idx > 0 {
430 let _ = self.delete_range(Pos::new(line, 0), Pos::new(line, idx));
431 }
432 removed_by_line.push((line, idx));
433 }
434
435 removed_by_line
436 }
437
438 fn normalized_line_range(
439 &self,
440 start_line: usize,
441 end_line_inclusive: usize,
442 ) -> (usize, usize) {
443 let (start, end) = if start_line <= end_line_inclusive {
444 (start_line, end_line_inclusive)
445 } else {
446 (end_line_inclusive, start_line)
447 };
448 let start = self.clamp_line(start);
449 let end = self.clamp_line(end.max(start));
450 (start, end)
451 }
452
453 fn collect_line_entries(&self, start_line: usize, end_line_inclusive: usize) -> Vec<String> {
454 let mut entries = Vec::with_capacity(end_line_inclusive.saturating_sub(start_line) + 1);
455 for line in start_line..=end_line_inclusive {
456 entries.push(self.line_string(line));
457 }
458 entries
459 }
460}