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 delete_char(&mut self, text: &str) -> EditResult {
264 if self.cursor >= text.len() {
265 return EditResult::none();
266 }
267
268 let start = self.cursor;
269
270 let mut end = self.cursor + 1;
272 while end < text.len() && !text.is_char_boundary(end) {
273 end += 1;
274 }
275
276 let deleted = text[start..end].to_string();
277
278 if end >= text.len() && self.cursor > 0 {
281 self.move_left(text);
282 }
283
284 EditResult::edit_and_yank(TextEdit::Delete { start, end }, deleted)
285 }
286
287 fn delete_to_end(&mut self, text: &str) -> EditResult {
289 let end = text[self.cursor..]
290 .find('\n')
291 .map(|i| self.cursor + i)
292 .unwrap_or(text.len());
293
294 if self.cursor >= end {
295 return EditResult::none();
296 }
297
298 let deleted = text[self.cursor..end].to_string();
299 EditResult::edit_and_yank(
300 TextEdit::Delete {
301 start: self.cursor,
302 end,
303 },
304 deleted,
305 )
306 }
307
308 fn delete_line(&mut self, text: &str) -> EditResult {
310 let line_start = text[..self.cursor].rfind('\n').map(|i| i + 1).unwrap_or(0);
311
312 let line_end = text[self.cursor..]
313 .find('\n')
314 .map(|i| self.cursor + i + 1) .unwrap_or(text.len());
316
317 let (start, end) = if line_start == 0 && line_end == text.len() {
319 (0, text.len())
320 } else if line_end == text.len() && line_start > 0 {
321 (line_start - 1, text.len())
323 } else {
324 (line_start, line_end)
325 };
326
327 let deleted = text[start..end].to_string();
328 self.cursor = start;
329
330 EditResult::edit_and_yank(TextEdit::Delete { start, end }, deleted)
331 }
332
333 fn paste_after(&mut self, text: &str) -> EditResult {
335 if self.yank_buffer.is_empty() {
336 return EditResult::none();
337 }
338
339 let insert_pos = (self.cursor + 1).min(text.len());
340 let to_insert = self.yank_buffer.clone();
341 self.cursor = (insert_pos + to_insert.len())
343 .saturating_sub(1)
344 .min(text.len() + to_insert.len());
345
346 EditResult::edit(TextEdit::Insert {
347 at: insert_pos,
348 text: to_insert,
349 })
350 }
351
352 fn paste_before(&mut self, text: &str) -> EditResult {
354 if self.yank_buffer.is_empty() {
355 return EditResult::none();
356 }
357
358 let to_insert = self.yank_buffer.clone();
359 let insert_pos = self.cursor.min(text.len());
360 self.cursor = (insert_pos + to_insert.len()).min(text.len() + to_insert.len());
362
363 EditResult::edit(TextEdit::Insert {
364 at: insert_pos,
365 text: to_insert,
366 })
367 }
368
369 fn handle_normal(&mut self, key: Key, text: &str) -> EditResult {
371 match key.code {
372 KeyCode::Char('i') => {
374 self.mode = Mode::Insert;
375 EditResult::none()
376 }
377 KeyCode::Char('a') => {
378 self.mode = Mode::Insert;
379 self.move_right(text);
380 EditResult::none()
381 }
382 KeyCode::Char('A') => {
383 self.mode = Mode::Insert;
384 self.move_line_end_insert(text);
385 EditResult::none()
386 }
387 KeyCode::Char('I') => {
388 self.mode = Mode::Insert;
389 self.move_first_non_blank(text);
390 EditResult::none()
391 }
392 KeyCode::Char('o') => {
393 self.mode = Mode::Insert;
394 self.move_line_end(text);
395 let pos = self.cursor;
396 self.cursor = pos + 1;
397 EditResult::edit(TextEdit::Insert {
398 at: pos,
399 text: "\n".to_string(),
400 })
401 }
402 KeyCode::Char('O') => {
403 self.mode = Mode::Insert;
404 self.move_line_start(text);
405 let pos = self.cursor;
406 EditResult::edit(TextEdit::Insert {
407 at: pos,
408 text: "\n".to_string(),
409 })
410 }
411
412 KeyCode::Char('v') => {
414 self.mode = Mode::Visual;
415 self.visual_anchor = Some(self.cursor);
416 EditResult::none()
417 }
418
419 KeyCode::Char('h') | KeyCode::Left => {
421 self.move_left(text);
422 EditResult::cursor_only()
423 }
424 KeyCode::Char('l') | KeyCode::Right => {
425 self.move_right(text);
426 EditResult::cursor_only()
427 }
428 KeyCode::Char('j') => {
429 self.move_down(text);
430 EditResult::cursor_only()
431 }
432 KeyCode::Char('k') => {
433 self.move_up(text);
434 EditResult::cursor_only()
435 }
436 KeyCode::Char('0') | KeyCode::Home => {
437 self.move_line_start(text);
438 EditResult::cursor_only()
439 }
440 KeyCode::Char('^') => {
441 self.move_first_non_blank(text);
442 EditResult::cursor_only()
443 }
444 KeyCode::Char('$') | KeyCode::End => {
445 self.move_line_end(text);
446 EditResult::cursor_only()
447 }
448 KeyCode::Char('w') => {
449 self.move_word_forward(text);
450 EditResult::cursor_only()
451 }
452 KeyCode::Char('b') => {
453 self.move_word_backward(text);
454 EditResult::cursor_only()
455 }
456 KeyCode::Char('e') => {
457 self.move_word_end(text);
458 EditResult::cursor_only()
459 }
460
461 KeyCode::Char('c') if key.ctrl => EditResult::action(Action::Cancel),
463
464 KeyCode::Char('d') => {
466 self.mode = Mode::OperatorPending(Operator::Delete);
467 EditResult::none()
468 }
469 KeyCode::Char('c') => {
470 self.mode = Mode::OperatorPending(Operator::Change);
471 EditResult::none()
472 }
473 KeyCode::Char('y') => {
474 self.mode = Mode::OperatorPending(Operator::Yank);
475 EditResult::none()
476 }
477
478 KeyCode::Char('x') => self.delete_char(text),
480 KeyCode::Char('D') => self.delete_to_end(text),
481 KeyCode::Char('C') => {
482 self.mode = Mode::Insert;
483 self.delete_to_end(text)
484 }
485
486 KeyCode::Char('r') => {
488 self.mode = Mode::ReplaceChar;
489 EditResult::none()
490 }
491
492 KeyCode::Char('p') => self.paste_after(text),
494 KeyCode::Char('P') => self.paste_before(text),
495
496 KeyCode::Up => EditResult::action(Action::HistoryPrev),
498 KeyCode::Down => EditResult::action(Action::HistoryNext),
499
500 KeyCode::Enter if !key.shift => EditResult::action(Action::Submit),
502
503 KeyCode::Enter if key.shift => {
505 self.mode = Mode::Insert;
506 let pos = self.cursor;
507 self.cursor = pos + 1;
508 EditResult::edit(TextEdit::Insert {
509 at: pos,
510 text: "\n".to_string(),
511 })
512 }
513
514 KeyCode::Escape => EditResult::none(),
517
518 _ => EditResult::none(),
519 }
520 }
521
522 fn handle_insert(&mut self, key: Key, text: &str) -> EditResult {
524 match key.code {
525 KeyCode::Escape => {
526 self.mode = Mode::Normal;
527 if self.cursor > 0 {
529 self.move_left(text);
530 }
531 EditResult::none()
532 }
533
534 KeyCode::Char('c') if key.ctrl => {
536 self.mode = Mode::Normal;
537 EditResult::none()
538 }
539
540 KeyCode::Char(c) if !key.ctrl && !key.alt => {
541 let pos = self.cursor;
542 self.cursor = pos + c.len_utf8();
543 EditResult::edit(TextEdit::Insert {
544 at: pos,
545 text: c.to_string(),
546 })
547 }
548
549 KeyCode::Backspace => {
550 if self.cursor == 0 {
551 return EditResult::none();
552 }
553 let mut start = self.cursor - 1;
554 while start > 0 && !text.is_char_boundary(start) {
555 start -= 1;
556 }
557 let end = self.cursor; self.cursor = start;
559 EditResult::edit(TextEdit::Delete { start, end })
560 }
561
562 KeyCode::Delete => self.delete_char(text),
563
564 KeyCode::Left => {
565 self.move_left(text);
566 EditResult::cursor_only()
567 }
568 KeyCode::Right => {
569 self.move_right(text);
570 EditResult::cursor_only()
571 }
572 KeyCode::Up => {
573 self.move_up(text);
574 EditResult::cursor_only()
575 }
576 KeyCode::Down => {
577 self.move_down(text);
578 EditResult::cursor_only()
579 }
580 KeyCode::Home => {
581 self.move_line_start(text);
582 EditResult::cursor_only()
583 }
584 KeyCode::End => {
585 self.move_line_end_insert(text);
587 EditResult::cursor_only()
588 }
589
590 KeyCode::Enter => {
592 let pos = self.cursor;
593 self.cursor = pos + 1;
594 EditResult::edit(TextEdit::Insert {
595 at: pos,
596 text: "\n".to_string(),
597 })
598 }
599
600 _ => EditResult::none(),
601 }
602 }
603
604 fn handle_operator_pending(&mut self, op: Operator, key: Key, text: &str) -> EditResult {
606 if key.code == KeyCode::Escape {
608 self.mode = Mode::Normal;
609 return EditResult::none();
610 }
611
612 let is_line_op = matches!(
614 (op, key.code),
615 (Operator::Delete, KeyCode::Char('d'))
616 | (Operator::Change, KeyCode::Char('c'))
617 | (Operator::Yank, KeyCode::Char('y'))
618 );
619
620 if is_line_op {
621 self.mode = Mode::Normal;
622 return self.apply_operator_line(op, text);
623 }
624
625 let start = self.cursor;
627 match key.code {
628 KeyCode::Char('w') => {
629 if op == Operator::Change {
632 self.move_word_end(text);
633 if self.cursor < text.len() {
635 self.cursor += 1;
636 }
637 } else {
638 self.move_word_forward(text);
639 }
640 }
641 KeyCode::Char('b') => self.move_word_backward(text),
642 KeyCode::Char('e') => {
643 self.move_word_end(text);
644 if self.cursor < text.len() {
646 self.cursor += 1;
647 }
648 }
649 KeyCode::Char('0') | KeyCode::Home => self.move_line_start(text),
650 KeyCode::Char('$') | KeyCode::End => self.move_line_end(text),
651 KeyCode::Char('^') => self.move_first_non_blank(text),
652 KeyCode::Char('h') | KeyCode::Left => self.move_left(text),
653 KeyCode::Char('l') | KeyCode::Right => self.move_right(text),
654 KeyCode::Char('j') => self.move_down(text),
655 KeyCode::Char('k') => self.move_up(text),
656 _ => {
657 self.mode = Mode::Normal;
659 return EditResult::none();
660 }
661 }
662
663 let end = self.cursor;
664 self.mode = Mode::Normal;
665
666 if start == end {
667 return EditResult::none();
668 }
669
670 let (range_start, range_end) = if start < end {
671 (start, end)
672 } else {
673 (end, start)
674 };
675
676 self.apply_operator(op, range_start, range_end, text)
677 }
678
679 fn apply_operator(&mut self, op: Operator, start: usize, end: usize, text: &str) -> EditResult {
681 let affected = text[start..end].to_string();
682 self.yank_buffer = affected.clone();
683 self.cursor = start;
684
685 match op {
686 Operator::Delete => {
687 EditResult::edit_and_yank(TextEdit::Delete { start, end }, affected)
688 }
689 Operator::Change => {
690 self.mode = Mode::Insert;
691 EditResult::edit_and_yank(TextEdit::Delete { start, end }, affected)
692 }
693 Operator::Yank => {
694 EditResult {
696 yanked: Some(affected),
697 ..Default::default()
698 }
699 }
700 }
701 }
702
703 fn apply_operator_line(&mut self, op: Operator, text: &str) -> EditResult {
705 match op {
706 Operator::Delete => self.delete_line(text),
707 Operator::Change => {
708 let result = self.delete_line(text);
709 self.mode = Mode::Insert;
710 result
711 }
712 Operator::Yank => {
713 let line_start = text[..self.cursor].rfind('\n').map(|i| i + 1).unwrap_or(0);
714 let line_end = text[self.cursor..]
715 .find('\n')
716 .map(|i| self.cursor + i + 1)
717 .unwrap_or(text.len());
718 let line = text[line_start..line_end].to_string();
719 self.yank_buffer = line.clone();
720 EditResult {
721 yanked: Some(line),
722 ..Default::default()
723 }
724 }
725 }
726 }
727
728 fn handle_visual(&mut self, key: Key, text: &str) -> EditResult {
730 match key.code {
731 KeyCode::Escape => {
732 self.mode = Mode::Normal;
733 self.visual_anchor = None;
734 EditResult::none()
735 }
736
737 KeyCode::Char('h') | KeyCode::Left => {
739 self.move_left(text);
740 EditResult::cursor_only()
741 }
742 KeyCode::Char('l') | KeyCode::Right => {
743 self.move_right(text);
744 EditResult::cursor_only()
745 }
746 KeyCode::Char('j') => {
747 self.move_down(text);
748 EditResult::cursor_only()
749 }
750 KeyCode::Char('k') => {
751 self.move_up(text);
752 EditResult::cursor_only()
753 }
754 KeyCode::Char('w') => {
755 self.move_word_forward(text);
756 EditResult::cursor_only()
757 }
758 KeyCode::Char('b') => {
759 self.move_word_backward(text);
760 EditResult::cursor_only()
761 }
762 KeyCode::Char('e') => {
763 self.move_word_end(text);
764 EditResult::cursor_only()
765 }
766 KeyCode::Char('0') | KeyCode::Home => {
767 self.move_line_start(text);
768 EditResult::cursor_only()
769 }
770 KeyCode::Char('$') | KeyCode::End => {
771 self.move_line_end(text);
772 EditResult::cursor_only()
773 }
774
775 KeyCode::Char('d') | KeyCode::Char('x') => {
777 let (start, end) = self.selection_range();
778 self.mode = Mode::Normal;
779 self.visual_anchor = None;
780 self.apply_operator(Operator::Delete, start, end, text)
781 }
782 KeyCode::Char('c') => {
783 let (start, end) = self.selection_range();
784 self.mode = Mode::Normal;
785 self.visual_anchor = None;
786 self.apply_operator(Operator::Change, start, end, text)
787 }
788 KeyCode::Char('y') => {
789 let (start, end) = self.selection_range();
790 self.mode = Mode::Normal;
791 self.visual_anchor = None;
792 self.apply_operator(Operator::Yank, start, end, text)
793 }
794
795 _ => EditResult::none(),
796 }
797 }
798
799 fn handle_replace_char(&mut self, key: Key, text: &str) -> EditResult {
801 self.mode = Mode::Normal;
802
803 match key.code {
804 KeyCode::Escape => EditResult::none(),
805 KeyCode::Char(c) if !key.ctrl && !key.alt => {
806 if self.cursor >= text.len() {
808 return EditResult::none();
809 }
810
811 let mut end = self.cursor + 1;
813 while end < text.len() && !text.is_char_boundary(end) {
814 end += 1;
815 }
816
817 EditResult {
820 edits: vec![
821 TextEdit::Insert {
822 at: self.cursor,
823 text: c.to_string(),
824 },
825 TextEdit::Delete {
826 start: self.cursor,
827 end,
828 },
829 ],
830 ..Default::default()
831 }
832 }
833 _ => EditResult::none(),
834 }
835 }
836
837 fn selection_range(&self) -> (usize, usize) {
839 let anchor = self.visual_anchor.unwrap_or(self.cursor);
840 if self.cursor < anchor {
841 (self.cursor, anchor)
842 } else {
843 (anchor, self.cursor + 1) }
845 }
846}
847
848impl LineEditor for VimLineEditor {
849 fn handle_key(&mut self, key: Key, text: &str) -> EditResult {
850 self.clamp_cursor(text);
851
852 let result = match self.mode {
853 Mode::Normal => self.handle_normal(key, text),
854 Mode::Insert => self.handle_insert(key, text),
855 Mode::OperatorPending(op) => self.handle_operator_pending(op, key, text),
856 Mode::Visual => self.handle_visual(key, text),
857 Mode::ReplaceChar => self.handle_replace_char(key, text),
858 };
859
860 if let Some(ref yanked) = result.yanked {
862 self.yank_buffer = yanked.clone();
863 }
864
865 result
866 }
867
868 fn cursor(&self) -> usize {
869 self.cursor
870 }
871
872 fn status(&self) -> &str {
873 match self.mode {
874 Mode::Normal => "NORMAL",
875 Mode::Insert => "INSERT",
876 Mode::OperatorPending(Operator::Delete) => "d...",
877 Mode::OperatorPending(Operator::Change) => "c...",
878 Mode::OperatorPending(Operator::Yank) => "y...",
879 Mode::Visual => "VISUAL",
880 Mode::ReplaceChar => "r...",
881 }
882 }
883
884 fn selection(&self) -> Option<Range<usize>> {
885 if self.mode == Mode::Visual {
886 let (start, end) = self.selection_range();
887 Some(start..end)
888 } else {
889 None
890 }
891 }
892
893 fn reset(&mut self) {
894 self.cursor = 0;
895 self.mode = Mode::Normal;
896 self.visual_anchor = None;
897 }
899
900 fn set_cursor(&mut self, pos: usize, text: &str) {
901 let pos = pos.min(text.len());
903 self.cursor = if text.is_char_boundary(pos) {
904 pos
905 } else {
906 let mut p = pos;
908 while p > 0 && !text.is_char_boundary(p) {
909 p -= 1;
910 }
911 p
912 };
913 }
914}
915
916#[cfg(test)]
917mod tests {
918 use super::*;
919
920 #[test]
921 fn test_basic_motion() {
922 let mut editor = VimLineEditor::new();
923 let text = "hello world";
924
925 editor.handle_key(Key::char('l'), text);
927 assert_eq!(editor.cursor(), 1);
928
929 editor.handle_key(Key::char('w'), text);
931 assert_eq!(editor.cursor(), 6); editor.handle_key(Key::char('$'), text);
935 assert_eq!(editor.cursor(), 10); editor.handle_key(Key::char('0'), text);
939 assert_eq!(editor.cursor(), 0);
940 }
941
942 #[test]
943 fn test_mode_switching() {
944 let mut editor = VimLineEditor::new();
945 let text = "hello";
946
947 assert_eq!(editor.mode(), Mode::Normal);
948
949 editor.handle_key(Key::char('i'), text);
950 assert_eq!(editor.mode(), Mode::Insert);
951
952 editor.handle_key(Key::code(KeyCode::Escape), text);
953 assert_eq!(editor.mode(), Mode::Normal);
954 }
955
956 #[test]
957 fn test_delete_word() {
958 let mut editor = VimLineEditor::new();
959 let text = "hello world";
960
961 editor.handle_key(Key::char('d'), text);
963 editor.handle_key(Key::char('w'), text);
964
965 assert_eq!(editor.mode(), Mode::Normal);
967 }
968
969 #[test]
970 fn test_insert_char() {
971 let mut editor = VimLineEditor::new();
972 let text = "";
973
974 editor.handle_key(Key::char('i'), text);
975 let result = editor.handle_key(Key::char('x'), text);
976
977 assert_eq!(result.edits.len(), 1);
978 match &result.edits[0] {
979 TextEdit::Insert { at, text } => {
980 assert_eq!(*at, 0);
981 assert_eq!(text, "x");
982 }
983 _ => panic!("Expected Insert"),
984 }
985 }
986
987 #[test]
988 fn test_visual_mode() {
989 let mut editor = VimLineEditor::new();
990 let text = "hello world";
991
992 editor.handle_key(Key::char('v'), text);
994 assert_eq!(editor.mode(), Mode::Visual);
995
996 editor.handle_key(Key::char('w'), text);
998
999 let sel = editor.selection().unwrap();
1001 assert_eq!(sel.start, 0);
1002 assert!(sel.end > 0);
1003 }
1004
1005 #[test]
1006 fn test_backspace_ascii() {
1007 let mut editor = VimLineEditor::new();
1008 let mut text = String::from("abc");
1009
1010 editor.handle_key(Key::char('i'), &text);
1012 editor.handle_key(Key::code(KeyCode::End), &text);
1013 assert_eq!(editor.cursor(), 3);
1014
1015 let result = editor.handle_key(Key::code(KeyCode::Backspace), &text);
1017 for edit in result.edits.into_iter().rev() {
1018 edit.apply(&mut text);
1019 }
1020 assert_eq!(text, "ab");
1021 assert_eq!(editor.cursor(), 2);
1022 }
1023
1024 #[test]
1025 fn test_backspace_unicode() {
1026 let mut editor = VimLineEditor::new();
1027 let mut text = String::from("a😀b");
1028
1029 editor.handle_key(Key::char('i'), &text);
1031 editor.handle_key(Key::code(KeyCode::End), &text);
1032 editor.handle_key(Key::code(KeyCode::Left), &text); assert_eq!(editor.cursor(), 5); let result = editor.handle_key(Key::code(KeyCode::Backspace), &text);
1037 for edit in result.edits.into_iter().rev() {
1038 edit.apply(&mut text);
1039 }
1040 assert_eq!(text, "ab");
1041 assert_eq!(editor.cursor(), 1);
1042 }
1043
1044 #[test]
1045 fn test_yank_and_paste() {
1046 let mut editor = VimLineEditor::new();
1047 let mut text = String::from("hello world");
1048
1049 editor.handle_key(Key::char('y'), &text);
1051 let result = editor.handle_key(Key::char('w'), &text);
1052 assert!(result.yanked.is_some());
1053 assert_eq!(result.yanked.unwrap(), "hello ");
1054
1055 editor.handle_key(Key::char('$'), &text);
1057 let result = editor.handle_key(Key::char('p'), &text);
1058
1059 for edit in result.edits.into_iter().rev() {
1060 edit.apply(&mut text);
1061 }
1062 assert_eq!(text, "hello worldhello ");
1063 }
1064
1065 #[test]
1066 fn test_visual_mode_delete() {
1067 let mut editor = VimLineEditor::new();
1068 let mut text = String::from("hello world");
1069
1070 editor.handle_key(Key::char('v'), &text);
1072 assert_eq!(editor.mode(), Mode::Visual);
1073
1074 editor.handle_key(Key::char('e'), &text);
1076
1077 let result = editor.handle_key(Key::char('d'), &text);
1079
1080 for edit in result.edits.into_iter().rev() {
1081 edit.apply(&mut text);
1082 }
1083 assert_eq!(text, " world");
1084 assert_eq!(editor.mode(), Mode::Normal);
1085 }
1086
1087 #[test]
1088 fn test_operator_pending_escape() {
1089 let mut editor = VimLineEditor::new();
1090 let text = "hello world";
1091
1092 editor.handle_key(Key::char('d'), text);
1094 assert!(matches!(editor.mode(), Mode::OperatorPending(_)));
1095
1096 editor.handle_key(Key::code(KeyCode::Escape), text);
1098 assert_eq!(editor.mode(), Mode::Normal);
1099 }
1100
1101 #[test]
1102 fn test_replace_char() {
1103 let mut editor = VimLineEditor::new();
1104 let mut text = String::from("hello");
1105
1106 editor.handle_key(Key::char('r'), &text);
1108 assert_eq!(editor.mode(), Mode::ReplaceChar);
1109
1110 let result = editor.handle_key(Key::char('x'), &text);
1111 assert_eq!(editor.mode(), Mode::Normal);
1112
1113 for edit in result.edits.into_iter().rev() {
1115 edit.apply(&mut text);
1116 }
1117 assert_eq!(text, "xello");
1118 }
1119
1120 #[test]
1121 fn test_replace_char_escape() {
1122 let mut editor = VimLineEditor::new();
1123 let text = "hello";
1124
1125 editor.handle_key(Key::char('r'), text);
1127 assert_eq!(editor.mode(), Mode::ReplaceChar);
1128
1129 editor.handle_key(Key::code(KeyCode::Escape), text);
1130 assert_eq!(editor.mode(), Mode::Normal);
1131 }
1132
1133 #[test]
1134 fn test_cw_no_trailing_space() {
1135 let mut editor = VimLineEditor::new();
1136 let mut text = String::from("hello world");
1137
1138 editor.handle_key(Key::char('c'), &text);
1140 let result = editor.handle_key(Key::char('w'), &text);
1141
1142 assert_eq!(editor.mode(), Mode::Insert);
1143
1144 for edit in result.edits.into_iter().rev() {
1146 edit.apply(&mut text);
1147 }
1148 assert_eq!(text, " world");
1150 }
1151
1152 #[test]
1153 fn test_dw_includes_trailing_space() {
1154 let mut editor = VimLineEditor::new();
1155 let mut text = String::from("hello world");
1156
1157 editor.handle_key(Key::char('d'), &text);
1159 let result = editor.handle_key(Key::char('w'), &text);
1160
1161 assert_eq!(editor.mode(), Mode::Normal);
1162
1163 for edit in result.edits.into_iter().rev() {
1165 edit.apply(&mut text);
1166 }
1167 assert_eq!(text, "world");
1168 }
1169
1170 #[test]
1171 fn test_paste_at_empty_buffer() {
1172 let mut editor = VimLineEditor::new();
1173
1174 let yank_text = String::from("test");
1176 editor.handle_key(Key::char('y'), &yank_text);
1177 editor.handle_key(Key::char('w'), &yank_text);
1178
1179 let mut text = String::new();
1181 editor.set_cursor(0, &text);
1182 let result = editor.handle_key(Key::char('p'), &text);
1183
1184 for edit in result.edits.into_iter().rev() {
1185 edit.apply(&mut text);
1186 }
1187 assert_eq!(text, "test");
1188 }
1189
1190 #[test]
1191 fn test_dollar_cursor_on_last_char() {
1192 let mut editor = VimLineEditor::new();
1193 let text = "abc";
1194
1195 editor.handle_key(Key::char('$'), text);
1197 assert_eq!(editor.cursor(), 2);
1198
1199 let text = "x";
1201 editor.set_cursor(0, text);
1202 editor.handle_key(Key::char('$'), text);
1203 assert_eq!(editor.cursor(), 0); }
1205
1206 #[test]
1207 fn test_x_delete_last_char_moves_cursor_left() {
1208 let mut editor = VimLineEditor::new();
1209 let mut text = String::from("abc");
1210
1211 editor.handle_key(Key::char('$'), &text);
1213 assert_eq!(editor.cursor(), 2); let result = editor.handle_key(Key::char('x'), &text);
1217 for edit in result.edits.into_iter().rev() {
1218 edit.apply(&mut text);
1219 }
1220
1221 assert_eq!(text, "ab");
1222 assert_eq!(editor.cursor(), 1); }
1225
1226 #[test]
1227 fn test_x_delete_middle_char_cursor_stays() {
1228 let mut editor = VimLineEditor::new();
1229 let mut text = String::from("abc");
1230
1231 editor.handle_key(Key::char('l'), &text);
1233 assert_eq!(editor.cursor(), 1);
1234
1235 let result = editor.handle_key(Key::char('x'), &text);
1237 for edit in result.edits.into_iter().rev() {
1238 edit.apply(&mut text);
1239 }
1240
1241 assert_eq!(text, "ac");
1242 assert_eq!(editor.cursor(), 1);
1244 }
1245}