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