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