1use std::ops::Range;
2use std::path::Path;
3
4use ropey::Rope;
5
6use crate::{
7 coordinates::Point,
8 error::{EditorError, Result},
9 history::{Edit, History, Transaction},
10};
11
12#[derive(Debug)]
32pub struct EditorBuffer {
33 text: Rope,
34 cursor: usize,
35 history: History,
36 version: usize,
37}
38
39impl EditorBuffer {
40 pub fn new(initial_text: &str) -> Self {
45 Self {
46 text: Rope::from_str(initial_text),
47 cursor: 0,
48 history: History::default(),
49 version: 0,
50 }
51 }
52
53 pub fn version(&self) -> usize {
55 self.version
56 }
57
58 pub fn text(&self) -> &Rope {
63 &self.text
64 }
65
66 pub fn cursor_offset(&self) -> usize {
70 self.cursor
71 }
72
73 pub fn len_bytes(&self) -> usize {
75 self.text.len_bytes()
76 }
77
78 pub fn len_lines(&self) -> usize {
80 self.text.len_lines()
81 }
82
83 pub fn line_to_string(&self, line_idx: usize) -> String {
87 if line_idx >= self.text.len_lines() {
88 return String::new();
89 }
90
91 self.text.line(line_idx).to_string()
92 }
93
94 pub fn offset_to_point(&self, offset: usize) -> Point {
100 let clamped = offset.min(self.text.len_bytes());
101 let row = self.text.byte_to_line(clamped);
102 let line_start_byte = self.text.line_to_byte(row);
103 let column = clamped - line_start_byte;
104
105 Point::new(row, column)
106 }
107
108 pub fn point_to_offset(&self, point: Point) -> usize {
114 if point.row >= self.text.len_lines() {
115 return self.text.len_bytes();
116 }
117 let line_start_byte = self.text.line_to_byte(point.row);
118 let line_len = self.text.line(point.row).len_bytes();
119 let col = point.column.min(line_len);
120 line_start_byte + col
121 }
122
123 pub fn cursor_point(&self) -> Point {
125 self.offset_to_point(self.cursor)
126 }
127
128 pub fn set_cursor_offset(&mut self, offset: usize) {
135 let offset = offset.min(self.text.len_bytes());
136 self.cursor = self.text.char_to_byte(self.text.byte_to_char(offset));
137 }
138
139 pub fn set_cursor_point(&mut self, point: Point) {
143 self.cursor = self.point_to_offset(point);
144 }
145
146 pub fn move_cursor_right(&mut self) {
150 if self.cursor < self.text.len_bytes() {
151 let char_idx = self.text.byte_to_char(self.cursor);
152 let next_char = (char_idx + 1).min(self.text.len_chars());
153 self.cursor = self.text.char_to_byte(next_char);
154 }
155 }
156
157 pub fn move_cursor_up(&mut self) {
162 let point = self.cursor_point();
163 if point.row > 0 {
164 self.set_cursor_point(Point::new(point.row - 1, point.column));
165 }
166 }
167
168 pub fn move_cursor_down(&mut self) {
173 let point = self.cursor_point();
174 if point.row + 1 < self.text.len_lines() {
175 self.set_cursor_point(Point::new(point.row + 1, point.column));
176 }
177 }
178
179 pub fn move_cursor_left(&mut self) {
184 if self.cursor > 0 {
185 let char_idx = self.text.byte_to_char(self.cursor);
186 self.cursor = self.text.char_to_byte(char_idx - 1);
187 }
188 }
189
190 pub fn prev_word_offset(&self) -> usize {
192 crate::movement::find_prev_word_start(&self.text, self.cursor)
193 }
194
195 pub fn next_word_offset(&self) -> usize {
197 crate::movement::find_next_word_end(&self.text, self.cursor)
198 }
199
200 pub fn line_start_offset(&self) -> usize {
202 crate::movement::find_line_start(&self.text, self.cursor)
203 }
204
205 pub fn line_end_offset(&self) -> usize {
207 crate::movement::find_line_end(&self.text, self.cursor)
208 }
209
210 pub fn word_range_at(&self, offset: usize) -> Range<usize> {
212 crate::movement::find_word_range_at(&self.text, offset)
213 }
214
215 pub fn line_range_at(&self, offset: usize) -> Range<usize> {
218 crate::movement::find_line_range_at(&self.text, offset)
219 }
220
221 pub fn move_cursor_prev_word(&mut self) {
223 self.cursor = self.prev_word_offset();
224 }
225
226 pub fn move_cursor_next_word(&mut self) {
228 self.cursor = self.next_word_offset();
229 }
230
231 pub fn move_cursor_line_start(&mut self) {
233 self.cursor = self.line_start_offset();
234 }
235
236 pub fn move_cursor_line_end(&mut self) {
238 self.cursor = self.line_end_offset();
239 }
240
241 pub fn delete_prev_word(&mut self) -> bool {
245 let target = self.prev_word_offset();
246 if target < self.cursor {
247 self.delete_range(target..self.cursor);
248 true
249 } else {
250 false
251 }
252 }
253
254 pub fn delete_next_word(&mut self) -> bool {
258 let target = self.next_word_offset();
259 if self.cursor < target {
260 self.delete_range(self.cursor..target);
261 true
262 } else {
263 false
264 }
265 }
266
267 pub fn insert(&mut self, text: &str) {
274 let previous_cursor = self.cursor;
275 let char_idx = self.text.byte_to_char(self.cursor);
276 self.text.insert(char_idx, text);
277 self.cursor += text.len();
278
279 let tx = Transaction {
280 edits: vec![Edit {
281 bytes_range: previous_cursor..previous_cursor,
282 inserted_text: text.to_string(),
283 deleted_text: String::new(),
284 }],
285 previous_cursor,
286 resulting_cursor: self.cursor,
287 };
288
289 self.history.undo_stack.push(tx);
290 self.history.redo_stack.clear();
291 self.version += 1;
292 }
293
294 pub fn backspace(&mut self) {
304 if self.cursor == 0 {
305 return;
306 }
307
308 let char_idx = self.text.byte_to_char(self.cursor);
309 let previous_char_byte = self.text.char_to_byte(char_idx - 1);
310 let range_to_delete = previous_char_byte..self.cursor;
311 let deleted_text = self.text.byte_slice(range_to_delete.clone()).to_string();
312
313 let previous_cursor = self.cursor;
314 self.text.remove((char_idx - 1)..char_idx);
315 self.cursor = previous_char_byte;
316
317 let tx = Transaction {
318 edits: vec![Edit {
319 bytes_range: range_to_delete,
320 inserted_text: String::new(),
321 deleted_text,
322 }],
323 previous_cursor,
324 resulting_cursor: self.cursor,
325 };
326
327 self.history.undo_stack.push(tx);
328 self.history.redo_stack.clear();
329 self.version += 1;
330 }
331
332 pub fn delete(&mut self) {
340 if self.cursor >= self.text.len_bytes() {
341 return;
342 }
343
344 let char_idx = self.text.byte_to_char(self.cursor);
345 let next_char = char_idx + 1;
346
347 let end = self.text.char_to_byte(next_char);
348 let byte_range = self.cursor..end;
349 let deleted_text = self.text.byte_slice(byte_range.clone()).to_string();
350
351 self.text.remove(char_idx..next_char);
352
353 let tx = Transaction {
354 edits: vec![Edit {
355 bytes_range: byte_range,
356 inserted_text: String::new(),
357 deleted_text,
358 }],
359 previous_cursor: self.cursor,
360 resulting_cursor: self.cursor,
361 };
362
363 self.history.undo_stack.push(tx);
364 self.history.redo_stack.clear();
365 self.version += 1;
366 }
367
368 pub fn delete_range(&mut self, range: Range<usize>) {
373 let start = range.start.min(self.text.len_bytes());
374 let end = range.end.min(self.text.len_bytes());
375 if start >= end {
376 return;
377 }
378
379 let start_char = self.text.byte_to_char(start);
380 let end_char = self.text.byte_to_char(end);
381 let deleted_text = self.text.byte_slice(start..end).to_string();
382 let previous_cursor = self.cursor;
383
384 self.text.remove(start_char..end_char);
385 self.cursor = start;
386
387 let tx = Transaction {
388 edits: vec![Edit {
389 bytes_range: start..end,
390 inserted_text: String::new(),
391 deleted_text,
392 }],
393 previous_cursor,
394 resulting_cursor: self.cursor,
395 };
396
397 self.history.undo_stack.push(tx);
398 self.history.redo_stack.clear();
399 self.version += 1;
400 }
401
402 pub fn replace_range(&mut self, range: Range<usize>, text: &str) {
406 let start = range.start.min(self.text.len_bytes());
407 let end = range.end.min(self.text.len_bytes());
408 if start == end {
409 self.cursor = start;
410 self.insert(text);
411 return;
412 }
413
414 let start_char = self.text.byte_to_char(start);
415 let end_char = self.text.byte_to_char(end);
416 let deleted_text = self.text.byte_slice(start..end).to_string();
417 let previous_cursor = self.cursor;
418
419 self.text.remove(start_char..end_char);
420 self.text.insert(start_char, text);
421 self.cursor = start + text.len();
422
423 let tx = Transaction {
424 edits: vec![Edit {
425 bytes_range: start..end,
426 inserted_text: text.to_string(),
427 deleted_text,
428 }],
429 previous_cursor,
430 resulting_cursor: self.cursor,
431 };
432
433 self.history.undo_stack.push(tx);
434 self.history.redo_stack.clear();
435 self.version += 1;
436 }
437
438 pub fn replace_many(&mut self, replacements: Vec<(Range<usize>, String)>) -> usize {
446 let len = self.text.len_bytes();
447 let mut valid: Vec<(usize, usize, String)> = Vec::with_capacity(replacements.len());
448 for (range, text) in replacements {
449 if range.start >= range.end || range.end > len {
450 continue;
451 }
452 if !self.is_char_boundary(range.start) || !self.is_char_boundary(range.end) {
453 continue;
454 }
455 valid.push((range.start, range.end, text));
456 }
457 if valid.is_empty() {
458 return 0;
459 }
460 valid.sort_by_key(|(start, _, _)| *start);
461 let mut dedup: Vec<(usize, usize, String)> = Vec::with_capacity(valid.len());
464 for (start, end, text) in valid {
465 if let Some((_, last_end, _)) = dedup.last()
466 && start < *last_end
467 {
468 continue;
469 }
470 dedup.push((start, end, text));
471 }
472 if dedup.is_empty() {
473 return 0;
474 }
475
476 let previous_cursor = self.cursor;
477 let mut edits: Vec<Edit> = Vec::with_capacity(dedup.len());
480 for (start, end, text) in dedup.iter().rev() {
481 let deleted_text = self.text.byte_slice(*start..*end).to_string();
482 let start_char = self.text.byte_to_char(*start);
483 let end_char = self.text.byte_to_char(*end);
484 self.text.remove(start_char..end_char);
485 self.text.insert(start_char, text);
486 edits.push(Edit {
487 bytes_range: *start..*end,
488 inserted_text: text.clone(),
489 deleted_text,
490 });
491 }
492 edits.reverse();
493
494 let mut shift: i64 = 0;
497 for edit in &edits[..edits.len() - 1] {
498 shift += edit.inserted_text.len() as i64
499 - (edit.bytes_range.end - edit.bytes_range.start) as i64;
500 }
501 let last = &edits[edits.len() - 1];
502 let new_cursor = (last.bytes_range.start as i64 + shift + last.inserted_text.len() as i64)
503 .max(0) as usize;
504 self.cursor = new_cursor.min(self.text.len_bytes());
505
506 let tx = Transaction {
507 edits,
508 previous_cursor,
509 resulting_cursor: self.cursor,
510 };
511 let applied = tx.edits.len();
512
513 self.history.undo_stack.push(tx);
514 self.history.redo_stack.clear();
515 self.version += 1;
516 applied
517 }
518
519 pub fn move_lines_up(&mut self, start_row: usize, end_row: usize) -> bool {
526 let total_lines = self.text.len_lines();
527 if start_row == 0 || start_row > end_row || end_row >= total_lines {
528 return false;
529 }
530
531 let target_row = start_row - 1;
532 let target_line_start = self.text.line_to_byte(target_row);
533 let block_line_start = self.text.line_to_byte(start_row);
534 let span_end = if end_row + 1 < total_lines {
535 self.text.line_to_byte(end_row + 1)
536 } else {
537 self.text.len_bytes()
538 };
539
540 let target_line = self
541 .text
542 .byte_slice(target_line_start..block_line_start)
543 .to_string();
544 let block = self.text.byte_slice(block_line_start..span_end).to_string();
545
546 let target_newline = if target_line.ends_with("\r\n") {
547 "\r\n"
548 } else {
549 "\n"
550 };
551
552 let swapped_text = if !block.ends_with('\n') {
553 let target_trimmed = &target_line[..target_line.len() - target_newline.len()];
554 format!("{block}{target_newline}{target_trimmed}")
555 } else {
556 format!("{block}{target_line}")
557 };
558
559 let cursor_point = self.cursor_point();
560 let new_point = if cursor_point.row >= start_row && cursor_point.row <= end_row {
561 Point::new(cursor_point.row - 1, cursor_point.column)
562 } else if cursor_point.row == target_row {
563 Point::new(end_row, cursor_point.column)
564 } else {
565 cursor_point
566 };
567
568 let start_char = self.text.byte_to_char(target_line_start);
569 let end_char = self.text.byte_to_char(span_end);
570 let deleted_text = self
571 .text
572 .byte_slice(target_line_start..span_end)
573 .to_string();
574 let previous_cursor = self.cursor;
575
576 self.text.remove(start_char..end_char);
577 self.text.insert(start_char, &swapped_text);
578 self.cursor = self.point_to_offset(new_point);
579
580 let tx = Transaction {
581 edits: vec![Edit {
582 bytes_range: target_line_start..span_end,
583 inserted_text: swapped_text,
584 deleted_text,
585 }],
586 previous_cursor,
587 resulting_cursor: self.cursor,
588 };
589
590 self.history.undo_stack.push(tx);
591 self.history.redo_stack.clear();
592 self.version += 1;
593 true
594 }
595
596 pub fn move_lines_down(&mut self, start_row: usize, end_row: usize) -> bool {
603 let total_lines = self.text.len_lines();
604 if start_row > end_row || end_row + 1 >= total_lines {
605 return false;
606 }
607
608 let target_row = end_row + 1;
609 let block_line_start = self.text.line_to_byte(start_row);
610 let target_line_start = self.text.line_to_byte(target_row);
611 let span_end = if target_row + 1 < total_lines {
612 self.text.line_to_byte(target_row + 1)
613 } else {
614 self.text.len_bytes()
615 };
616
617 let block = self
618 .text
619 .byte_slice(block_line_start..target_line_start)
620 .to_string();
621 let target_line = self
622 .text
623 .byte_slice(target_line_start..span_end)
624 .to_string();
625
626 let block_newline = if block.ends_with("\r\n") {
627 "\r\n"
628 } else {
629 "\n"
630 };
631
632 let swapped_text = if !target_line.ends_with('\n') {
633 let block_trimmed = &block[..block.len() - block_newline.len()];
634 format!("{target_line}{block_newline}{block_trimmed}")
635 } else {
636 format!("{target_line}{block}")
637 };
638
639 let cursor_point = self.cursor_point();
640 let new_point = if cursor_point.row >= start_row && cursor_point.row <= end_row {
641 Point::new(cursor_point.row + 1, cursor_point.column)
642 } else if cursor_point.row == target_row {
643 Point::new(start_row, cursor_point.column)
644 } else {
645 cursor_point
646 };
647
648 let start_char = self.text.byte_to_char(block_line_start);
649 let end_char = self.text.byte_to_char(span_end);
650 let deleted_text = self.text.byte_slice(block_line_start..span_end).to_string();
651 let previous_cursor = self.cursor;
652
653 self.text.remove(start_char..end_char);
654 self.text.insert(start_char, &swapped_text);
655 self.cursor = self.point_to_offset(new_point);
656
657 let tx = Transaction {
658 edits: vec![Edit {
659 bytes_range: block_line_start..span_end,
660 inserted_text: swapped_text,
661 deleted_text,
662 }],
663 previous_cursor,
664 resulting_cursor: self.cursor,
665 };
666
667 self.history.undo_stack.push(tx);
668 self.history.redo_stack.clear();
669 self.version += 1;
670 true
671 }
672
673 pub fn undo(&mut self) {
678 if let Some(tx) = self.history.undo_stack.pop() {
679 let mut prefix = Vec::with_capacity(tx.edits.len() + 1);
683 prefix.push(0i64);
684 for edit in &tx.edits {
685 let delta = edit.inserted_text.len() as i64
686 - (edit.bytes_range.end - edit.bytes_range.start) as i64;
687 prefix.push(prefix.last().copied().unwrap_or(0) + delta);
688 }
689 for (index, edit) in tx.edits.iter().enumerate().rev() {
690 let start = (edit.bytes_range.start as i64 + prefix[index]).max(0) as usize;
691 let end = start + edit.inserted_text.len();
692
693 if end > start {
694 let start_char = self.text.byte_to_char(start);
695 let end_char = self.text.byte_to_char(end);
696 self.text.remove(start_char..end_char);
697 }
698 if !edit.deleted_text.is_empty() {
699 let start_char = self.text.byte_to_char(start);
700 self.text.insert(start_char, &edit.deleted_text);
701 }
702 }
703 self.cursor = tx.previous_cursor;
704 self.history.redo_stack.push(tx);
705 self.version += 1;
706 }
707 }
708
709 pub fn can_undo(&self) -> bool {
711 !self.history.undo_stack.is_empty()
712 }
713
714 pub fn can_redo(&self) -> bool {
716 !self.history.redo_stack.is_empty()
717 }
718
719 pub fn redo(&mut self) {
724 if let Some(tx) = self.history.redo_stack.pop() {
725 for edit in tx.edits.iter().rev() {
728 let start = edit.bytes_range.start;
729 let end = start + edit.deleted_text.len();
730
731 if end > start {
732 let start_char = self.text.byte_to_char(start);
733 let end_char = self.text.byte_to_char(end);
734 self.text.remove(start_char..end_char);
735 }
736 if !edit.inserted_text.is_empty() {
737 let start_char = self.text.byte_to_char(start);
738 self.text.insert(start_char, &edit.inserted_text);
739 }
740 }
741 self.cursor = tx.resulting_cursor;
742 self.history.undo_stack.push(tx);
743 self.version += 1;
744 }
745 }
746
747 pub fn is_char_boundary(&self, offset: usize) -> bool {
749 if offset > self.text.len_bytes() {
750 return false;
751 }
752 let char_idx = self.text.byte_to_char(offset);
753 self.text.char_to_byte(char_idx) == offset
754 }
755
756 pub fn validate_offset(&self, offset: usize) -> Result<()> {
758 let len = self.text.len_bytes();
759 if offset > len {
760 return Err(EditorError::OutOfBounds { offset, len });
761 }
762 if !self.is_char_boundary(offset) {
763 return Err(EditorError::InvalidCharBoundary { offset });
764 }
765 Ok(())
766 }
767
768 pub fn validate_range(&self, range: &Range<usize>) -> Result<()> {
770 let len = self.text.len_bytes();
771 if range.start > range.end || range.end > len {
772 return Err(EditorError::InvalidRange {
773 range: range.clone(),
774 len,
775 });
776 }
777 if !self.is_char_boundary(range.start) {
778 return Err(EditorError::InvalidCharBoundary {
779 offset: range.start,
780 });
781 }
782 if !self.is_char_boundary(range.end) {
783 return Err(EditorError::InvalidCharBoundary { offset: range.end });
784 }
785 Ok(())
786 }
787
788 pub fn try_line_to_string(&self, row: usize) -> Result<String> {
790 let total_lines = self.text.len_lines();
791 if row >= total_lines {
792 return Err(EditorError::InvalidRow { row, total_lines });
793 }
794 Ok(self.text.line(row).to_string())
795 }
796
797 pub fn try_replace_range(&mut self, range: Range<usize>, text: &str) -> Result<()> {
799 self.validate_range(&range)?;
800 self.replace_range(range, text);
801 Ok(())
802 }
803
804 pub fn try_delete_range(&mut self, range: Range<usize>) -> Result<()> {
806 self.validate_range(&range)?;
807 self.delete_range(range);
808 Ok(())
809 }
810
811 pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
813 let content = std::fs::read_to_string(path)?;
814 Ok(Self::new(&content))
815 }
816
817 pub fn save_to_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
819 std::fs::write(path, self.text.to_string())?;
820 Ok(())
821 }
822}