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 apply_edit(&mut self, edit: Edit) -> Pos {
140 let maxc = self.len_chars();
141 let start = edit.range.start.min(maxc);
142 let end = edit.range.end.min(maxc);
143 let (start, end) = if start <= end {
144 (start, end)
145 } else {
146 (end, start)
147 };
148
149 if start < end {
150 self.rope.remove(start..end);
151 }
152
153 if !edit.insert.is_empty() {
154 self.rope.insert(start, &edit.insert);
155 let inserted_chars = edit.insert.chars().count();
156 self.char_to_pos(start + inserted_chars)
157 } else {
158 self.char_to_pos(start)
159 }
160 }
161
162 pub fn apply_edits(&mut self, edits: &[Edit]) -> EditBatchSummary {
166 let mut changed_start = usize::MAX;
167 let mut changed_end = 0usize;
168 let mut cursor = self.char_to_pos(self.len_chars());
169
170 for edit in edits {
171 let maxc = self.len_chars();
172 let start = edit.range.start.min(maxc);
173 let end = edit.range.end.min(maxc);
174 let (start, _) = if start <= end {
175 (start, end)
176 } else {
177 (end, start)
178 };
179
180 cursor = self.apply_edit(edit.clone());
181 let cursor_char = self.pos_to_char(cursor);
182
183 changed_start = changed_start.min(start);
184 changed_end = changed_end.max(cursor_char.max(start));
185 }
186
187 if edits.is_empty() {
188 let cursor = self.char_to_pos(self.len_chars());
189 let at = self.pos_to_char(cursor);
190 return EditBatchSummary {
191 changed_range: at..at,
192 cursor,
193 edits_applied: 0,
194 };
195 }
196
197 EditBatchSummary {
198 changed_range: changed_start..changed_end,
199 cursor,
200 edits_applied: edits.len(),
201 }
202 }
203
204 pub fn replace_selection(&mut self, sel: Selection, text: &str) -> Selection {
209 if !sel.is_empty() {
210 let (start, end) = sel.ordered();
211 let cursor = self.delete_range(start, end);
212 let cursor = self.insert(cursor, text);
213 Selection::empty(cursor)
214 } else {
215 let cursor = self.insert(sel.cursor, text);
216 Selection::empty(cursor)
217 }
218 }
219
220 pub fn paste_before(&mut self, cursor: Pos, text: &str, linewise: bool) -> Pos {
227 let insert_pos = if linewise {
228 let line = self.clamp_line(cursor.line);
229 self.clamp_pos(Pos::new(line, 0))
230 } else {
231 self.clamp_pos(cursor)
232 };
233
234 let end_pos = self.insert(insert_pos, text);
235 if linewise { insert_pos } else { end_pos }
236 }
237
238 pub fn paste_after(&mut self, cursor: Pos, text: &str, linewise: bool) -> Pos {
247 let insert_pos = if linewise {
248 let line = self.clamp_line(cursor.line);
249 let target_line = (line + 1).min(self.len_lines());
250 self.clamp_pos(Pos::new(target_line, 0))
251 } else {
252 let line = self.clamp_line(cursor.line);
253 let line_len = self.line_len_chars(line);
254 let col = if cursor.col < line_len {
255 cursor.col.saturating_add(1)
256 } else {
257 line_len
258 };
259 Pos::new(line, col)
260 };
261
262 let end_pos = self.insert(insert_pos, text);
263 if linewise { insert_pos } else { end_pos }
264 }
265
266 pub fn move_line_range_up_once(
271 &mut self,
272 start_line: usize,
273 end_line_inclusive: usize,
274 ) -> Option<(usize, usize)> {
275 let (start, end) = self.normalized_line_range(start_line, end_line_inclusive);
276 if start == 0 {
277 return None;
278 }
279
280 let first = start - 1;
281 let last = end;
282 let mut entries = self.collect_line_entries(first, last);
283 entries.rotate_left(1);
284 let mut replacement = entries.join("\n");
285 if last + 1 < self.len_lines() {
286 replacement.push('\n');
287 }
288
289 let replace_start = self.line_to_char(first);
290 let replace_end = self.line_full_end_char(last);
291 self.rope.remove(replace_start..replace_end);
292 self.rope.insert(replace_start, &replacement);
293
294 Some((start - 1, end - 1))
295 }
296
297 pub fn move_line_range_up(
302 &mut self,
303 start_line: usize,
304 end_line_inclusive: usize,
305 count: usize,
306 ) -> Option<(usize, usize)> {
307 if count == 0 {
308 return None;
309 }
310
311 let mut current = self.normalized_line_range(start_line, end_line_inclusive);
312 let mut moved = false;
313 for _ in 0..count {
314 let Some(next) = self.move_line_range_up_once(current.0, current.1) else {
315 break;
316 };
317 current = next;
318 moved = true;
319 }
320
321 if moved { Some(current) } else { None }
322 }
323
324 pub fn move_line_range_down_once(
329 &mut self,
330 start_line: usize,
331 end_line_inclusive: usize,
332 ) -> Option<(usize, usize)> {
333 let (start, end) = self.normalized_line_range(start_line, end_line_inclusive);
334 if end + 1 >= self.len_lines() {
335 return None;
336 }
337
338 let first = start;
339 let last = end + 1;
340 let mut entries = self.collect_line_entries(first, last);
341 entries.rotate_right(1);
342 let mut replacement = entries.join("\n");
343 if last + 1 < self.len_lines() {
344 replacement.push('\n');
345 }
346
347 let replace_start = self.line_to_char(first);
348 let replace_end = self.line_full_end_char(last);
349 self.rope.remove(replace_start..replace_end);
350 self.rope.insert(replace_start, &replacement);
351
352 Some((start + 1, end + 1))
353 }
354
355 pub fn move_line_range_down(
360 &mut self,
361 start_line: usize,
362 end_line_inclusive: usize,
363 count: usize,
364 ) -> Option<(usize, usize)> {
365 if count == 0 {
366 return None;
367 }
368
369 let mut current = self.normalized_line_range(start_line, end_line_inclusive);
370 let mut moved = false;
371 for _ in 0..count {
372 let Some(next) = self.move_line_range_down_once(current.0, current.1) else {
373 break;
374 };
375 current = next;
376 moved = true;
377 }
378
379 if moved { Some(current) } else { None }
380 }
381
382 pub fn indent_line_span(
386 &mut self,
387 start_line: usize,
388 end_line_inclusive: usize,
389 count: usize,
390 ) -> Vec<(usize, usize)> {
391 if count == 0 {
392 return Vec::new();
393 }
394
395 let (start, end) = self.normalized_line_range(start_line, end_line_inclusive);
396 let indent = "\t".repeat(count);
397 let mut added_by_line = Vec::with_capacity(end.saturating_sub(start) + 1);
398 for line in start..=end {
399 let _ = self.insert(Pos::new(line, 0), &indent);
400 added_by_line.push((line, count));
401 }
402 added_by_line
403 }
404
405 pub fn outdent_line_span(
410 &mut self,
411 start_line: usize,
412 end_line_inclusive: usize,
413 count: usize,
414 ) -> Vec<(usize, usize)> {
415 const TAB_STOP: usize = 4;
416
417 if count == 0 {
418 return Vec::new();
419 }
420
421 let (start, end) = self.normalized_line_range(start_line, end_line_inclusive);
422 let mut removed_by_line = Vec::with_capacity(end.saturating_sub(start) + 1);
423
424 for line in start..=end {
425 let text = self.line_string(line);
426 let chars: Vec<char> = text.chars().collect();
427 let mut idx = 0usize;
428 let mut levels_left = count;
429 while levels_left > 0 && idx < chars.len() {
430 if chars[idx] == '\t' {
431 idx += 1;
432 levels_left -= 1;
433 continue;
434 }
435
436 let mut spaces = 0usize;
437 while idx + spaces < chars.len() && chars[idx + spaces] == ' ' && spaces < TAB_STOP
438 {
439 spaces += 1;
440 }
441 if spaces == 0 {
442 break;
443 }
444
445 idx += spaces;
446 levels_left -= 1;
447 }
448
449 if idx > 0 {
450 let _ = self.delete_range(Pos::new(line, 0), Pos::new(line, idx));
451 }
452 removed_by_line.push((line, idx));
453 }
454
455 removed_by_line
456 }
457
458 fn normalized_line_range(
459 &self,
460 start_line: usize,
461 end_line_inclusive: usize,
462 ) -> (usize, usize) {
463 let (start, end) = if start_line <= end_line_inclusive {
464 (start_line, end_line_inclusive)
465 } else {
466 (end_line_inclusive, start_line)
467 };
468 let start = self.clamp_line(start);
469 let end = self.clamp_line(end.max(start));
470 (start, end)
471 }
472
473 fn collect_line_entries(&self, start_line: usize, end_line_inclusive: usize) -> Vec<String> {
474 let mut entries = Vec::with_capacity(end_line_inclusive.saturating_sub(start_line) + 1);
475 for line in start_line..=end_line_inclusive {
476 entries.push(self.line_string(line));
477 }
478 entries
479 }
480}