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 { insert_pos } else { end_pos }
257 }
258
259 pub fn paste_after(&mut self, cursor: Pos, text: &str, linewise: bool) -> Pos {
268 let insert_pos = if linewise {
269 let line = self.clamp_line(cursor.line);
270 let target_line = (line + 1).min(self.len_lines());
271 self.clamp_pos(Pos::new(target_line, 0))
272 } else {
273 let line = self.clamp_line(cursor.line);
274 let line_len = self.line_len_chars(line);
275 let col = if cursor.col < line_len {
276 cursor.col.saturating_add(1)
277 } else {
278 line_len
279 };
280 Pos::new(line, col)
281 };
282
283 let end_pos = self.insert(insert_pos, text);
284 if linewise { insert_pos } else { end_pos }
285 }
286
287 pub fn move_line_range_up_once(
292 &mut self,
293 start_line: usize,
294 end_line_inclusive: usize,
295 ) -> Option<(usize, usize)> {
296 let (start, end) = self.normalized_line_range(start_line, end_line_inclusive);
297 if start == 0 {
298 return None;
299 }
300
301 let first = start - 1;
302 let last = end;
303 let mut entries = self.collect_line_entries(first, last);
304 entries.rotate_left(1);
305 let mut replacement = entries.join("\n");
306 if last + 1 < self.len_lines() {
307 replacement.push('\n');
308 }
309
310 let replace_start = self.line_to_char(first);
311 let replace_end = self.line_full_end_char(last);
312 self.rope.remove(replace_start..replace_end);
313 self.rope.insert(replace_start, &replacement);
314
315 Some((start - 1, end - 1))
316 }
317
318 pub fn move_line_range_up(
323 &mut self,
324 start_line: usize,
325 end_line_inclusive: usize,
326 count: usize,
327 ) -> Option<(usize, usize)> {
328 if count == 0 {
329 return None;
330 }
331
332 let mut current = self.normalized_line_range(start_line, end_line_inclusive);
333 let mut moved = false;
334 for _ in 0..count {
335 let Some(next) = self.move_line_range_up_once(current.0, current.1) else {
336 break;
337 };
338 current = next;
339 moved = true;
340 }
341
342 if moved { Some(current) } else { None }
343 }
344
345 pub fn move_line_range_down_once(
350 &mut self,
351 start_line: usize,
352 end_line_inclusive: usize,
353 ) -> Option<(usize, usize)> {
354 let (start, end) = self.normalized_line_range(start_line, end_line_inclusive);
355 if end + 1 >= self.len_lines() {
356 return None;
357 }
358
359 let first = start;
360 let last = end + 1;
361 let mut entries = self.collect_line_entries(first, last);
362 entries.rotate_right(1);
363 let mut replacement = entries.join("\n");
364 if last + 1 < self.len_lines() {
365 replacement.push('\n');
366 }
367
368 let replace_start = self.line_to_char(first);
369 let replace_end = self.line_full_end_char(last);
370 self.rope.remove(replace_start..replace_end);
371 self.rope.insert(replace_start, &replacement);
372
373 Some((start + 1, end + 1))
374 }
375
376 pub fn move_line_range_down(
381 &mut self,
382 start_line: usize,
383 end_line_inclusive: usize,
384 count: usize,
385 ) -> Option<(usize, usize)> {
386 if count == 0 {
387 return None;
388 }
389
390 let mut current = self.normalized_line_range(start_line, end_line_inclusive);
391 let mut moved = false;
392 for _ in 0..count {
393 let Some(next) = self.move_line_range_down_once(current.0, current.1) else {
394 break;
395 };
396 current = next;
397 moved = true;
398 }
399
400 if moved { Some(current) } else { None }
401 }
402
403 pub fn indent_line_span(
407 &mut self,
408 start_line: usize,
409 end_line_inclusive: usize,
410 count: usize,
411 ) -> Vec<(usize, usize)> {
412 if count == 0 {
413 return Vec::new();
414 }
415
416 let (start, end) = self.normalized_line_range(start_line, end_line_inclusive);
417 let indent = "\t".repeat(count);
418 let mut added_by_line = Vec::with_capacity(end.saturating_sub(start) + 1);
419 for line in start..=end {
420 let _ = self.insert(Pos::new(line, 0), &indent);
421 added_by_line.push((line, count));
422 }
423 added_by_line
424 }
425
426 pub fn outdent_line_span(
431 &mut self,
432 start_line: usize,
433 end_line_inclusive: usize,
434 count: usize,
435 ) -> Vec<(usize, usize)> {
436 const TAB_STOP: usize = 4;
437
438 if count == 0 {
439 return Vec::new();
440 }
441
442 let (start, end) = self.normalized_line_range(start_line, end_line_inclusive);
443 let mut removed_by_line = Vec::with_capacity(end.saturating_sub(start) + 1);
444
445 for line in start..=end {
446 let text = self.line_string(line);
447 let chars: Vec<char> = text.chars().collect();
448 let mut idx = 0usize;
449 let mut levels_left = count;
450 while levels_left > 0 && idx < chars.len() {
451 if chars[idx] == '\t' {
452 idx += 1;
453 levels_left -= 1;
454 continue;
455 }
456
457 let mut spaces = 0usize;
458 while idx + spaces < chars.len() && chars[idx + spaces] == ' ' && spaces < TAB_STOP
459 {
460 spaces += 1;
461 }
462 if spaces == 0 {
463 break;
464 }
465
466 idx += spaces;
467 levels_left -= 1;
468 }
469
470 if idx > 0 {
471 let _ = self.delete_range(Pos::new(line, 0), Pos::new(line, idx));
472 }
473 removed_by_line.push((line, idx));
474 }
475
476 removed_by_line
477 }
478
479 fn normalized_line_range(
480 &self,
481 start_line: usize,
482 end_line_inclusive: usize,
483 ) -> (usize, usize) {
484 let (start, end) = if start_line <= end_line_inclusive {
485 (start_line, end_line_inclusive)
486 } else {
487 (end_line_inclusive, start_line)
488 };
489 let start = self.clamp_line(start);
490 let end = self.clamp_line(end.max(start));
491 (start, end)
492 }
493
494 fn collect_line_entries(&self, start_line: usize, end_line_inclusive: usize) -> Vec<String> {
495 let mut entries = Vec::with_capacity(end_line_inclusive.saturating_sub(start_line) + 1);
496 for line in start_line..=end_line_inclusive {
497 entries.push(self.line_string(line));
498 }
499 entries
500 }
501}