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 const 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 const fn cursor(&self) -> usize {
46 self.cursor
47 }
48
49 pub const fn scroll_offset(&self) -> usize {
50 self.scroll_offset
51 }
52
53 pub const 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_or(0, |p| p + 1);
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 }
214 self.cursor = 0;
216 }
217
218 pub fn move_word_right(&mut self) {
220 if self.cursor >= self.content.len() {
221 return;
222 }
223 let after = &self.content[self.cursor..];
224 let mut chars = after.char_indices();
225 let mut advanced = false;
227 for (i, c) in &mut chars {
228 if !(c.is_alphanumeric() || c == '_') {
229 if advanced {
230 self.cursor += i;
231 let remaining = &self.content[self.cursor..];
233 for (j, c2) in remaining.char_indices() {
234 if c2.is_alphanumeric() || c2 == '_' {
235 self.cursor += j;
236 return;
237 }
238 }
239 self.cursor = self.content.len();
240 return;
241 }
242 let remaining = &self.content[self.cursor + i + c.len_utf8()..];
244 for (j, c2) in remaining.char_indices() {
245 if c2.is_alphanumeric() || c2 == '_' {
246 self.cursor = self.cursor + i + c.len_utf8() + j;
247 return;
248 }
249 }
250 self.cursor = self.content.len();
251 return;
252 }
253 advanced = true;
254 }
255 self.cursor = self.content.len();
256 }
257
258 pub fn move_up(&mut self) {
259 let (line, col) = self.cursor_line_col();
260 if line > 0 {
261 let target_line = line - 1;
262 let target_start = self.line_start(target_line);
263 let target_content = self.line_content(target_line);
264 self.cursor = target_start + byte_offset_at_width(target_content, col);
265 }
266 }
267
268 pub fn move_down(&mut self) {
269 let (line, col) = self.cursor_line_col();
270 if line + 1 < self.line_count() {
271 let target_line = line + 1;
272 let target_start = self.line_start(target_line);
273 let target_content = self.line_content(target_line);
274 self.cursor = target_start + byte_offset_at_width(target_content, col);
275 }
276 }
277
278 pub fn move_home(&mut self) {
280 let (line, _) = self.cursor_line_col();
281 self.cursor = self.line_start(line);
282 self.scroll_offset = 0;
283 }
284
285 pub fn move_end(&mut self) {
287 let (line, _) = self.cursor_line_col();
288 self.cursor = self.line_end(line);
289 }
290
291 pub fn delete_char_at_cursor(&mut self) {
293 self.delete_forward();
294 }
295
296 pub fn delete_line(&mut self) {
298 self.push_undo_snapshot();
299 let (line, _) = self.cursor_line_col();
300 let start = self.line_start(line);
301 let end = self.line_end(line);
302 let line_count = self.line_count();
303
304 if line_count == 1 {
305 self.content.clear();
306 self.cursor = 0;
307 } else if line + 1 < line_count {
308 self.content.drain(start..=end);
310 self.cursor = start;
311 } else {
312 self.content.drain(start - 1..end);
314 let prev = line.saturating_sub(1);
315 self.cursor = self.line_start(prev);
316 }
317 }
318
319 pub fn clear_line(&mut self) {
321 self.push_undo_snapshot();
322 let (line, _) = self.cursor_line_col();
323 let start = self.line_start(line);
324 let end = self.line_end(line);
325 self.content.drain(start..end);
326 self.cursor = start;
327 }
328
329 pub fn insert_str(&mut self, s: &str) {
331 if s.is_empty() {
332 return;
333 }
334 self.push_undo_snapshot();
335 self.content.insert_str(self.cursor, s);
336 self.cursor += s.len();
337 }
338
339 pub fn open_line_below(&mut self) {
341 self.push_undo_snapshot();
342 let (line, _) = self.cursor_line_col();
343 let end = self.line_end(line);
344 self.content.insert(end, '\n');
345 self.cursor = end + 1;
346 }
347
348 pub fn open_line_above(&mut self) {
350 self.push_undo_snapshot();
351 let (line, _) = self.cursor_line_col();
352 let start = self.line_start(line);
353 self.content.insert(start, '\n');
354 self.cursor = start;
355 }
356
357 pub fn move_to_first_non_blank(&mut self) {
359 let (line, _) = self.cursor_line_col();
360 let start = self.line_start(line);
361 let line_text = self.line_content(line);
362 let offset = line_text
363 .char_indices()
364 .find(|(_, c)| !c.is_whitespace())
365 .map_or(0, |(i, _)| i);
366 self.cursor = start + offset;
367 }
368
369 pub fn move_to_first_line(&mut self) {
371 self.cursor = 0;
372 }
373
374 pub fn move_to_last_line(&mut self) {
376 let last = self.line_count().saturating_sub(1);
377 self.cursor = self.line_start(last);
378 }
379
380 pub fn move_word_forward_end(&mut self) {
382 if self.cursor >= self.content.len() {
383 return;
384 }
385 let is_word_char = |c: char| c.is_alphanumeric() || c == '_';
386 let after = &self.content[self.cursor..];
387 let mut chars = after.char_indices().peekable();
388
389 if chars.next().is_none() {
391 return;
392 }
393
394 while let Some(&(_, c)) = chars.peek() {
396 if !c.is_whitespace() {
397 break;
398 }
399 chars.next();
400 }
401
402 if let Some(&(first_offset, first)) = chars.peek() {
404 let first_is_word = is_word_char(first);
405 let mut last_offset = first_offset;
406 chars.next();
407
408 for (i, c) in chars {
409 if is_word_char(c) != first_is_word || c.is_whitespace() {
410 break;
411 }
412 last_offset = i;
413 }
414 self.cursor += last_offset;
415 }
416 }
417
418 pub fn update_scroll(&mut self, visible_width: usize) {
420 let (_, col) = self.cursor_line_col();
421 if col < self.scroll_offset {
422 self.scroll_offset = col;
423 } else if col >= self.scroll_offset + visible_width {
424 self.scroll_offset = col - visible_width + 1;
425 }
426 }
427
428 pub fn update_vertical_scroll(&mut self, visible_height: usize) {
430 let (line, _) = self.cursor_line_col();
431 if line < self.vertical_scroll {
432 self.vertical_scroll = line;
433 } else if line >= self.vertical_scroll + visible_height {
434 self.vertical_scroll = line - visible_height + 1;
435 }
436 }
437
438 pub fn set_cursor_by_col(&mut self, col: usize) {
440 self.cursor = byte_offset_at_width(&self.content, col);
441 }
442
443 pub fn set_cursor_by_position(&mut self, line: usize, col: usize) {
445 let target_line = line.min(self.line_count().saturating_sub(1));
446 let start = self.line_start(target_line);
447 let line_text = self.line_content(target_line);
448 self.cursor = start + byte_offset_at_width(line_text, col);
449 }
450
451 fn prev_char_boundary(&self) -> usize {
452 let mut pos = self.cursor - 1;
453 while !self.content.is_char_boundary(pos) {
454 pos -= 1;
455 }
456 pos
457 }
458
459 fn next_char_boundary(&self) -> usize {
460 let mut pos = self.cursor + 1;
461 while pos < self.content.len() && !self.content.is_char_boundary(pos) {
462 pos += 1;
463 }
464 pos
465 }
466}
467
468fn byte_offset_at_width(line: &str, target_width: usize) -> usize {
470 let mut width = 0;
471 for (i, c) in line.char_indices() {
472 if width >= target_width {
473 return i;
474 }
475 width += unicode_width::UnicodeWidthChar::width(c).unwrap_or(0);
476 }
477 line.len()
478}
479
480impl Default for Editor {
481 fn default() -> Self {
482 Self::new()
483 }
484}
485
486#[cfg(test)]
487mod tests {
488 use super::*;
489
490 #[test]
491 fn test_insert_and_content() {
492 let mut editor = Editor::new();
493 editor.insert_char('h');
494 editor.insert_char('i');
495 assert_eq!(editor.content(), "hi");
496 assert_eq!(editor.cursor(), 2);
497 }
498
499 #[test]
500 fn test_delete_back() {
501 let mut editor = Editor::with_content("hello".to_string());
502 editor.delete_back();
503 assert_eq!(editor.content(), "hell");
504 }
505
506 #[test]
507 fn test_cursor_movement() {
508 let mut editor = Editor::with_content("hello".to_string());
509 editor.move_left();
510 assert_eq!(editor.cursor(), 4);
511 editor.move_home();
512 assert_eq!(editor.cursor(), 0);
513 editor.move_end();
514 assert_eq!(editor.cursor(), 5);
515 }
516
517 #[test]
518 fn test_insert_newline() {
519 let mut editor = Editor::new();
520 editor.insert_char('a');
521 editor.insert_newline();
522 editor.insert_char('b');
523 assert_eq!(editor.content(), "a\nb");
524 assert_eq!(editor.cursor(), 3);
525 }
526
527 #[test]
528 fn test_cursor_line_col() {
529 let editor = Editor::with_content("abc\ndef\nghi".to_string());
530 assert_eq!(editor.cursor_line_col(), (2, 3));
532 }
533
534 #[test]
535 fn test_move_up_down() {
536 let mut editor = Editor::with_content("abc\ndef\nghi".to_string());
537 editor.move_up();
539 assert_eq!(editor.cursor_line_col(), (1, 3));
540 assert_eq!(&editor.content()[..editor.cursor()], "abc\ndef");
541 editor.move_up();
542 assert_eq!(editor.cursor_line_col(), (0, 3));
543 assert_eq!(&editor.content()[..editor.cursor()], "abc");
544 editor.move_up();
546 assert_eq!(editor.cursor_line_col(), (0, 3));
547 editor.move_down();
549 assert_eq!(editor.cursor_line_col(), (1, 3));
550 }
551
552 #[test]
553 fn test_move_up_clamps_column() {
554 let mut editor = Editor::with_content("abcdef\nab\nxyz".to_string());
555 editor.move_up();
557 assert_eq!(editor.cursor_line_col(), (1, 2));
559 editor.move_up();
560 assert_eq!(editor.cursor_line_col(), (0, 2));
562 }
563
564 #[test]
565 fn test_line_helpers() {
566 let editor = Editor::with_content("abc\ndef\nghi".to_string());
567 assert_eq!(editor.line_count(), 3);
568 assert_eq!(editor.line_content(0), "abc");
569 assert_eq!(editor.line_content(1), "def");
570 assert_eq!(editor.line_content(2), "ghi");
571 }
572
573 #[test]
574 fn test_home_end_multiline() {
575 let mut editor = Editor::with_content("abc\ndef".to_string());
576 editor.move_home();
578 assert_eq!(editor.cursor(), 4); assert_eq!(editor.cursor_line_col(), (1, 0));
581 editor.move_end();
582 assert_eq!(editor.cursor(), 7); assert_eq!(editor.cursor_line_col(), (1, 3));
584 }
585
586 #[test]
587 fn test_vertical_scroll() {
588 let mut editor = Editor::with_content("a\nb\nc\nd\ne".to_string());
589 editor.update_vertical_scroll(3);
590 assert_eq!(editor.vertical_scroll(), 2);
592 }
593
594 #[test]
595 fn test_undo_insert() {
596 let mut editor = Editor::new();
597 editor.insert_char('a');
598 editor.insert_char('b');
599 assert_eq!(editor.content(), "ab");
600 editor.undo();
601 assert_eq!(editor.content(), "a");
602 editor.undo();
603 assert_eq!(editor.content(), "");
604 assert!(!editor.undo());
606 }
607
608 #[test]
609 fn test_undo_delete() {
610 let mut editor = Editor::with_content("abc".to_string());
611 editor.delete_back();
612 assert_eq!(editor.content(), "ab");
613 editor.undo();
614 assert_eq!(editor.content(), "abc");
615 }
616
617 #[test]
618 fn test_redo() {
619 let mut editor = Editor::new();
620 editor.insert_char('a');
621 editor.insert_char('b');
622 editor.undo();
623 assert_eq!(editor.content(), "a");
624 editor.redo();
625 assert_eq!(editor.content(), "ab");
626 assert!(!editor.redo());
628 }
629
630 #[test]
631 fn test_redo_cleared_on_new_edit() {
632 let mut editor = Editor::new();
633 editor.insert_char('a');
634 editor.insert_char('b');
635 editor.undo();
636 editor.insert_char('c');
638 assert_eq!(editor.content(), "ac");
639 assert!(!editor.redo());
640 }
641
642 #[test]
643 fn test_set_cursor_by_col() {
644 let mut editor = Editor::with_content("hello".to_string());
645 editor.set_cursor_by_col(3);
646 assert_eq!(editor.cursor(), 3);
647 }
648
649 #[test]
650 fn test_set_cursor_by_position() {
651 let mut editor = Editor::with_content("abc\ndef\nghi".to_string());
652 editor.set_cursor_by_position(1, 2);
653 assert_eq!(editor.cursor_line_col(), (1, 2));
654 }
655
656 #[test]
657 fn test_move_word_right() {
658 let mut editor = Editor::with_content("hello world foo".to_string());
659 editor.cursor = 0;
660 editor.move_word_right();
661 assert_eq!(editor.cursor(), 6);
663 editor.move_word_right();
664 assert_eq!(editor.cursor(), 12);
665 editor.move_word_right();
666 assert_eq!(editor.cursor(), 15); }
668
669 #[test]
670 fn test_move_word_left() {
671 let mut editor = Editor::with_content("hello world foo".to_string());
672 editor.move_word_left();
674 assert_eq!(editor.cursor(), 12); editor.move_word_left();
676 assert_eq!(editor.cursor(), 6); editor.move_word_left();
678 assert_eq!(editor.cursor(), 0); }
680
681 #[test]
682 fn test_delete_back_across_newline() {
683 let mut editor = Editor::with_content("abc\ndef".to_string());
684 editor.cursor = 4;
686 editor.delete_back();
687 assert_eq!(editor.content(), "abcdef");
688 assert_eq!(editor.cursor(), 3);
689 }
690
691 #[test]
692 fn test_delete_char_at_cursor() {
693 let mut editor = Editor::with_content("hello".to_string());
694 editor.cursor = 0;
695 editor.delete_char_at_cursor();
696 assert_eq!(editor.content(), "ello");
697 assert_eq!(editor.cursor(), 0);
698 }
699
700 #[test]
701 fn test_delete_char_at_cursor_end() {
702 let mut editor = Editor::with_content("hello".to_string());
703 editor.delete_char_at_cursor();
704 assert_eq!(editor.content(), "hello");
705 }
706
707 #[test]
708 fn test_delete_line() {
709 let mut editor = Editor::with_content("abc\ndef\nghi".to_string());
710 editor.cursor = 5;
711 editor.delete_line();
712 assert_eq!(editor.content(), "abc\nghi");
713 assert_eq!(editor.cursor(), 4);
714 }
715
716 #[test]
717 fn test_delete_line_last() {
718 let mut editor = Editor::with_content("abc\ndef".to_string());
719 editor.cursor = 5;
720 editor.delete_line();
721 assert_eq!(editor.content(), "abc");
722 assert_eq!(editor.cursor(), 0);
723 }
724
725 #[test]
726 fn test_delete_line_single() {
727 let mut editor = Editor::with_content("hello".to_string());
728 editor.cursor = 2;
729 editor.delete_line();
730 assert_eq!(editor.content(), "");
731 assert_eq!(editor.cursor(), 0);
732 }
733
734 #[test]
735 fn test_clear_line() {
736 let mut editor = Editor::with_content("abc\ndef\nghi".to_string());
737 editor.cursor = 5;
738 editor.clear_line();
739 assert_eq!(editor.content(), "abc\n\nghi");
740 assert_eq!(editor.cursor(), 4);
741 }
742
743 #[test]
744 fn test_clear_line_single() {
745 let mut editor = Editor::with_content("hello".to_string());
746 editor.cursor = 2;
747 editor.clear_line();
748 assert_eq!(editor.content(), "");
749 assert_eq!(editor.cursor(), 0);
750 }
751
752 #[test]
753 fn test_insert_str() {
754 let mut editor = Editor::with_content("hd".to_string());
755 editor.cursor = 1;
756 editor.insert_str("ello worl");
757 assert_eq!(editor.content(), "hello world");
758 assert_eq!(editor.cursor(), 10);
759 }
760
761 #[test]
762 fn test_insert_str_undo() {
763 let mut editor = Editor::with_content("ad".to_string());
764 editor.cursor = 1;
765 editor.insert_str("bc");
766 assert_eq!(editor.content(), "abcd");
767 editor.undo();
768 assert_eq!(editor.content(), "ad");
769 }
770
771 #[test]
772 fn test_open_line_below() {
773 let mut editor = Editor::with_content("abc\ndef".to_string());
774 editor.cursor = 1;
775 editor.open_line_below();
776 assert_eq!(editor.content(), "abc\n\ndef");
777 assert_eq!(editor.cursor(), 4);
778 }
779
780 #[test]
781 fn test_open_line_above() {
782 let mut editor = Editor::with_content("abc\ndef".to_string());
783 editor.cursor = 5;
784 editor.open_line_above();
785 assert_eq!(editor.content(), "abc\n\ndef");
786 assert_eq!(editor.cursor(), 4);
787 }
788
789 #[test]
790 fn test_move_to_first_non_blank() {
791 let mut editor = Editor::with_content(" hello".to_string());
792 editor.cursor = 7;
793 editor.move_to_first_non_blank();
794 assert_eq!(editor.cursor(), 3);
795 }
796
797 #[test]
798 fn test_move_to_first_line() {
799 let mut editor = Editor::with_content("abc\ndef\nghi".to_string());
800 editor.move_to_first_line();
801 assert_eq!(editor.cursor(), 0);
802 }
803
804 #[test]
805 fn test_move_to_last_line() {
806 let mut editor = Editor::with_content("abc\ndef\nghi".to_string());
807 editor.cursor = 0;
808 editor.move_to_last_line();
809 let (line, _) = editor.cursor_line_col();
810 assert_eq!(line, 2);
811 }
812
813 #[test]
814 fn test_move_word_forward_end() {
815 let mut editor = Editor::with_content("hello world foo".to_string());
816 editor.cursor = 0;
817 editor.move_word_forward_end();
818 assert_eq!(editor.cursor(), 4);
819 editor.move_word_forward_end();
820 assert_eq!(editor.cursor(), 10);
821 }
822
823 #[test]
824 fn test_move_left_in_line_normal() {
825 let mut editor = Editor::with_content("hello".to_string());
826 editor.move_left_in_line();
828 assert_eq!(editor.cursor(), 4);
829 }
830
831 #[test]
832 fn test_move_left_in_line_at_line_start() {
833 let mut editor = Editor::with_content("abc\ndef".to_string());
834 editor.cursor = 4;
836 editor.move_left_in_line();
837 assert_eq!(editor.cursor(), 4);
839 }
840
841 #[test]
842 fn test_move_left_in_line_at_content_start() {
843 let mut editor = Editor::with_content("hello".to_string());
844 editor.cursor = 0;
845 editor.move_left_in_line();
846 assert_eq!(editor.cursor(), 0);
847 }
848}