redox_core/buffer/text_buffer/
editing.rs1use crate::buffer::{Edit, EditBatchSummary, Pos, Selection, TextBuffer};
13use crate::{SOFT_TAB, SOFT_TAB_WIDTH};
14
15impl TextBuffer {
16 pub fn insert(&mut self, pos: Pos, text: &str) -> Pos {
22 let at = self.pos_to_char(pos);
23 self.rope.insert(at, text);
24
25 let inserted_chars = text.chars().count();
26 self.char_to_pos(at + inserted_chars)
27 }
28
29 pub fn delete_range(&mut self, a: Pos, b: Pos) -> Pos {
33 let start = self.pos_to_char(crate::buffer::util::min_pos(self, a, b));
34 let end = self.pos_to_char(crate::buffer::util::max_pos(self, a, b));
35
36 if start < end {
37 self.rope.remove(start..end);
38 }
39
40 self.char_to_pos(start)
41 }
42
43 pub fn delete_selection(&mut self, sel: Selection) -> (Pos, bool) {
45 if sel.is_empty() {
46 return (self.clamp_pos(sel.cursor), false);
47 }
48
49 let (start, end) = sel.ordered();
50 let new_cursor = self.delete_range(start, end);
51 (new_cursor, true)
52 }
53
54 pub fn backspace(&mut self, sel: Selection) -> Selection {
60 if !sel.is_empty() {
61 let (cursor, _) = self.delete_selection(sel);
62 return Selection::empty(cursor);
63 }
64
65 let cursor = self.clamp_pos(sel.cursor);
66 let at = self.pos_to_char(cursor);
67 if at == 0 {
68 return Selection::empty(cursor);
69 }
70
71 let start = at - 1;
72 self.rope.remove(start..at);
73 let new_cursor = self.char_to_pos(start);
74 Selection::empty(new_cursor)
75 }
76
77 pub fn delete(&mut self, sel: Selection) -> Selection {
83 if !sel.is_empty() {
84 let (cursor, _) = self.delete_selection(sel);
85 return Selection::empty(cursor);
86 }
87
88 let cursor = self.clamp_pos(sel.cursor);
89 let at = self.pos_to_char(cursor);
90 let maxc = self.len_chars();
91
92 if at >= maxc {
93 return Selection::empty(cursor);
94 }
95
96 self.rope.remove(at..at + 1);
97 let new_cursor = self.char_to_pos(at);
98 Selection::empty(new_cursor)
99 }
100
101 pub fn insert_newline(&mut self, sel: Selection) -> Selection {
105 if !sel.is_empty() {
106 let (start, end) = sel.ordered();
107 let cursor = self.delete_range(start, end);
108 let new_cursor = self.insert(cursor, "\n");
109 return Selection::empty(new_cursor);
110 }
111
112 let cursor = self.clamp_pos(sel.cursor);
113 let new_cursor = self.insert(cursor, "\n");
114 Selection::empty(new_cursor)
115 }
116
117 pub fn replace_line_indent(&mut self, line: usize, indent: &str) -> Option<(usize, usize)> {
121 let line = self.clamp_line(line);
122 let text = self.line_string(line);
123 let existing_chars = text
124 .chars()
125 .take_while(|ch| *ch == ' ' || *ch == '\t')
126 .count();
127 let existing: String = text.chars().take(existing_chars).collect();
128 if existing == indent {
129 return None;
130 }
131
132 let _ = self.delete_range(Pos::new(line, 0), Pos::new(line, existing_chars));
133 let _ = self.insert(Pos::new(line, 0), indent);
134 Some((existing_chars, indent.chars().count()))
135 }
136
137 pub fn trim_trailing_whitespace(&mut self) -> bool {
141 let mut changed = false;
142
143 for line in (0..self.len_lines()).rev() {
144 let text = self.line_string(line);
145 let line_len = text.chars().count();
146 let trimmed_len = text.trim_end_matches([' ', '\t']).chars().count();
147 if trimmed_len == line_len {
148 continue;
149 }
150
151 let _ = self.delete_range(Pos::new(line, trimmed_len), Pos::new(line, line_len));
152 changed = true;
153 }
154
155 changed
156 }
157
158 pub fn apply_edit(&mut self, edit: Edit) -> Pos {
162 let maxc = self.len_chars();
163 let start = edit.range.start.min(maxc);
164 let end = edit.range.end.min(maxc);
165 let (start, end) = if start <= end {
166 (start, end)
167 } else {
168 (end, start)
169 };
170
171 if start < end {
172 self.rope.remove(start..end);
173 }
174
175 if !edit.insert.is_empty() {
176 self.rope.insert(start, &edit.insert);
177 let inserted_chars = edit.insert.chars().count();
178 self.char_to_pos(start + inserted_chars)
179 } else {
180 self.char_to_pos(start)
181 }
182 }
183
184 pub fn apply_edits(&mut self, edits: &[Edit]) -> EditBatchSummary {
188 let mut changed_start = usize::MAX;
189 let mut changed_end = 0usize;
190 let mut cursor = self.char_to_pos(self.len_chars());
191
192 for edit in edits {
193 let maxc = self.len_chars();
194 let start = edit.range.start.min(maxc);
195 let end = edit.range.end.min(maxc);
196 let (start, _) = if start <= end {
197 (start, end)
198 } else {
199 (end, start)
200 };
201
202 cursor = self.apply_edit(edit.clone());
203 let cursor_char = self.pos_to_char(cursor);
204
205 changed_start = changed_start.min(start);
206 changed_end = changed_end.max(cursor_char.max(start));
207 }
208
209 if edits.is_empty() {
210 let cursor = self.char_to_pos(self.len_chars());
211 let at = self.pos_to_char(cursor);
212 return EditBatchSummary {
213 changed_range: at..at,
214 cursor,
215 edits_applied: 0,
216 };
217 }
218
219 EditBatchSummary {
220 changed_range: changed_start..changed_end,
221 cursor,
222 edits_applied: edits.len(),
223 }
224 }
225
226 pub fn replace_selection(&mut self, sel: Selection, text: &str) -> Selection {
231 if !sel.is_empty() {
232 let (start, end) = sel.ordered();
233 let cursor = self.delete_range(start, end);
234 let cursor = self.insert(cursor, text);
235 Selection::empty(cursor)
236 } else {
237 let cursor = self.insert(sel.cursor, text);
238 Selection::empty(cursor)
239 }
240 }
241
242 pub fn paste_before(&mut self, cursor: Pos, text: &str, linewise: bool) -> Pos {
249 let insert_pos = if linewise {
250 let line = self.clamp_line(cursor.line);
251 self.clamp_pos(Pos::new(line, 0))
252 } else {
253 self.clamp_pos(cursor)
254 };
255
256 let end_pos = self.insert(insert_pos, text);
257 if linewise {
258 linewise_paste_cursor(end_pos)
259 } else {
260 end_pos
261 }
262 }
263
264 pub fn paste_after(&mut self, cursor: Pos, text: &str, linewise: bool) -> Pos {
273 let insert_pos = if linewise {
274 let line = self.clamp_line(cursor.line);
275 let target_line = (line + 1).min(self.len_lines());
276 self.clamp_pos(Pos::new(target_line, 0))
277 } else {
278 let line = self.clamp_line(cursor.line);
279 let line_len = self.line_len_chars(line);
280 let col = if cursor.col < line_len {
281 cursor.col.saturating_add(1)
282 } else {
283 line_len
284 };
285 Pos::new(line, col)
286 };
287
288 let end_pos = self.insert(insert_pos, text);
289 if linewise {
290 linewise_paste_cursor(end_pos)
291 } else {
292 end_pos
293 }
294 }
295
296 pub fn move_line_range_up_once(
301 &mut self,
302 start_line: usize,
303 end_line_inclusive: usize,
304 ) -> Option<(usize, usize)> {
305 let (start, end) = self.normalized_line_range(start_line, end_line_inclusive);
306 if start == 0 {
307 return None;
308 }
309
310 let first = start - 1;
311 let last = end;
312 let mut entries = self.collect_line_entries(first, last);
313 entries.rotate_left(1);
314 let mut replacement = entries.join("\n");
315 if last + 1 < self.len_lines() {
316 replacement.push('\n');
317 }
318
319 let replace_start = self.line_to_char(first);
320 let replace_end = self.line_full_end_char(last);
321 self.rope.remove(replace_start..replace_end);
322 self.rope.insert(replace_start, &replacement);
323
324 Some((start - 1, end - 1))
325 }
326
327 pub fn move_line_range_up(
332 &mut self,
333 start_line: usize,
334 end_line_inclusive: usize,
335 count: usize,
336 ) -> Option<(usize, usize)> {
337 if count == 0 {
338 return None;
339 }
340
341 let mut current = self.normalized_line_range(start_line, end_line_inclusive);
342 let mut moved = false;
343 for _ in 0..count {
344 let Some(next) = self.move_line_range_up_once(current.0, current.1) else {
345 break;
346 };
347 current = next;
348 moved = true;
349 }
350
351 if moved { Some(current) } else { None }
352 }
353
354 pub fn move_line_range_down_once(
359 &mut self,
360 start_line: usize,
361 end_line_inclusive: usize,
362 ) -> Option<(usize, usize)> {
363 let (start, end) = self.normalized_line_range(start_line, end_line_inclusive);
364 if end + 1 >= self.len_lines() {
365 return None;
366 }
367
368 let first = start;
369 let last = end + 1;
370 let mut entries = self.collect_line_entries(first, last);
371 entries.rotate_right(1);
372 let mut replacement = entries.join("\n");
373 if last + 1 < self.len_lines() {
374 replacement.push('\n');
375 }
376
377 let replace_start = self.line_to_char(first);
378 let replace_end = self.line_full_end_char(last);
379 self.rope.remove(replace_start..replace_end);
380 self.rope.insert(replace_start, &replacement);
381
382 Some((start + 1, end + 1))
383 }
384
385 pub fn move_line_range_down(
390 &mut self,
391 start_line: usize,
392 end_line_inclusive: usize,
393 count: usize,
394 ) -> Option<(usize, usize)> {
395 if count == 0 {
396 return None;
397 }
398
399 let mut current = self.normalized_line_range(start_line, end_line_inclusive);
400 let mut moved = false;
401 for _ in 0..count {
402 let Some(next) = self.move_line_range_down_once(current.0, current.1) else {
403 break;
404 };
405 current = next;
406 moved = true;
407 }
408
409 if moved { Some(current) } else { None }
410 }
411
412 pub fn indent_line_span(
416 &mut self,
417 start_line: usize,
418 end_line_inclusive: usize,
419 count: usize,
420 ) -> Vec<(usize, usize)> {
421 if count == 0 {
422 return Vec::new();
423 }
424
425 let (start, end) = self.normalized_line_range(start_line, end_line_inclusive);
426 let indent = SOFT_TAB.repeat(count);
427 let mut added_by_line = Vec::with_capacity(end.saturating_sub(start) + 1);
428 for line in start..=end {
429 let _ = self.insert(Pos::new(line, 0), &indent);
430 added_by_line.push((line, indent.chars().count()));
431 }
432 added_by_line
433 }
434
435 pub fn outdent_line_span(
440 &mut self,
441 start_line: usize,
442 end_line_inclusive: usize,
443 count: usize,
444 ) -> Vec<(usize, usize)> {
445 if count == 0 {
446 return Vec::new();
447 }
448
449 let (start, end) = self.normalized_line_range(start_line, end_line_inclusive);
450 let mut removed_by_line = Vec::with_capacity(end.saturating_sub(start) + 1);
451
452 for line in start..=end {
453 let text = self.line_string(line);
454 let chars: Vec<char> = text.chars().collect();
455 let mut idx = 0usize;
456 let mut levels_left = count;
457 while levels_left > 0 && idx < chars.len() {
458 if chars[idx] == '\t' {
459 idx += 1;
460 levels_left -= 1;
461 continue;
462 }
463
464 let mut spaces = 0usize;
465 while idx + spaces < chars.len()
466 && chars[idx + spaces] == ' '
467 && spaces < SOFT_TAB_WIDTH
468 {
469 spaces += 1;
470 }
471 if spaces == 0 {
472 break;
473 }
474
475 idx += spaces;
476 levels_left -= 1;
477 }
478
479 if idx > 0 {
480 let _ = self.delete_range(Pos::new(line, 0), Pos::new(line, idx));
481 }
482 removed_by_line.push((line, idx));
483 }
484
485 removed_by_line
486 }
487
488 fn normalized_line_range(
489 &self,
490 start_line: usize,
491 end_line_inclusive: usize,
492 ) -> (usize, usize) {
493 let (start, end) = if start_line <= end_line_inclusive {
494 (start_line, end_line_inclusive)
495 } else {
496 (end_line_inclusive, start_line)
497 };
498 let start = self.clamp_line(start);
499 let end = self.clamp_line(end.max(start));
500 (start, end)
501 }
502
503 fn collect_line_entries(&self, start_line: usize, end_line_inclusive: usize) -> Vec<String> {
504 let mut entries = Vec::with_capacity(end_line_inclusive.saturating_sub(start_line) + 1);
505 for line in start_line..=end_line_inclusive {
506 entries.push(self.line_string(line));
507 }
508 entries
509 }
510}
511
512fn linewise_paste_cursor(end_pos: Pos) -> Pos {
513 Pos::new(end_pos.line.saturating_sub(1), 0)
514}