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 replace_line_indent(&mut self, line: usize, indent: &str) -> Option<(usize, usize)> {
120 let line = self.clamp_line(line);
121 let text = self.line_string(line);
122 let existing_chars = text
123 .chars()
124 .take_while(|ch| *ch == ' ' || *ch == '\t')
125 .count();
126 let existing: String = text.chars().take(existing_chars).collect();
127 if existing == indent {
128 return None;
129 }
130
131 let _ = self.delete_range(Pos::new(line, 0), Pos::new(line, existing_chars));
132 let _ = self.insert(Pos::new(line, 0), indent);
133 Some((existing_chars, indent.chars().count()))
134 }
135
136 pub fn trim_trailing_whitespace(&mut self) -> bool {
140 let mut changed = false;
141
142 for line in (0..self.len_lines()).rev() {
143 let text = self.line_string(line);
144 let line_len = text.chars().count();
145 let trimmed_len = text.trim_end_matches([' ', '\t']).chars().count();
146 if trimmed_len == line_len {
147 continue;
148 }
149
150 let _ = self.delete_range(Pos::new(line, trimmed_len), Pos::new(line, line_len));
151 changed = true;
152 }
153
154 changed
155 }
156
157 pub fn apply_edit(&mut self, edit: Edit) -> Pos {
161 let maxc = self.len_chars();
162 let start = edit.range.start.min(maxc);
163 let end = edit.range.end.min(maxc);
164 let (start, end) = if start <= end {
165 (start, end)
166 } else {
167 (end, start)
168 };
169
170 if start < end {
171 self.rope.remove(start..end);
172 }
173
174 if !edit.insert.is_empty() {
175 self.rope.insert(start, &edit.insert);
176 let inserted_chars = edit.insert.chars().count();
177 self.char_to_pos(start + inserted_chars)
178 } else {
179 self.char_to_pos(start)
180 }
181 }
182
183 pub fn apply_edits(&mut self, edits: &[Edit]) -> EditBatchSummary {
187 let mut changed_start = usize::MAX;
188 let mut changed_end = 0usize;
189 let mut cursor = self.char_to_pos(self.len_chars());
190
191 for edit in edits {
192 let maxc = self.len_chars();
193 let start = edit.range.start.min(maxc);
194 let end = edit.range.end.min(maxc);
195 let (start, _) = if start <= end {
196 (start, end)
197 } else {
198 (end, start)
199 };
200
201 cursor = self.apply_edit(edit.clone());
202 let cursor_char = self.pos_to_char(cursor);
203
204 changed_start = changed_start.min(start);
205 changed_end = changed_end.max(cursor_char.max(start));
206 }
207
208 if edits.is_empty() {
209 let cursor = self.char_to_pos(self.len_chars());
210 let at = self.pos_to_char(cursor);
211 return EditBatchSummary {
212 changed_range: at..at,
213 cursor,
214 edits_applied: 0,
215 };
216 }
217
218 EditBatchSummary {
219 changed_range: changed_start..changed_end,
220 cursor,
221 edits_applied: edits.len(),
222 }
223 }
224
225 pub fn replace_selection(&mut self, sel: Selection, text: &str) -> Selection {
230 if !sel.is_empty() {
231 let (start, end) = sel.ordered();
232 let cursor = self.delete_range(start, end);
233 let cursor = self.insert(cursor, text);
234 Selection::empty(cursor)
235 } else {
236 let cursor = self.insert(sel.cursor, text);
237 Selection::empty(cursor)
238 }
239 }
240
241 pub fn paste_before(&mut self, cursor: Pos, text: &str, linewise: bool) -> Pos {
248 let insert_pos = if linewise {
249 let line = self.clamp_line(cursor.line);
250 self.clamp_pos(Pos::new(line, 0))
251 } else {
252 self.clamp_pos(cursor)
253 };
254
255 let end_pos = self.insert(insert_pos, text);
256 if linewise {
257 linewise_paste_cursor(end_pos)
258 } else {
259 end_pos
260 }
261 }
262
263 pub fn paste_after(&mut self, cursor: Pos, text: &str, linewise: bool) -> Pos {
272 let insert_pos = if linewise {
273 let line = self.clamp_line(cursor.line);
274 let target_line = (line + 1).min(self.len_lines());
275 self.clamp_pos(Pos::new(target_line, 0))
276 } else {
277 let line = self.clamp_line(cursor.line);
278 let line_len = self.line_len_chars(line);
279 let col = if cursor.col < line_len {
280 cursor.col.saturating_add(1)
281 } else {
282 line_len
283 };
284 Pos::new(line, col)
285 };
286
287 let end_pos = self.insert(insert_pos, text);
288 if linewise {
289 linewise_paste_cursor(end_pos)
290 } else {
291 end_pos
292 }
293 }
294
295 pub fn move_line_range_up_once(
300 &mut self,
301 start_line: usize,
302 end_line_inclusive: usize,
303 ) -> Option<(usize, usize)> {
304 let (start, end) = self.normalized_line_range(start_line, end_line_inclusive);
305 if start == 0 {
306 return None;
307 }
308
309 let first = start - 1;
310 let last = end;
311 let mut entries = self.collect_line_entries(first, last);
312 entries.rotate_left(1);
313 let mut replacement = entries.join("\n");
314 if last + 1 < self.len_lines() {
315 replacement.push('\n');
316 }
317
318 let replace_start = self.line_to_char(first);
319 let replace_end = self.line_full_end_char(last);
320 self.rope.remove(replace_start..replace_end);
321 self.rope.insert(replace_start, &replacement);
322
323 Some((start - 1, end - 1))
324 }
325
326 pub fn move_line_range_up(
331 &mut self,
332 start_line: usize,
333 end_line_inclusive: usize,
334 count: usize,
335 ) -> Option<(usize, usize)> {
336 if count == 0 {
337 return None;
338 }
339
340 let mut current = self.normalized_line_range(start_line, end_line_inclusive);
341 let mut moved = false;
342 for _ in 0..count {
343 let Some(next) = self.move_line_range_up_once(current.0, current.1) else {
344 break;
345 };
346 current = next;
347 moved = true;
348 }
349
350 if moved { Some(current) } else { None }
351 }
352
353 pub fn move_line_range_down_once(
358 &mut self,
359 start_line: usize,
360 end_line_inclusive: usize,
361 ) -> Option<(usize, usize)> {
362 let (start, end) = self.normalized_line_range(start_line, end_line_inclusive);
363 if end + 1 >= self.len_lines() {
364 return None;
365 }
366
367 let first = start;
368 let last = end + 1;
369 let mut entries = self.collect_line_entries(first, last);
370 entries.rotate_right(1);
371 let mut replacement = entries.join("\n");
372 if last + 1 < self.len_lines() {
373 replacement.push('\n');
374 }
375
376 let replace_start = self.line_to_char(first);
377 let replace_end = self.line_full_end_char(last);
378 self.rope.remove(replace_start..replace_end);
379 self.rope.insert(replace_start, &replacement);
380
381 Some((start + 1, end + 1))
382 }
383
384 pub fn move_line_range_down(
389 &mut self,
390 start_line: usize,
391 end_line_inclusive: usize,
392 count: usize,
393 ) -> Option<(usize, usize)> {
394 if count == 0 {
395 return None;
396 }
397
398 let mut current = self.normalized_line_range(start_line, end_line_inclusive);
399 let mut moved = false;
400 for _ in 0..count {
401 let Some(next) = self.move_line_range_down_once(current.0, current.1) else {
402 break;
403 };
404 current = next;
405 moved = true;
406 }
407
408 if moved { Some(current) } else { None }
409 }
410
411 pub fn indent_line_span(
415 &mut self,
416 start_line: usize,
417 end_line_inclusive: usize,
418 count: usize,
419 ) -> Vec<(usize, usize)> {
420 if count == 0 {
421 return Vec::new();
422 }
423
424 let (start, end) = self.normalized_line_range(start_line, end_line_inclusive);
425 let indent = "\t".repeat(count);
426 let mut added_by_line = Vec::with_capacity(end.saturating_sub(start) + 1);
427 for line in start..=end {
428 let _ = self.insert(Pos::new(line, 0), &indent);
429 added_by_line.push((line, count));
430 }
431 added_by_line
432 }
433
434 pub fn outdent_line_span(
439 &mut self,
440 start_line: usize,
441 end_line_inclusive: usize,
442 count: usize,
443 ) -> Vec<(usize, usize)> {
444 const TAB_STOP: usize = 4;
445
446 if count == 0 {
447 return Vec::new();
448 }
449
450 let (start, end) = self.normalized_line_range(start_line, end_line_inclusive);
451 let mut removed_by_line = Vec::with_capacity(end.saturating_sub(start) + 1);
452
453 for line in start..=end {
454 let text = self.line_string(line);
455 let chars: Vec<char> = text.chars().collect();
456 let mut idx = 0usize;
457 let mut levels_left = count;
458 while levels_left > 0 && idx < chars.len() {
459 if chars[idx] == '\t' {
460 idx += 1;
461 levels_left -= 1;
462 continue;
463 }
464
465 let mut spaces = 0usize;
466 while idx + spaces < chars.len() && chars[idx + spaces] == ' ' && spaces < TAB_STOP
467 {
468 spaces += 1;
469 }
470 if spaces == 0 {
471 break;
472 }
473
474 idx += spaces;
475 levels_left -= 1;
476 }
477
478 if idx > 0 {
479 let _ = self.delete_range(Pos::new(line, 0), Pos::new(line, idx));
480 }
481 removed_by_line.push((line, idx));
482 }
483
484 removed_by_line
485 }
486
487 fn normalized_line_range(
488 &self,
489 start_line: usize,
490 end_line_inclusive: usize,
491 ) -> (usize, usize) {
492 let (start, end) = if start_line <= end_line_inclusive {
493 (start_line, end_line_inclusive)
494 } else {
495 (end_line_inclusive, start_line)
496 };
497 let start = self.clamp_line(start);
498 let end = self.clamp_line(end.max(start));
499 (start, end)
500 }
501
502 fn collect_line_entries(&self, start_line: usize, end_line_inclusive: usize) -> Vec<String> {
503 let mut entries = Vec::with_capacity(end_line_inclusive.saturating_sub(start_line) + 1);
504 for line in start_line..=end_line_inclusive {
505 entries.push(self.line_string(line));
506 }
507 entries
508 }
509}
510
511fn linewise_paste_cursor(end_pos: Pos) -> Pos {
512 Pos::new(end_pos.line.saturating_sub(1), 0)
513}