1use crate::{Action, EditResult, Key, KeyCode, LineEditor, TextEdit};
4use std::ops::Range;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
8pub enum Mode {
9 #[default]
10 Normal,
11 Insert,
12 OperatorPending(Operator),
13 Visual,
14 ReplaceChar,
16}
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum Operator {
21 Delete,
22 Change,
23 Yank,
24}
25
26#[derive(Debug, Clone)]
31pub struct VimLineEditor {
32 cursor: usize,
33 mode: Mode,
34 visual_anchor: Option<usize>,
36 yank_buffer: String,
38}
39
40impl Default for VimLineEditor {
41 fn default() -> Self {
42 Self::new()
43 }
44}
45
46impl VimLineEditor {
47 pub fn new() -> Self {
49 Self {
50 cursor: 0,
51 mode: Mode::Normal,
52 visual_anchor: None,
53 yank_buffer: String::new(),
54 }
55 }
56
57 pub fn mode(&self) -> Mode {
59 self.mode
60 }
61
62 fn clamp_cursor(&mut self, text: &str) {
64 self.cursor = self.cursor.min(text.len());
65 }
66
67 fn move_left(&mut self, text: &str) {
69 if self.cursor > 0 {
70 let mut new_pos = self.cursor - 1;
72 while new_pos > 0 && !text.is_char_boundary(new_pos) {
73 new_pos -= 1;
74 }
75 self.cursor = new_pos;
76 }
77 }
78
79 fn move_right(&mut self, text: &str) {
81 if self.cursor < text.len() {
82 let mut new_pos = self.cursor + 1;
84 while new_pos < text.len() && !text.is_char_boundary(new_pos) {
85 new_pos += 1;
86 }
87 self.cursor = new_pos;
88 }
89 }
90
91 fn move_line_start(&mut self, text: &str) {
93 self.cursor = text[..self.cursor].rfind('\n').map(|i| i + 1).unwrap_or(0);
95 }
96
97 fn move_first_non_blank(&mut self, text: &str) {
99 self.move_line_start(text);
100 let line_start = self.cursor;
102 for (i, c) in text[line_start..].char_indices() {
103 if c == '\n' || !c.is_whitespace() {
104 self.cursor = line_start + i;
105 return;
106 }
107 }
108 }
109
110 fn move_line_end_impl(&mut self, text: &str, past_end: bool) {
114 let line_end = text[self.cursor..]
116 .find('\n')
117 .map(|i| self.cursor + i)
118 .unwrap_or(text.len());
119
120 if past_end || line_end == 0 {
121 self.cursor = line_end;
122 } else {
123 let mut last_char_start = line_end.saturating_sub(1);
126 while last_char_start > 0 && !text.is_char_boundary(last_char_start) {
127 last_char_start -= 1;
128 }
129 self.cursor = last_char_start;
130 }
131 }
132
133 fn move_line_end(&mut self, text: &str) {
135 self.move_line_end_impl(text, false);
136 }
137
138 fn move_line_end_insert(&mut self, text: &str) {
140 self.move_line_end_impl(text, true);
141 }
142
143 fn move_word_forward(&mut self, text: &str) {
145 let bytes = text.as_bytes();
146 let mut pos = self.cursor;
147
148 while pos < bytes.len() && !bytes[pos].is_ascii_whitespace() {
150 pos += 1;
151 }
152 while pos < bytes.len() && bytes[pos].is_ascii_whitespace() {
154 pos += 1;
155 }
156
157 self.cursor = pos;
158 }
159
160 fn move_word_backward(&mut self, text: &str) {
162 let bytes = text.as_bytes();
163 let mut pos = self.cursor;
164
165 while pos > 0 && bytes[pos - 1].is_ascii_whitespace() {
167 pos -= 1;
168 }
169 while pos > 0 && !bytes[pos - 1].is_ascii_whitespace() {
171 pos -= 1;
172 }
173
174 self.cursor = pos;
175 }
176
177 fn move_word_end(&mut self, text: &str) {
179 let bytes = text.as_bytes();
180 let mut pos = self.cursor;
181
182 if pos < bytes.len() {
184 pos += 1;
185 }
186 while pos < bytes.len() && bytes[pos].is_ascii_whitespace() {
188 pos += 1;
189 }
190 while pos < bytes.len() && !bytes[pos].is_ascii_whitespace() {
192 pos += 1;
193 }
194 if pos > self.cursor + 1 {
196 pos -= 1;
197 }
198
199 self.cursor = pos;
200 }
201
202 fn move_up(&mut self, text: &str) {
204 let line_start = text[..self.cursor].rfind('\n').map(|i| i + 1).unwrap_or(0);
206
207 if line_start == 0 {
208 return;
210 }
211
212 let col = self.cursor - line_start;
214
215 let prev_line_start = text[..line_start - 1]
217 .rfind('\n')
218 .map(|i| i + 1)
219 .unwrap_or(0);
220
221 let prev_line_end = line_start - 1; let prev_line_len = prev_line_end - prev_line_start;
224
225 self.cursor = prev_line_start + col.min(prev_line_len);
227 }
228
229 fn move_down(&mut self, text: &str) {
231 let line_start = text[..self.cursor].rfind('\n').map(|i| i + 1).unwrap_or(0);
233
234 let col = self.cursor - line_start;
236
237 let Some(newline_pos) = text[self.cursor..].find('\n') else {
239 return;
241 };
242 let next_line_start = self.cursor + newline_pos + 1;
243
244 if next_line_start >= text.len() {
245 self.cursor = text.len();
247 return;
248 }
249
250 let next_line_end = text[next_line_start..]
252 .find('\n')
253 .map(|i| next_line_start + i)
254 .unwrap_or(text.len());
255
256 let next_line_len = next_line_end - next_line_start;
257
258 self.cursor = next_line_start + col.min(next_line_len);
260 }
261
262 fn move_to_matching_bracket(&mut self, text: &str) {
265 if self.cursor >= text.len() {
266 return;
267 }
268
269 let char_at_cursor = text[self.cursor..].chars().next();
271 let c = match char_at_cursor {
272 Some(c) => c,
273 None => return,
274 };
275
276 let pairs = [('(', ')'), ('[', ']'), ('{', '}'), ('<', '>')];
278
279 for (open, close) in pairs.iter() {
281 if c == *open {
282 if let Some(pos) = self.find_matching_forward(text, *open, *close) {
284 self.cursor = pos;
285 }
286 return;
287 }
288 if c == *close {
289 if let Some(pos) = self.find_matching_backward(text, *open, *close) {
291 self.cursor = pos;
292 }
293 return;
294 }
295 }
296 }
297
298 fn find_matching_forward(&self, text: &str, open: char, close: char) -> Option<usize> {
300 let mut depth = 1;
301 let mut pos = self.cursor;
302
303 pos += open.len_utf8();
305
306 for (i, c) in text[pos..].char_indices() {
307 if c == open {
308 depth += 1;
309 } else if c == close {
310 depth -= 1;
311 if depth == 0 {
312 return Some(pos + i);
313 }
314 }
315 }
316 None
317 }
318
319 fn find_matching_backward(&self, text: &str, open: char, close: char) -> Option<usize> {
321 let mut depth = 1;
322
323 let search_text = &text[..self.cursor];
325 for (i, c) in search_text.char_indices().rev() {
326 if c == close {
327 depth += 1;
328 } else if c == open {
329 depth -= 1;
330 if depth == 0 {
331 return Some(i);
332 }
333 }
334 }
335 None
336 }
337
338 fn delete_char(&mut self, text: &str) -> EditResult {
340 if self.cursor >= text.len() {
341 return EditResult::none();
342 }
343
344 let start = self.cursor;
345
346 let mut end = self.cursor + 1;
348 while end < text.len() && !text.is_char_boundary(end) {
349 end += 1;
350 }
351
352 let deleted = text[start..end].to_string();
353
354 if end >= text.len() && self.cursor > 0 {
357 self.move_left(text);
358 }
359
360 EditResult::edit_and_yank(TextEdit::Delete { start, end }, deleted)
361 }
362
363 fn delete_to_end(&mut self, text: &str) -> EditResult {
365 let end = text[self.cursor..]
366 .find('\n')
367 .map(|i| self.cursor + i)
368 .unwrap_or(text.len());
369
370 if self.cursor >= end {
371 return EditResult::none();
372 }
373
374 let deleted = text[self.cursor..end].to_string();
375 EditResult::edit_and_yank(
376 TextEdit::Delete {
377 start: self.cursor,
378 end,
379 },
380 deleted,
381 )
382 }
383
384 fn delete_line(&mut self, text: &str) -> EditResult {
386 let line_start = text[..self.cursor].rfind('\n').map(|i| i + 1).unwrap_or(0);
387
388 let line_end = text[self.cursor..]
389 .find('\n')
390 .map(|i| self.cursor + i + 1) .unwrap_or(text.len());
392
393 let (start, end) = if line_start == 0 && line_end == text.len() {
395 (0, text.len())
396 } else if line_end == text.len() && line_start > 0 {
397 (line_start - 1, text.len())
399 } else {
400 (line_start, line_end)
401 };
402
403 let deleted = text[start..end].to_string();
404 self.cursor = start;
405
406 EditResult::edit_and_yank(TextEdit::Delete { start, end }, deleted)
407 }
408
409 fn paste_after(&mut self, text: &str) -> EditResult {
411 if self.yank_buffer.is_empty() {
412 return EditResult::none();
413 }
414
415 let insert_pos = (self.cursor + 1).min(text.len());
416 let to_insert = self.yank_buffer.clone();
417 self.cursor = (insert_pos + to_insert.len())
419 .saturating_sub(1)
420 .min(text.len() + to_insert.len());
421
422 EditResult::edit(TextEdit::Insert {
423 at: insert_pos,
424 text: to_insert,
425 })
426 }
427
428 fn paste_before(&mut self, text: &str) -> EditResult {
430 if self.yank_buffer.is_empty() {
431 return EditResult::none();
432 }
433
434 let to_insert = self.yank_buffer.clone();
435 let insert_pos = self.cursor.min(text.len());
436 self.cursor = (insert_pos + to_insert.len()).min(text.len() + to_insert.len());
438
439 EditResult::edit(TextEdit::Insert {
440 at: insert_pos,
441 text: to_insert,
442 })
443 }
444
445 fn handle_normal(&mut self, key: Key, text: &str) -> EditResult {
447 match key.code {
448 KeyCode::Char('i') => {
450 self.mode = Mode::Insert;
451 EditResult::none()
452 }
453 KeyCode::Char('a') => {
454 self.mode = Mode::Insert;
455 self.move_right(text);
456 EditResult::none()
457 }
458 KeyCode::Char('A') => {
459 self.mode = Mode::Insert;
460 self.move_line_end_insert(text);
461 EditResult::none()
462 }
463 KeyCode::Char('I') => {
464 self.mode = Mode::Insert;
465 self.move_first_non_blank(text);
466 EditResult::none()
467 }
468 KeyCode::Char('o') => {
469 self.mode = Mode::Insert;
470 self.move_line_end(text);
471 let pos = self.cursor;
472 self.cursor = pos + 1;
473 EditResult::edit(TextEdit::Insert {
474 at: pos,
475 text: "\n".to_string(),
476 })
477 }
478 KeyCode::Char('O') => {
479 self.mode = Mode::Insert;
480 self.move_line_start(text);
481 let pos = self.cursor;
482 EditResult::edit(TextEdit::Insert {
483 at: pos,
484 text: "\n".to_string(),
485 })
486 }
487
488 KeyCode::Char('v') => {
490 self.mode = Mode::Visual;
491 self.visual_anchor = Some(self.cursor);
492 EditResult::none()
493 }
494
495 KeyCode::Char('h') | KeyCode::Left => {
497 self.move_left(text);
498 EditResult::cursor_only()
499 }
500 KeyCode::Char('l') | KeyCode::Right => {
501 self.move_right(text);
502 EditResult::cursor_only()
503 }
504 KeyCode::Char('j') => {
505 self.move_down(text);
506 EditResult::cursor_only()
507 }
508 KeyCode::Char('k') => {
509 self.move_up(text);
510 EditResult::cursor_only()
511 }
512 KeyCode::Char('0') | KeyCode::Home => {
513 self.move_line_start(text);
514 EditResult::cursor_only()
515 }
516 KeyCode::Char('^') => {
517 self.move_first_non_blank(text);
518 EditResult::cursor_only()
519 }
520 KeyCode::Char('$') | KeyCode::End => {
521 self.move_line_end(text);
522 EditResult::cursor_only()
523 }
524 KeyCode::Char('w') => {
525 self.move_word_forward(text);
526 EditResult::cursor_only()
527 }
528 KeyCode::Char('b') => {
529 self.move_word_backward(text);
530 EditResult::cursor_only()
531 }
532 KeyCode::Char('e') => {
533 self.move_word_end(text);
534 EditResult::cursor_only()
535 }
536 KeyCode::Char('%') => {
537 self.move_to_matching_bracket(text);
538 EditResult::cursor_only()
539 }
540
541 KeyCode::Char('c') if key.ctrl => EditResult::action(Action::Cancel),
543
544 KeyCode::Char('d') => {
546 self.mode = Mode::OperatorPending(Operator::Delete);
547 EditResult::none()
548 }
549 KeyCode::Char('c') => {
550 self.mode = Mode::OperatorPending(Operator::Change);
551 EditResult::none()
552 }
553 KeyCode::Char('y') => {
554 self.mode = Mode::OperatorPending(Operator::Yank);
555 EditResult::none()
556 }
557
558 KeyCode::Char('x') => self.delete_char(text),
560 KeyCode::Char('D') => self.delete_to_end(text),
561 KeyCode::Char('C') => {
562 self.mode = Mode::Insert;
563 self.delete_to_end(text)
564 }
565
566 KeyCode::Char('r') => {
568 self.mode = Mode::ReplaceChar;
569 EditResult::none()
570 }
571
572 KeyCode::Char('p') => self.paste_after(text),
574 KeyCode::Char('P') => self.paste_before(text),
575
576 KeyCode::Up => EditResult::action(Action::HistoryPrev),
578 KeyCode::Down => EditResult::action(Action::HistoryNext),
579
580 KeyCode::Enter if !key.shift => EditResult::action(Action::Submit),
582
583 KeyCode::Enter if key.shift => {
585 self.mode = Mode::Insert;
586 let pos = self.cursor;
587 self.cursor = pos + 1;
588 EditResult::edit(TextEdit::Insert {
589 at: pos,
590 text: "\n".to_string(),
591 })
592 }
593
594 KeyCode::Escape => EditResult::none(),
597
598 _ => EditResult::none(),
599 }
600 }
601
602 fn handle_insert(&mut self, key: Key, text: &str) -> EditResult {
604 match key.code {
605 KeyCode::Escape => {
606 self.mode = Mode::Normal;
607 if self.cursor > 0 {
609 self.move_left(text);
610 }
611 EditResult::none()
612 }
613
614 KeyCode::Char('c') if key.ctrl => {
616 self.mode = Mode::Normal;
617 EditResult::none()
618 }
619
620 KeyCode::Char(c) if !key.ctrl && !key.alt => {
621 let pos = self.cursor;
622 self.cursor = pos + c.len_utf8();
623 EditResult::edit(TextEdit::Insert {
624 at: pos,
625 text: c.to_string(),
626 })
627 }
628
629 KeyCode::Backspace => {
630 if self.cursor == 0 {
631 return EditResult::none();
632 }
633 let mut start = self.cursor - 1;
634 while start > 0 && !text.is_char_boundary(start) {
635 start -= 1;
636 }
637 let end = self.cursor; self.cursor = start;
639 EditResult::edit(TextEdit::Delete { start, end })
640 }
641
642 KeyCode::Delete => self.delete_char(text),
643
644 KeyCode::Left => {
645 self.move_left(text);
646 EditResult::cursor_only()
647 }
648 KeyCode::Right => {
649 self.move_right(text);
650 EditResult::cursor_only()
651 }
652 KeyCode::Up => {
653 self.move_up(text);
654 EditResult::cursor_only()
655 }
656 KeyCode::Down => {
657 self.move_down(text);
658 EditResult::cursor_only()
659 }
660 KeyCode::Home => {
661 self.move_line_start(text);
662 EditResult::cursor_only()
663 }
664 KeyCode::End => {
665 self.move_line_end_insert(text);
667 EditResult::cursor_only()
668 }
669
670 KeyCode::Enter => {
672 let pos = self.cursor;
673 self.cursor = pos + 1;
674 EditResult::edit(TextEdit::Insert {
675 at: pos,
676 text: "\n".to_string(),
677 })
678 }
679
680 _ => EditResult::none(),
681 }
682 }
683
684 fn handle_operator_pending(&mut self, op: Operator, key: Key, text: &str) -> EditResult {
686 if key.code == KeyCode::Escape {
688 self.mode = Mode::Normal;
689 return EditResult::none();
690 }
691
692 let is_line_op = matches!(
694 (op, key.code),
695 (Operator::Delete, KeyCode::Char('d'))
696 | (Operator::Change, KeyCode::Char('c'))
697 | (Operator::Yank, KeyCode::Char('y'))
698 );
699
700 if is_line_op {
701 self.mode = Mode::Normal;
702 return self.apply_operator_line(op, text);
703 }
704
705 let start = self.cursor;
707 match key.code {
708 KeyCode::Char('w') => {
709 if op == Operator::Change {
712 self.move_word_end(text);
713 if self.cursor < text.len() {
715 self.cursor += 1;
716 }
717 } else {
718 self.move_word_forward(text);
719 }
720 }
721 KeyCode::Char('b') => self.move_word_backward(text),
722 KeyCode::Char('e') => {
723 self.move_word_end(text);
724 if self.cursor < text.len() {
726 self.cursor += 1;
727 }
728 }
729 KeyCode::Char('0') | KeyCode::Home => self.move_line_start(text),
730 KeyCode::Char('$') | KeyCode::End => self.move_line_end(text),
731 KeyCode::Char('^') => self.move_first_non_blank(text),
732 KeyCode::Char('h') | KeyCode::Left => self.move_left(text),
733 KeyCode::Char('l') | KeyCode::Right => self.move_right(text),
734 KeyCode::Char('j') => self.move_down(text),
735 KeyCode::Char('k') => self.move_up(text),
736 _ => {
737 self.mode = Mode::Normal;
739 return EditResult::none();
740 }
741 }
742
743 let end = self.cursor;
744 self.mode = Mode::Normal;
745
746 if start == end {
747 return EditResult::none();
748 }
749
750 let (range_start, range_end) = if start < end {
751 (start, end)
752 } else {
753 (end, start)
754 };
755
756 self.apply_operator(op, range_start, range_end, text)
757 }
758
759 fn apply_operator(&mut self, op: Operator, start: usize, end: usize, text: &str) -> EditResult {
761 let affected = text[start..end].to_string();
762 self.yank_buffer = affected.clone();
763 self.cursor = start;
764
765 match op {
766 Operator::Delete => {
767 EditResult::edit_and_yank(TextEdit::Delete { start, end }, affected)
768 }
769 Operator::Change => {
770 self.mode = Mode::Insert;
771 EditResult::edit_and_yank(TextEdit::Delete { start, end }, affected)
772 }
773 Operator::Yank => {
774 EditResult {
776 yanked: Some(affected),
777 ..Default::default()
778 }
779 }
780 }
781 }
782
783 fn apply_operator_line(&mut self, op: Operator, text: &str) -> EditResult {
785 match op {
786 Operator::Delete => self.delete_line(text),
787 Operator::Change => {
788 let result = self.delete_line(text);
789 self.mode = Mode::Insert;
790 result
791 }
792 Operator::Yank => {
793 let line_start = text[..self.cursor].rfind('\n').map(|i| i + 1).unwrap_or(0);
794 let line_end = text[self.cursor..]
795 .find('\n')
796 .map(|i| self.cursor + i + 1)
797 .unwrap_or(text.len());
798 let line = text[line_start..line_end].to_string();
799 self.yank_buffer = line.clone();
800 EditResult {
801 yanked: Some(line),
802 ..Default::default()
803 }
804 }
805 }
806 }
807
808 fn handle_visual(&mut self, key: Key, text: &str) -> EditResult {
810 match key.code {
811 KeyCode::Escape => {
812 self.mode = Mode::Normal;
813 self.visual_anchor = None;
814 EditResult::none()
815 }
816
817 KeyCode::Char('h') | KeyCode::Left => {
819 self.move_left(text);
820 EditResult::cursor_only()
821 }
822 KeyCode::Char('l') | KeyCode::Right => {
823 self.move_right(text);
824 EditResult::cursor_only()
825 }
826 KeyCode::Char('j') => {
827 self.move_down(text);
828 EditResult::cursor_only()
829 }
830 KeyCode::Char('k') => {
831 self.move_up(text);
832 EditResult::cursor_only()
833 }
834 KeyCode::Char('w') => {
835 self.move_word_forward(text);
836 EditResult::cursor_only()
837 }
838 KeyCode::Char('b') => {
839 self.move_word_backward(text);
840 EditResult::cursor_only()
841 }
842 KeyCode::Char('e') => {
843 self.move_word_end(text);
844 EditResult::cursor_only()
845 }
846 KeyCode::Char('0') | KeyCode::Home => {
847 self.move_line_start(text);
848 EditResult::cursor_only()
849 }
850 KeyCode::Char('$') | KeyCode::End => {
851 self.move_line_end(text);
852 EditResult::cursor_only()
853 }
854
855 KeyCode::Char('d') | KeyCode::Char('x') => {
857 let (start, end) = self.selection_range();
858 self.mode = Mode::Normal;
859 self.visual_anchor = None;
860 self.apply_operator(Operator::Delete, start, end, text)
861 }
862 KeyCode::Char('c') => {
863 let (start, end) = self.selection_range();
864 self.mode = Mode::Normal;
865 self.visual_anchor = None;
866 self.apply_operator(Operator::Change, start, end, text)
867 }
868 KeyCode::Char('y') => {
869 let (start, end) = self.selection_range();
870 self.mode = Mode::Normal;
871 self.visual_anchor = None;
872 self.apply_operator(Operator::Yank, start, end, text)
873 }
874
875 _ => EditResult::none(),
876 }
877 }
878
879 fn handle_replace_char(&mut self, key: Key, text: &str) -> EditResult {
881 self.mode = Mode::Normal;
882
883 match key.code {
884 KeyCode::Escape => EditResult::none(),
885 KeyCode::Char(c) if !key.ctrl && !key.alt => {
886 if self.cursor >= text.len() {
888 return EditResult::none();
889 }
890
891 let mut end = self.cursor + 1;
893 while end < text.len() && !text.is_char_boundary(end) {
894 end += 1;
895 }
896
897 EditResult {
900 edits: vec![
901 TextEdit::Insert {
902 at: self.cursor,
903 text: c.to_string(),
904 },
905 TextEdit::Delete {
906 start: self.cursor,
907 end,
908 },
909 ],
910 ..Default::default()
911 }
912 }
913 _ => EditResult::none(),
914 }
915 }
916
917 fn selection_range(&self) -> (usize, usize) {
919 let anchor = self.visual_anchor.unwrap_or(self.cursor);
920 if self.cursor < anchor {
921 (self.cursor, anchor)
922 } else {
923 (anchor, self.cursor + 1) }
925 }
926}
927
928impl LineEditor for VimLineEditor {
929 fn handle_key(&mut self, key: Key, text: &str) -> EditResult {
930 self.clamp_cursor(text);
931
932 let result = match self.mode {
933 Mode::Normal => self.handle_normal(key, text),
934 Mode::Insert => self.handle_insert(key, text),
935 Mode::OperatorPending(op) => self.handle_operator_pending(op, key, text),
936 Mode::Visual => self.handle_visual(key, text),
937 Mode::ReplaceChar => self.handle_replace_char(key, text),
938 };
939
940 if let Some(ref yanked) = result.yanked {
942 self.yank_buffer = yanked.clone();
943 }
944
945 result
946 }
947
948 fn cursor(&self) -> usize {
949 self.cursor
950 }
951
952 fn status(&self) -> &str {
953 match self.mode {
954 Mode::Normal => "NORMAL",
955 Mode::Insert => "INSERT",
956 Mode::OperatorPending(Operator::Delete) => "d...",
957 Mode::OperatorPending(Operator::Change) => "c...",
958 Mode::OperatorPending(Operator::Yank) => "y...",
959 Mode::Visual => "VISUAL",
960 Mode::ReplaceChar => "r...",
961 }
962 }
963
964 fn selection(&self) -> Option<Range<usize>> {
965 if self.mode == Mode::Visual {
966 let (start, end) = self.selection_range();
967 Some(start..end)
968 } else {
969 None
970 }
971 }
972
973 fn reset(&mut self) {
974 self.cursor = 0;
975 self.mode = Mode::Normal;
976 self.visual_anchor = None;
977 }
979
980 fn set_cursor(&mut self, pos: usize, text: &str) {
981 let pos = pos.min(text.len());
983 self.cursor = if text.is_char_boundary(pos) {
984 pos
985 } else {
986 let mut p = pos;
988 while p > 0 && !text.is_char_boundary(p) {
989 p -= 1;
990 }
991 p
992 };
993 }
994}
995
996#[cfg(test)]
997mod tests {
998 use super::*;
999
1000 #[test]
1001 fn test_basic_motion() {
1002 let mut editor = VimLineEditor::new();
1003 let text = "hello world";
1004
1005 editor.handle_key(Key::char('l'), text);
1007 assert_eq!(editor.cursor(), 1);
1008
1009 editor.handle_key(Key::char('w'), text);
1011 assert_eq!(editor.cursor(), 6); editor.handle_key(Key::char('$'), text);
1015 assert_eq!(editor.cursor(), 10); editor.handle_key(Key::char('0'), text);
1019 assert_eq!(editor.cursor(), 0);
1020 }
1021
1022 #[test]
1023 fn test_mode_switching() {
1024 let mut editor = VimLineEditor::new();
1025 let text = "hello";
1026
1027 assert_eq!(editor.mode(), Mode::Normal);
1028
1029 editor.handle_key(Key::char('i'), text);
1030 assert_eq!(editor.mode(), Mode::Insert);
1031
1032 editor.handle_key(Key::code(KeyCode::Escape), text);
1033 assert_eq!(editor.mode(), Mode::Normal);
1034 }
1035
1036 #[test]
1037 fn test_delete_word() {
1038 let mut editor = VimLineEditor::new();
1039 let text = "hello world";
1040
1041 editor.handle_key(Key::char('d'), text);
1043 editor.handle_key(Key::char('w'), text);
1044
1045 assert_eq!(editor.mode(), Mode::Normal);
1047 }
1048
1049 #[test]
1050 fn test_insert_char() {
1051 let mut editor = VimLineEditor::new();
1052 let text = "";
1053
1054 editor.handle_key(Key::char('i'), text);
1055 let result = editor.handle_key(Key::char('x'), text);
1056
1057 assert_eq!(result.edits.len(), 1);
1058 match &result.edits[0] {
1059 TextEdit::Insert { at, text } => {
1060 assert_eq!(*at, 0);
1061 assert_eq!(text, "x");
1062 }
1063 _ => panic!("Expected Insert"),
1064 }
1065 }
1066
1067 #[test]
1068 fn test_visual_mode() {
1069 let mut editor = VimLineEditor::new();
1070 let text = "hello world";
1071
1072 editor.handle_key(Key::char('v'), text);
1074 assert_eq!(editor.mode(), Mode::Visual);
1075
1076 editor.handle_key(Key::char('w'), text);
1078
1079 let sel = editor.selection().unwrap();
1081 assert_eq!(sel.start, 0);
1082 assert!(sel.end > 0);
1083 }
1084
1085 #[test]
1086 fn test_backspace_ascii() {
1087 let mut editor = VimLineEditor::new();
1088 let mut text = String::from("abc");
1089
1090 editor.handle_key(Key::char('i'), &text);
1092 editor.handle_key(Key::code(KeyCode::End), &text);
1093 assert_eq!(editor.cursor(), 3);
1094
1095 let result = editor.handle_key(Key::code(KeyCode::Backspace), &text);
1097 for edit in result.edits.into_iter().rev() {
1098 edit.apply(&mut text);
1099 }
1100 assert_eq!(text, "ab");
1101 assert_eq!(editor.cursor(), 2);
1102 }
1103
1104 #[test]
1105 fn test_backspace_unicode() {
1106 let mut editor = VimLineEditor::new();
1107 let mut text = String::from("a😀b");
1108
1109 editor.handle_key(Key::char('i'), &text);
1111 editor.handle_key(Key::code(KeyCode::End), &text);
1112 editor.handle_key(Key::code(KeyCode::Left), &text); assert_eq!(editor.cursor(), 5); let result = editor.handle_key(Key::code(KeyCode::Backspace), &text);
1117 for edit in result.edits.into_iter().rev() {
1118 edit.apply(&mut text);
1119 }
1120 assert_eq!(text, "ab");
1121 assert_eq!(editor.cursor(), 1);
1122 }
1123
1124 #[test]
1125 fn test_yank_and_paste() {
1126 let mut editor = VimLineEditor::new();
1127 let mut text = String::from("hello world");
1128
1129 editor.handle_key(Key::char('y'), &text);
1131 let result = editor.handle_key(Key::char('w'), &text);
1132 assert!(result.yanked.is_some());
1133 assert_eq!(result.yanked.unwrap(), "hello ");
1134
1135 editor.handle_key(Key::char('$'), &text);
1137 let result = editor.handle_key(Key::char('p'), &text);
1138
1139 for edit in result.edits.into_iter().rev() {
1140 edit.apply(&mut text);
1141 }
1142 assert_eq!(text, "hello worldhello ");
1143 }
1144
1145 #[test]
1146 fn test_visual_mode_delete() {
1147 let mut editor = VimLineEditor::new();
1148 let mut text = String::from("hello world");
1149
1150 editor.handle_key(Key::char('v'), &text);
1152 assert_eq!(editor.mode(), Mode::Visual);
1153
1154 editor.handle_key(Key::char('e'), &text);
1156
1157 let result = editor.handle_key(Key::char('d'), &text);
1159
1160 for edit in result.edits.into_iter().rev() {
1161 edit.apply(&mut text);
1162 }
1163 assert_eq!(text, " world");
1164 assert_eq!(editor.mode(), Mode::Normal);
1165 }
1166
1167 #[test]
1168 fn test_operator_pending_escape() {
1169 let mut editor = VimLineEditor::new();
1170 let text = "hello world";
1171
1172 editor.handle_key(Key::char('d'), text);
1174 assert!(matches!(editor.mode(), Mode::OperatorPending(_)));
1175
1176 editor.handle_key(Key::code(KeyCode::Escape), text);
1178 assert_eq!(editor.mode(), Mode::Normal);
1179 }
1180
1181 #[test]
1182 fn test_replace_char() {
1183 let mut editor = VimLineEditor::new();
1184 let mut text = String::from("hello");
1185
1186 editor.handle_key(Key::char('r'), &text);
1188 assert_eq!(editor.mode(), Mode::ReplaceChar);
1189
1190 let result = editor.handle_key(Key::char('x'), &text);
1191 assert_eq!(editor.mode(), Mode::Normal);
1192
1193 for edit in result.edits.into_iter().rev() {
1195 edit.apply(&mut text);
1196 }
1197 assert_eq!(text, "xello");
1198 }
1199
1200 #[test]
1201 fn test_replace_char_escape() {
1202 let mut editor = VimLineEditor::new();
1203 let text = "hello";
1204
1205 editor.handle_key(Key::char('r'), text);
1207 assert_eq!(editor.mode(), Mode::ReplaceChar);
1208
1209 editor.handle_key(Key::code(KeyCode::Escape), text);
1210 assert_eq!(editor.mode(), Mode::Normal);
1211 }
1212
1213 #[test]
1214 fn test_cw_no_trailing_space() {
1215 let mut editor = VimLineEditor::new();
1216 let mut text = String::from("hello world");
1217
1218 editor.handle_key(Key::char('c'), &text);
1220 let result = editor.handle_key(Key::char('w'), &text);
1221
1222 assert_eq!(editor.mode(), Mode::Insert);
1223
1224 for edit in result.edits.into_iter().rev() {
1226 edit.apply(&mut text);
1227 }
1228 assert_eq!(text, " world");
1230 }
1231
1232 #[test]
1233 fn test_dw_includes_trailing_space() {
1234 let mut editor = VimLineEditor::new();
1235 let mut text = String::from("hello world");
1236
1237 editor.handle_key(Key::char('d'), &text);
1239 let result = editor.handle_key(Key::char('w'), &text);
1240
1241 assert_eq!(editor.mode(), Mode::Normal);
1242
1243 for edit in result.edits.into_iter().rev() {
1245 edit.apply(&mut text);
1246 }
1247 assert_eq!(text, "world");
1248 }
1249
1250 #[test]
1251 fn test_paste_at_empty_buffer() {
1252 let mut editor = VimLineEditor::new();
1253
1254 let yank_text = String::from("test");
1256 editor.handle_key(Key::char('y'), &yank_text);
1257 editor.handle_key(Key::char('w'), &yank_text);
1258
1259 let mut text = String::new();
1261 editor.set_cursor(0, &text);
1262 let result = editor.handle_key(Key::char('p'), &text);
1263
1264 for edit in result.edits.into_iter().rev() {
1265 edit.apply(&mut text);
1266 }
1267 assert_eq!(text, "test");
1268 }
1269
1270 #[test]
1271 fn test_dollar_cursor_on_last_char() {
1272 let mut editor = VimLineEditor::new();
1273 let text = "abc";
1274
1275 editor.handle_key(Key::char('$'), text);
1277 assert_eq!(editor.cursor(), 2);
1278
1279 let text = "x";
1281 editor.set_cursor(0, text);
1282 editor.handle_key(Key::char('$'), text);
1283 assert_eq!(editor.cursor(), 0); }
1285
1286 #[test]
1287 fn test_x_delete_last_char_moves_cursor_left() {
1288 let mut editor = VimLineEditor::new();
1289 let mut text = String::from("abc");
1290
1291 editor.handle_key(Key::char('$'), &text);
1293 assert_eq!(editor.cursor(), 2); let result = editor.handle_key(Key::char('x'), &text);
1297 for edit in result.edits.into_iter().rev() {
1298 edit.apply(&mut text);
1299 }
1300
1301 assert_eq!(text, "ab");
1302 assert_eq!(editor.cursor(), 1); }
1305
1306 #[test]
1307 fn test_x_delete_middle_char_cursor_stays() {
1308 let mut editor = VimLineEditor::new();
1309 let mut text = String::from("abc");
1310
1311 editor.handle_key(Key::char('l'), &text);
1313 assert_eq!(editor.cursor(), 1);
1314
1315 let result = editor.handle_key(Key::char('x'), &text);
1317 for edit in result.edits.into_iter().rev() {
1318 edit.apply(&mut text);
1319 }
1320
1321 assert_eq!(text, "ac");
1322 assert_eq!(editor.cursor(), 1);
1324 }
1325
1326 #[test]
1327 fn test_percent_bracket_matching() {
1328 let mut editor = VimLineEditor::new();
1329 let text = "(hello world)";
1330
1331 assert_eq!(editor.cursor(), 0);
1333
1334 editor.handle_key(Key::char('%'), text);
1336 assert_eq!(editor.cursor(), 12); editor.handle_key(Key::char('%'), text);
1340 assert_eq!(editor.cursor(), 0);
1341 }
1342
1343 #[test]
1344 fn test_percent_nested_brackets() {
1345 let mut editor = VimLineEditor::new();
1346 let text = "([{<>}])";
1347
1348 editor.handle_key(Key::char('%'), text);
1350 assert_eq!(editor.cursor(), 7); editor.set_cursor(1, text);
1354 editor.handle_key(Key::char('%'), text);
1355 assert_eq!(editor.cursor(), 6); editor.set_cursor(2, text);
1359 editor.handle_key(Key::char('%'), text);
1360 assert_eq!(editor.cursor(), 5); editor.set_cursor(3, text);
1364 editor.handle_key(Key::char('%'), text);
1365 assert_eq!(editor.cursor(), 4); }
1367
1368 #[test]
1369 fn test_percent_on_non_bracket() {
1370 let mut editor = VimLineEditor::new();
1371 let text = "hello";
1372
1373 let orig_cursor = editor.cursor();
1375 editor.handle_key(Key::char('%'), text);
1376 assert_eq!(editor.cursor(), orig_cursor);
1378 }
1379}