1use std::collections::VecDeque;
2
3use unicode_width::UnicodeWidthStr;
4
5const MAX_UNDO_STACK: usize = 500;
6
7#[derive(Debug, Clone)]
8pub struct Editor {
9 content: String,
10 cursor: usize,
11 scroll_offset: usize,
12 vertical_scroll: usize,
13 undo_stack: VecDeque<(String, usize)>,
14 redo_stack: VecDeque<(String, usize)>,
15}
16
17impl Editor {
18 pub fn new() -> Self {
19 Self {
20 content: String::new(),
21 cursor: 0,
22 scroll_offset: 0,
23 vertical_scroll: 0,
24 undo_stack: VecDeque::new(),
25 redo_stack: VecDeque::new(),
26 }
27 }
28
29 pub fn with_content(content: String) -> Self {
30 let cursor = content.len();
31 Self {
32 content,
33 cursor,
34 scroll_offset: 0,
35 vertical_scroll: 0,
36 undo_stack: VecDeque::new(),
37 redo_stack: VecDeque::new(),
38 }
39 }
40
41 pub fn content(&self) -> &str {
42 &self.content
43 }
44
45 pub fn cursor(&self) -> usize {
46 self.cursor
47 }
48
49 pub fn scroll_offset(&self) -> usize {
50 self.scroll_offset
51 }
52
53 pub fn vertical_scroll(&self) -> usize {
54 self.vertical_scroll
55 }
56
57 pub fn cursor_line_col(&self) -> (usize, usize) {
59 let before = &self.content[..self.cursor];
60 let line = before.matches('\n').count();
61 let line_start = before.rfind('\n').map(|p| p + 1).unwrap_or(0);
62 let col = UnicodeWidthStr::width(&self.content[line_start..self.cursor]);
63 (line, col)
64 }
65
66 pub fn line_count(&self) -> usize {
67 self.content.matches('\n').count() + 1
68 }
69
70 fn line_start(&self, n: usize) -> usize {
72 if n == 0 {
73 return 0;
74 }
75 let mut count = 0;
76 for (i, c) in self.content.char_indices() {
77 if c == '\n' {
78 count += 1;
79 if count == n {
80 return i + 1;
81 }
82 }
83 }
84 self.content.len()
85 }
86
87 fn line_end(&self, n: usize) -> usize {
89 let start = self.line_start(n);
90 match self.content[start..].find('\n') {
91 Some(pos) => start + pos,
92 None => self.content.len(),
93 }
94 }
95
96 fn line_content(&self, n: usize) -> &str {
98 &self.content[self.line_start(n)..self.line_end(n)]
99 }
100
101 pub fn visual_cursor(&self) -> usize {
103 let (_, col) = self.cursor_line_col();
104 col.saturating_sub(self.scroll_offset)
105 }
106
107 fn push_undo_snapshot(&mut self) {
108 self.undo_stack
109 .push_back((self.content.clone(), self.cursor));
110 if self.undo_stack.len() > MAX_UNDO_STACK {
111 self.undo_stack.pop_front();
112 }
113 self.redo_stack.clear();
114 }
115
116 pub fn undo(&mut self) -> bool {
117 if let Some((content, cursor)) = self.undo_stack.pop_back() {
118 self.redo_stack
119 .push_back((self.content.clone(), self.cursor));
120 self.content = content;
121 self.cursor = cursor;
122 true
123 } else {
124 false
125 }
126 }
127
128 pub fn redo(&mut self) -> bool {
129 if let Some((content, cursor)) = self.redo_stack.pop_back() {
130 self.undo_stack
131 .push_back((self.content.clone(), self.cursor));
132 self.content = content;
133 self.cursor = cursor;
134 true
135 } else {
136 false
137 }
138 }
139
140 pub fn insert_char(&mut self, c: char) {
141 self.push_undo_snapshot();
142 self.content.insert(self.cursor, c);
143 self.cursor += c.len_utf8();
144 }
145
146 pub fn insert_newline(&mut self) {
147 self.push_undo_snapshot();
148 self.content.insert(self.cursor, '\n');
149 self.cursor += 1;
150 }
151
152 pub fn delete_back(&mut self) {
153 if self.cursor > 0 {
154 self.push_undo_snapshot();
155 let prev = self.prev_char_boundary();
156 self.content.drain(prev..self.cursor);
157 self.cursor = prev;
158 }
159 }
160
161 pub fn delete_forward(&mut self) {
162 if self.cursor < self.content.len() {
163 self.push_undo_snapshot();
164 let next = self.next_char_boundary();
165 self.content.drain(self.cursor..next);
166 }
167 }
168
169 pub fn move_left(&mut self) {
170 if self.cursor > 0 {
171 self.cursor = self.prev_char_boundary();
172 }
173 }
174
175 pub fn move_left_in_line(&mut self) {
178 if self.cursor > 0 && self.content.as_bytes()[self.cursor - 1] != b'\n' {
179 self.cursor = self.prev_char_boundary();
180 }
181 }
182
183 pub fn move_right(&mut self) {
184 if self.cursor < self.content.len() {
185 self.cursor = self.next_char_boundary();
186 }
187 }
188
189 pub fn move_word_left(&mut self) {
191 if self.cursor == 0 {
192 return;
193 }
194 let before = &self.content[..self.cursor];
195 let mut chars = before.char_indices().rev();
196 let mut last_idx = self.cursor;
198 for (i, c) in &mut chars {
199 if c.is_alphanumeric() || c == '_' {
200 last_idx = i;
201 break;
202 }
203 last_idx = i;
204 }
205 if last_idx < self.cursor {
207 let before_word = &self.content[..last_idx];
208 for (i, c) in before_word.char_indices().rev() {
209 if !(c.is_alphanumeric() || c == '_') {
210 self.cursor = i + c.len_utf8();
211 return;
212 }
213 }
214 self.cursor = 0;
216 } else {
217 self.cursor = 0;
218 }
219 }
220
221 pub fn move_word_right(&mut self) {
223 if self.cursor >= self.content.len() {
224 return;
225 }
226 let after = &self.content[self.cursor..];
227 let mut chars = after.char_indices();
228 let mut advanced = false;
230 for (i, c) in &mut chars {
231 if !(c.is_alphanumeric() || c == '_') {
232 if advanced {
233 self.cursor += i;
234 let remaining = &self.content[self.cursor..];
236 for (j, c2) in remaining.char_indices() {
237 if c2.is_alphanumeric() || c2 == '_' {
238 self.cursor += j;
239 return;
240 }
241 }
242 self.cursor = self.content.len();
243 return;
244 }
245 let remaining = &self.content[self.cursor + i + c.len_utf8()..];
247 for (j, c2) in remaining.char_indices() {
248 if c2.is_alphanumeric() || c2 == '_' {
249 self.cursor = self.cursor + i + c.len_utf8() + j;
250 return;
251 }
252 }
253 self.cursor = self.content.len();
254 return;
255 }
256 advanced = true;
257 }
258 self.cursor = self.content.len();
259 }
260
261 pub fn move_up(&mut self) {
262 let (line, col) = self.cursor_line_col();
263 if line > 0 {
264 let target_line = line - 1;
265 let target_start = self.line_start(target_line);
266 let target_content = self.line_content(target_line);
267 self.cursor = target_start + byte_offset_at_width(target_content, col);
268 }
269 }
270
271 pub fn move_down(&mut self) {
272 let (line, col) = self.cursor_line_col();
273 if line + 1 < self.line_count() {
274 let target_line = line + 1;
275 let target_start = self.line_start(target_line);
276 let target_content = self.line_content(target_line);
277 self.cursor = target_start + byte_offset_at_width(target_content, col);
278 }
279 }
280
281 pub fn move_home(&mut self) {
283 let (line, _) = self.cursor_line_col();
284 self.cursor = self.line_start(line);
285 self.scroll_offset = 0;
286 }
287
288 pub fn move_end(&mut self) {
290 let (line, _) = self.cursor_line_col();
291 self.cursor = self.line_end(line);
292 }
293
294 pub fn delete_char_at_cursor(&mut self) {
296 self.delete_forward();
297 }
298
299 pub fn delete_line(&mut self) {
301 self.push_undo_snapshot();
302 let (line, _) = self.cursor_line_col();
303 let start = self.line_start(line);
304 let end = self.line_end(line);
305 let line_count = self.line_count();
306
307 if line_count == 1 {
308 self.content.clear();
309 self.cursor = 0;
310 } else if line + 1 < line_count {
311 self.content.drain(start..end + 1);
313 self.cursor = start;
314 } else {
315 self.content.drain(start - 1..end);
317 let prev = line.saturating_sub(1);
318 self.cursor = self.line_start(prev);
319 }
320 }
321
322 pub fn clear_line(&mut self) {
324 self.push_undo_snapshot();
325 let (line, _) = self.cursor_line_col();
326 let start = self.line_start(line);
327 let end = self.line_end(line);
328 self.content.drain(start..end);
329 self.cursor = start;
330 }
331
332 pub fn insert_str(&mut self, s: &str) {
334 if s.is_empty() {
335 return;
336 }
337 self.push_undo_snapshot();
338 self.content.insert_str(self.cursor, s);
339 self.cursor += s.len();
340 }
341
342 pub fn open_line_below(&mut self) {
344 self.push_undo_snapshot();
345 let (line, _) = self.cursor_line_col();
346 let end = self.line_end(line);
347 self.content.insert(end, '\n');
348 self.cursor = end + 1;
349 }
350
351 pub fn open_line_above(&mut self) {
353 self.push_undo_snapshot();
354 let (line, _) = self.cursor_line_col();
355 let start = self.line_start(line);
356 self.content.insert(start, '\n');
357 self.cursor = start;
358 }
359
360 pub fn move_to_first_non_blank(&mut self) {
362 let (line, _) = self.cursor_line_col();
363 let start = self.line_start(line);
364 let line_text = self.line_content(line);
365 let offset = line_text
366 .char_indices()
367 .find(|(_, c)| !c.is_whitespace())
368 .map(|(i, _)| i)
369 .unwrap_or(0);
370 self.cursor = start + offset;
371 }
372
373 pub fn move_to_first_line(&mut self) {
375 self.cursor = 0;
376 }
377
378 pub fn move_to_last_line(&mut self) {
380 let last = self.line_count().saturating_sub(1);
381 self.cursor = self.line_start(last);
382 }
383
384 pub fn move_word_forward_end(&mut self) {
386 if self.cursor >= self.content.len() {
387 return;
388 }
389 let is_word_char = |c: char| c.is_alphanumeric() || c == '_';
390 let after = &self.content[self.cursor..];
391 let mut chars = after.char_indices().peekable();
392
393 if chars.next().is_none() {
395 return;
396 }
397
398 while let Some(&(_, c)) = chars.peek() {
400 if !c.is_whitespace() {
401 break;
402 }
403 chars.next();
404 }
405
406 if let Some(&(first_offset, first)) = chars.peek() {
408 let first_is_word = is_word_char(first);
409 let mut last_offset = first_offset;
410 chars.next();
411
412 for (i, c) in chars {
413 if is_word_char(c) != first_is_word || c.is_whitespace() {
414 break;
415 }
416 last_offset = i;
417 }
418 self.cursor += last_offset;
419 }
420 }
421
422 pub fn update_scroll(&mut self, visible_width: usize) {
424 let (_, col) = self.cursor_line_col();
425 if col < self.scroll_offset {
426 self.scroll_offset = col;
427 } else if col >= self.scroll_offset + visible_width {
428 self.scroll_offset = col - visible_width + 1;
429 }
430 }
431
432 pub fn update_vertical_scroll(&mut self, visible_height: usize) {
434 let (line, _) = self.cursor_line_col();
435 if line < self.vertical_scroll {
436 self.vertical_scroll = line;
437 } else if line >= self.vertical_scroll + visible_height {
438 self.vertical_scroll = line - visible_height + 1;
439 }
440 }
441
442 pub fn set_cursor_by_col(&mut self, col: usize) {
444 self.cursor = byte_offset_at_width(&self.content, col);
445 }
446
447 pub fn set_cursor_by_position(&mut self, line: usize, col: usize) {
449 let target_line = line.min(self.line_count().saturating_sub(1));
450 let start = self.line_start(target_line);
451 let line_text = self.line_content(target_line);
452 self.cursor = start + byte_offset_at_width(line_text, col);
453 }
454
455 fn prev_char_boundary(&self) -> usize {
456 let mut pos = self.cursor - 1;
457 while !self.content.is_char_boundary(pos) {
458 pos -= 1;
459 }
460 pos
461 }
462
463 fn next_char_boundary(&self) -> usize {
464 let mut pos = self.cursor + 1;
465 while pos < self.content.len() && !self.content.is_char_boundary(pos) {
466 pos += 1;
467 }
468 pos
469 }
470}
471
472fn byte_offset_at_width(line: &str, target_width: usize) -> usize {
474 let mut width = 0;
475 for (i, c) in line.char_indices() {
476 if width >= target_width {
477 return i;
478 }
479 width += unicode_width::UnicodeWidthChar::width(c).unwrap_or(0);
480 }
481 line.len()
482}
483
484impl Default for Editor {
485 fn default() -> Self {
486 Self::new()
487 }
488}
489
490#[cfg(test)]
491mod tests {
492 use super::*;
493
494 #[test]
495 fn test_insert_and_content() {
496 let mut editor = Editor::new();
497 editor.insert_char('h');
498 editor.insert_char('i');
499 assert_eq!(editor.content(), "hi");
500 assert_eq!(editor.cursor(), 2);
501 }
502
503 #[test]
504 fn test_delete_back() {
505 let mut editor = Editor::with_content("hello".to_string());
506 editor.delete_back();
507 assert_eq!(editor.content(), "hell");
508 }
509
510 #[test]
511 fn test_cursor_movement() {
512 let mut editor = Editor::with_content("hello".to_string());
513 editor.move_left();
514 assert_eq!(editor.cursor(), 4);
515 editor.move_home();
516 assert_eq!(editor.cursor(), 0);
517 editor.move_end();
518 assert_eq!(editor.cursor(), 5);
519 }
520
521 #[test]
522 fn test_insert_newline() {
523 let mut editor = Editor::new();
524 editor.insert_char('a');
525 editor.insert_newline();
526 editor.insert_char('b');
527 assert_eq!(editor.content(), "a\nb");
528 assert_eq!(editor.cursor(), 3);
529 }
530
531 #[test]
532 fn test_cursor_line_col() {
533 let editor = Editor::with_content("abc\ndef\nghi".to_string());
534 assert_eq!(editor.cursor_line_col(), (2, 3));
536 }
537
538 #[test]
539 fn test_move_up_down() {
540 let mut editor = Editor::with_content("abc\ndef\nghi".to_string());
541 editor.move_up();
543 assert_eq!(editor.cursor_line_col(), (1, 3));
544 assert_eq!(&editor.content()[..editor.cursor()], "abc\ndef");
545 editor.move_up();
546 assert_eq!(editor.cursor_line_col(), (0, 3));
547 assert_eq!(&editor.content()[..editor.cursor()], "abc");
548 editor.move_up();
550 assert_eq!(editor.cursor_line_col(), (0, 3));
551 editor.move_down();
553 assert_eq!(editor.cursor_line_col(), (1, 3));
554 }
555
556 #[test]
557 fn test_move_up_clamps_column() {
558 let mut editor = Editor::with_content("abcdef\nab\nxyz".to_string());
559 editor.move_up();
561 assert_eq!(editor.cursor_line_col(), (1, 2));
563 editor.move_up();
564 assert_eq!(editor.cursor_line_col(), (0, 2));
566 }
567
568 #[test]
569 fn test_line_helpers() {
570 let editor = Editor::with_content("abc\ndef\nghi".to_string());
571 assert_eq!(editor.line_count(), 3);
572 assert_eq!(editor.line_content(0), "abc");
573 assert_eq!(editor.line_content(1), "def");
574 assert_eq!(editor.line_content(2), "ghi");
575 }
576
577 #[test]
578 fn test_home_end_multiline() {
579 let mut editor = Editor::with_content("abc\ndef".to_string());
580 editor.move_home();
582 assert_eq!(editor.cursor(), 4); assert_eq!(editor.cursor_line_col(), (1, 0));
585 editor.move_end();
586 assert_eq!(editor.cursor(), 7); assert_eq!(editor.cursor_line_col(), (1, 3));
588 }
589
590 #[test]
591 fn test_vertical_scroll() {
592 let mut editor = Editor::with_content("a\nb\nc\nd\ne".to_string());
593 editor.update_vertical_scroll(3);
594 assert_eq!(editor.vertical_scroll(), 2);
596 }
597
598 #[test]
599 fn test_undo_insert() {
600 let mut editor = Editor::new();
601 editor.insert_char('a');
602 editor.insert_char('b');
603 assert_eq!(editor.content(), "ab");
604 editor.undo();
605 assert_eq!(editor.content(), "a");
606 editor.undo();
607 assert_eq!(editor.content(), "");
608 assert!(!editor.undo());
610 }
611
612 #[test]
613 fn test_undo_delete() {
614 let mut editor = Editor::with_content("abc".to_string());
615 editor.delete_back();
616 assert_eq!(editor.content(), "ab");
617 editor.undo();
618 assert_eq!(editor.content(), "abc");
619 }
620
621 #[test]
622 fn test_redo() {
623 let mut editor = Editor::new();
624 editor.insert_char('a');
625 editor.insert_char('b');
626 editor.undo();
627 assert_eq!(editor.content(), "a");
628 editor.redo();
629 assert_eq!(editor.content(), "ab");
630 assert!(!editor.redo());
632 }
633
634 #[test]
635 fn test_redo_cleared_on_new_edit() {
636 let mut editor = Editor::new();
637 editor.insert_char('a');
638 editor.insert_char('b');
639 editor.undo();
640 editor.insert_char('c');
642 assert_eq!(editor.content(), "ac");
643 assert!(!editor.redo());
644 }
645
646 #[test]
647 fn test_set_cursor_by_col() {
648 let mut editor = Editor::with_content("hello".to_string());
649 editor.set_cursor_by_col(3);
650 assert_eq!(editor.cursor(), 3);
651 }
652
653 #[test]
654 fn test_set_cursor_by_position() {
655 let mut editor = Editor::with_content("abc\ndef\nghi".to_string());
656 editor.set_cursor_by_position(1, 2);
657 assert_eq!(editor.cursor_line_col(), (1, 2));
658 }
659
660 #[test]
661 fn test_move_word_right() {
662 let mut editor = Editor::with_content("hello world foo".to_string());
663 editor.cursor = 0;
664 editor.move_word_right();
665 assert_eq!(editor.cursor(), 6);
667 editor.move_word_right();
668 assert_eq!(editor.cursor(), 12);
669 editor.move_word_right();
670 assert_eq!(editor.cursor(), 15); }
672
673 #[test]
674 fn test_move_word_left() {
675 let mut editor = Editor::with_content("hello world foo".to_string());
676 editor.move_word_left();
678 assert_eq!(editor.cursor(), 12); editor.move_word_left();
680 assert_eq!(editor.cursor(), 6); editor.move_word_left();
682 assert_eq!(editor.cursor(), 0); }
684
685 #[test]
686 fn test_delete_back_across_newline() {
687 let mut editor = Editor::with_content("abc\ndef".to_string());
688 editor.cursor = 4;
690 editor.delete_back();
691 assert_eq!(editor.content(), "abcdef");
692 assert_eq!(editor.cursor(), 3);
693 }
694
695 #[test]
696 fn test_delete_char_at_cursor() {
697 let mut editor = Editor::with_content("hello".to_string());
698 editor.cursor = 0;
699 editor.delete_char_at_cursor();
700 assert_eq!(editor.content(), "ello");
701 assert_eq!(editor.cursor(), 0);
702 }
703
704 #[test]
705 fn test_delete_char_at_cursor_end() {
706 let mut editor = Editor::with_content("hello".to_string());
707 editor.delete_char_at_cursor();
708 assert_eq!(editor.content(), "hello");
709 }
710
711 #[test]
712 fn test_delete_line() {
713 let mut editor = Editor::with_content("abc\ndef\nghi".to_string());
714 editor.cursor = 5;
715 editor.delete_line();
716 assert_eq!(editor.content(), "abc\nghi");
717 assert_eq!(editor.cursor(), 4);
718 }
719
720 #[test]
721 fn test_delete_line_last() {
722 let mut editor = Editor::with_content("abc\ndef".to_string());
723 editor.cursor = 5;
724 editor.delete_line();
725 assert_eq!(editor.content(), "abc");
726 assert_eq!(editor.cursor(), 0);
727 }
728
729 #[test]
730 fn test_delete_line_single() {
731 let mut editor = Editor::with_content("hello".to_string());
732 editor.cursor = 2;
733 editor.delete_line();
734 assert_eq!(editor.content(), "");
735 assert_eq!(editor.cursor(), 0);
736 }
737
738 #[test]
739 fn test_clear_line() {
740 let mut editor = Editor::with_content("abc\ndef\nghi".to_string());
741 editor.cursor = 5;
742 editor.clear_line();
743 assert_eq!(editor.content(), "abc\n\nghi");
744 assert_eq!(editor.cursor(), 4);
745 }
746
747 #[test]
748 fn test_clear_line_single() {
749 let mut editor = Editor::with_content("hello".to_string());
750 editor.cursor = 2;
751 editor.clear_line();
752 assert_eq!(editor.content(), "");
753 assert_eq!(editor.cursor(), 0);
754 }
755
756 #[test]
757 fn test_insert_str() {
758 let mut editor = Editor::with_content("hd".to_string());
759 editor.cursor = 1;
760 editor.insert_str("ello worl");
761 assert_eq!(editor.content(), "hello world");
762 assert_eq!(editor.cursor(), 10);
763 }
764
765 #[test]
766 fn test_insert_str_undo() {
767 let mut editor = Editor::with_content("ad".to_string());
768 editor.cursor = 1;
769 editor.insert_str("bc");
770 assert_eq!(editor.content(), "abcd");
771 editor.undo();
772 assert_eq!(editor.content(), "ad");
773 }
774
775 #[test]
776 fn test_open_line_below() {
777 let mut editor = Editor::with_content("abc\ndef".to_string());
778 editor.cursor = 1;
779 editor.open_line_below();
780 assert_eq!(editor.content(), "abc\n\ndef");
781 assert_eq!(editor.cursor(), 4);
782 }
783
784 #[test]
785 fn test_open_line_above() {
786 let mut editor = Editor::with_content("abc\ndef".to_string());
787 editor.cursor = 5;
788 editor.open_line_above();
789 assert_eq!(editor.content(), "abc\n\ndef");
790 assert_eq!(editor.cursor(), 4);
791 }
792
793 #[test]
794 fn test_move_to_first_non_blank() {
795 let mut editor = Editor::with_content(" hello".to_string());
796 editor.cursor = 7;
797 editor.move_to_first_non_blank();
798 assert_eq!(editor.cursor(), 3);
799 }
800
801 #[test]
802 fn test_move_to_first_line() {
803 let mut editor = Editor::with_content("abc\ndef\nghi".to_string());
804 editor.move_to_first_line();
805 assert_eq!(editor.cursor(), 0);
806 }
807
808 #[test]
809 fn test_move_to_last_line() {
810 let mut editor = Editor::with_content("abc\ndef\nghi".to_string());
811 editor.cursor = 0;
812 editor.move_to_last_line();
813 let (line, _) = editor.cursor_line_col();
814 assert_eq!(line, 2);
815 }
816
817 #[test]
818 fn test_move_word_forward_end() {
819 let mut editor = Editor::with_content("hello world foo".to_string());
820 editor.cursor = 0;
821 editor.move_word_forward_end();
822 assert_eq!(editor.cursor(), 4);
823 editor.move_word_forward_end();
824 assert_eq!(editor.cursor(), 10);
825 }
826
827 #[test]
828 fn test_move_left_in_line_normal() {
829 let mut editor = Editor::with_content("hello".to_string());
830 editor.move_left_in_line();
832 assert_eq!(editor.cursor(), 4);
833 }
834
835 #[test]
836 fn test_move_left_in_line_at_line_start() {
837 let mut editor = Editor::with_content("abc\ndef".to_string());
838 editor.cursor = 4;
840 editor.move_left_in_line();
841 assert_eq!(editor.cursor(), 4);
843 }
844
845 #[test]
846 fn test_move_left_in_line_at_content_start() {
847 let mut editor = Editor::with_content("hello".to_string());
848 editor.cursor = 0;
849 editor.move_left_in_line();
850 assert_eq!(editor.cursor(), 0);
851 }
852}