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}
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum Operator {
19 Delete,
20 Change,
21 Yank,
22}
23
24#[derive(Debug, Clone)]
29pub struct VimLineEditor {
30 cursor: usize,
31 mode: Mode,
32 visual_anchor: Option<usize>,
34 yank_buffer: String,
36}
37
38impl Default for VimLineEditor {
39 fn default() -> Self {
40 Self::new()
41 }
42}
43
44impl VimLineEditor {
45 pub fn new() -> Self {
47 Self {
48 cursor: 0,
49 mode: Mode::Normal,
50 visual_anchor: None,
51 yank_buffer: String::new(),
52 }
53 }
54
55 pub fn mode(&self) -> Mode {
57 self.mode
58 }
59
60 fn clamp_cursor(&mut self, text: &str) {
62 self.cursor = self.cursor.min(text.len());
63 }
64
65 fn move_left(&mut self, text: &str) {
67 if self.cursor > 0 {
68 let mut new_pos = self.cursor - 1;
70 while new_pos > 0 && !text.is_char_boundary(new_pos) {
71 new_pos -= 1;
72 }
73 self.cursor = new_pos;
74 }
75 }
76
77 fn move_right(&mut self, text: &str) {
79 if self.cursor < text.len() {
80 let mut new_pos = self.cursor + 1;
82 while new_pos < text.len() && !text.is_char_boundary(new_pos) {
83 new_pos += 1;
84 }
85 self.cursor = new_pos;
86 }
87 }
88
89 fn move_line_start(&mut self, text: &str) {
91 self.cursor = text[..self.cursor].rfind('\n').map(|i| i + 1).unwrap_or(0);
93 }
94
95 fn move_first_non_blank(&mut self, text: &str) {
97 self.move_line_start(text);
98 let line_start = self.cursor;
100 for (i, c) in text[line_start..].char_indices() {
101 if c == '\n' || !c.is_whitespace() {
102 self.cursor = line_start + i;
103 return;
104 }
105 }
106 }
107
108 fn move_line_end(&mut self, text: &str) {
110 self.cursor = text[self.cursor..]
112 .find('\n')
113 .map(|i| self.cursor + i)
114 .unwrap_or(text.len());
115 }
116
117 fn move_word_forward(&mut self, text: &str) {
119 let bytes = text.as_bytes();
120 let mut pos = self.cursor;
121
122 while pos < bytes.len() && !bytes[pos].is_ascii_whitespace() {
124 pos += 1;
125 }
126 while pos < bytes.len() && bytes[pos].is_ascii_whitespace() {
128 pos += 1;
129 }
130
131 self.cursor = pos;
132 }
133
134 fn move_word_backward(&mut self, text: &str) {
136 let bytes = text.as_bytes();
137 let mut pos = self.cursor;
138
139 while pos > 0 && bytes[pos - 1].is_ascii_whitespace() {
141 pos -= 1;
142 }
143 while pos > 0 && !bytes[pos - 1].is_ascii_whitespace() {
145 pos -= 1;
146 }
147
148 self.cursor = pos;
149 }
150
151 fn move_word_end(&mut self, text: &str) {
153 let bytes = text.as_bytes();
154 let mut pos = self.cursor;
155
156 if pos < bytes.len() {
158 pos += 1;
159 }
160 while pos < bytes.len() && bytes[pos].is_ascii_whitespace() {
162 pos += 1;
163 }
164 while pos < bytes.len() && !bytes[pos].is_ascii_whitespace() {
166 pos += 1;
167 }
168 if pos > self.cursor + 1 {
170 pos -= 1;
171 }
172
173 self.cursor = pos;
174 }
175
176 fn move_up(&mut self, text: &str) {
178 let line_start = text[..self.cursor].rfind('\n').map(|i| i + 1).unwrap_or(0);
180
181 if line_start == 0 {
182 return;
184 }
185
186 let col = self.cursor - line_start;
188
189 let prev_line_start = text[..line_start - 1]
191 .rfind('\n')
192 .map(|i| i + 1)
193 .unwrap_or(0);
194
195 let prev_line_end = line_start - 1; let prev_line_len = prev_line_end - prev_line_start;
198
199 self.cursor = prev_line_start + col.min(prev_line_len);
201 }
202
203 fn move_down(&mut self, text: &str) {
205 let line_start = text[..self.cursor].rfind('\n').map(|i| i + 1).unwrap_or(0);
207
208 let col = self.cursor - line_start;
210
211 let Some(newline_pos) = text[self.cursor..].find('\n') else {
213 return;
215 };
216 let next_line_start = self.cursor + newline_pos + 1;
217
218 if next_line_start >= text.len() {
219 self.cursor = text.len();
221 return;
222 }
223
224 let next_line_end = text[next_line_start..]
226 .find('\n')
227 .map(|i| next_line_start + i)
228 .unwrap_or(text.len());
229
230 let next_line_len = next_line_end - next_line_start;
231
232 self.cursor = next_line_start + col.min(next_line_len);
234 }
235
236 fn delete_char(&mut self, text: &str) -> EditResult {
238 if self.cursor >= text.len() {
239 return EditResult::none();
240 }
241
242 let mut end = self.cursor + 1;
244 while end < text.len() && !text.is_char_boundary(end) {
245 end += 1;
246 }
247
248 let deleted = text[self.cursor..end].to_string();
249 EditResult::edit_and_yank(
250 TextEdit::Delete {
251 start: self.cursor,
252 end,
253 },
254 deleted,
255 )
256 }
257
258 fn delete_to_end(&mut self, text: &str) -> EditResult {
260 let end = text[self.cursor..]
261 .find('\n')
262 .map(|i| self.cursor + i)
263 .unwrap_or(text.len());
264
265 if self.cursor >= end {
266 return EditResult::none();
267 }
268
269 let deleted = text[self.cursor..end].to_string();
270 EditResult::edit_and_yank(
271 TextEdit::Delete {
272 start: self.cursor,
273 end,
274 },
275 deleted,
276 )
277 }
278
279 fn delete_line(&mut self, text: &str) -> EditResult {
281 let line_start = text[..self.cursor].rfind('\n').map(|i| i + 1).unwrap_or(0);
282
283 let line_end = text[self.cursor..]
284 .find('\n')
285 .map(|i| self.cursor + i + 1) .unwrap_or(text.len());
287
288 let (start, end) = if line_start == 0 && line_end == text.len() {
290 (0, text.len())
291 } else if line_end == text.len() && line_start > 0 {
292 (line_start - 1, text.len())
294 } else {
295 (line_start, line_end)
296 };
297
298 let deleted = text[start..end].to_string();
299 self.cursor = start;
300
301 EditResult::edit_and_yank(TextEdit::Delete { start, end }, deleted)
302 }
303
304 fn paste_after(&mut self, text: &str) -> EditResult {
306 if self.yank_buffer.is_empty() {
307 return EditResult::none();
308 }
309
310 let insert_pos = (self.cursor + 1).min(text.len());
311 let to_insert = self.yank_buffer.clone();
312 self.cursor = (insert_pos + to_insert.len())
314 .saturating_sub(1)
315 .min(text.len() + to_insert.len());
316
317 EditResult::edit(TextEdit::Insert {
318 at: insert_pos,
319 text: to_insert,
320 })
321 }
322
323 fn paste_before(&mut self, text: &str) -> EditResult {
325 if self.yank_buffer.is_empty() {
326 return EditResult::none();
327 }
328
329 let to_insert = self.yank_buffer.clone();
330 let insert_pos = self.cursor.min(text.len());
331 self.cursor = (insert_pos + to_insert.len()).min(text.len() + to_insert.len());
333
334 EditResult::edit(TextEdit::Insert {
335 at: insert_pos,
336 text: to_insert,
337 })
338 }
339
340 fn handle_normal(&mut self, key: Key, text: &str) -> EditResult {
342 match key.code {
343 KeyCode::Char('i') => {
345 self.mode = Mode::Insert;
346 EditResult::none()
347 }
348 KeyCode::Char('a') => {
349 self.mode = Mode::Insert;
350 self.move_right(text);
351 EditResult::none()
352 }
353 KeyCode::Char('A') => {
354 self.mode = Mode::Insert;
355 self.move_line_end(text);
356 EditResult::none()
357 }
358 KeyCode::Char('I') => {
359 self.mode = Mode::Insert;
360 self.move_first_non_blank(text);
361 EditResult::none()
362 }
363 KeyCode::Char('o') => {
364 self.mode = Mode::Insert;
365 self.move_line_end(text);
366 let pos = self.cursor;
367 self.cursor = pos + 1;
368 EditResult::edit(TextEdit::Insert {
369 at: pos,
370 text: "\n".to_string(),
371 })
372 }
373 KeyCode::Char('O') => {
374 self.mode = Mode::Insert;
375 self.move_line_start(text);
376 let pos = self.cursor;
377 EditResult::edit(TextEdit::Insert {
378 at: pos,
379 text: "\n".to_string(),
380 })
381 }
382
383 KeyCode::Char('v') => {
385 self.mode = Mode::Visual;
386 self.visual_anchor = Some(self.cursor);
387 EditResult::none()
388 }
389
390 KeyCode::Char('h') | KeyCode::Left => {
392 self.move_left(text);
393 EditResult::cursor_only()
394 }
395 KeyCode::Char('l') | KeyCode::Right => {
396 self.move_right(text);
397 EditResult::cursor_only()
398 }
399 KeyCode::Char('j') => {
400 self.move_down(text);
401 EditResult::cursor_only()
402 }
403 KeyCode::Char('k') => {
404 self.move_up(text);
405 EditResult::cursor_only()
406 }
407 KeyCode::Char('0') | KeyCode::Home => {
408 self.move_line_start(text);
409 EditResult::cursor_only()
410 }
411 KeyCode::Char('^') => {
412 self.move_first_non_blank(text);
413 EditResult::cursor_only()
414 }
415 KeyCode::Char('$') | KeyCode::End => {
416 self.move_line_end(text);
417 EditResult::cursor_only()
418 }
419 KeyCode::Char('w') => {
420 self.move_word_forward(text);
421 EditResult::cursor_only()
422 }
423 KeyCode::Char('b') => {
424 self.move_word_backward(text);
425 EditResult::cursor_only()
426 }
427 KeyCode::Char('e') => {
428 self.move_word_end(text);
429 EditResult::cursor_only()
430 }
431
432 KeyCode::Char('c') if key.ctrl => EditResult::action(Action::Cancel),
434
435 KeyCode::Char('d') => {
437 self.mode = Mode::OperatorPending(Operator::Delete);
438 EditResult::none()
439 }
440 KeyCode::Char('c') => {
441 self.mode = Mode::OperatorPending(Operator::Change);
442 EditResult::none()
443 }
444 KeyCode::Char('y') => {
445 self.mode = Mode::OperatorPending(Operator::Yank);
446 EditResult::none()
447 }
448
449 KeyCode::Char('x') => self.delete_char(text),
451 KeyCode::Char('D') => self.delete_to_end(text),
452 KeyCode::Char('C') => {
453 self.mode = Mode::Insert;
454 self.delete_to_end(text)
455 }
456
457 KeyCode::Char('p') => self.paste_after(text),
459 KeyCode::Char('P') => self.paste_before(text),
460
461 KeyCode::Up => EditResult::action(Action::HistoryPrev),
463 KeyCode::Down => EditResult::action(Action::HistoryNext),
464
465 KeyCode::Enter if !key.shift => EditResult::action(Action::Submit),
467
468 KeyCode::Enter if key.shift => {
470 self.mode = Mode::Insert;
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
479 KeyCode::Escape => EditResult::none(),
482
483 _ => EditResult::none(),
484 }
485 }
486
487 fn handle_insert(&mut self, key: Key, text: &str) -> EditResult {
489 match key.code {
490 KeyCode::Escape => {
491 self.mode = Mode::Normal;
492 if self.cursor > 0 {
494 self.move_left(text);
495 }
496 EditResult::none()
497 }
498
499 KeyCode::Char('c') if key.ctrl => {
501 self.mode = Mode::Normal;
502 EditResult::none()
503 }
504
505 KeyCode::Char(c) if !key.ctrl && !key.alt => {
506 let pos = self.cursor;
507 self.cursor = pos + c.len_utf8();
508 EditResult::edit(TextEdit::Insert {
509 at: pos,
510 text: c.to_string(),
511 })
512 }
513
514 KeyCode::Backspace => {
515 if self.cursor == 0 {
516 return EditResult::none();
517 }
518 let mut start = self.cursor - 1;
519 while start > 0 && !text.is_char_boundary(start) {
520 start -= 1;
521 }
522 let end = self.cursor; self.cursor = start;
524 EditResult::edit(TextEdit::Delete { start, end })
525 }
526
527 KeyCode::Delete => self.delete_char(text),
528
529 KeyCode::Left => {
530 self.move_left(text);
531 EditResult::cursor_only()
532 }
533 KeyCode::Right => {
534 self.move_right(text);
535 EditResult::cursor_only()
536 }
537 KeyCode::Up => {
538 self.move_up(text);
539 EditResult::cursor_only()
540 }
541 KeyCode::Down => {
542 self.move_down(text);
543 EditResult::cursor_only()
544 }
545 KeyCode::Home => {
546 self.move_line_start(text);
547 EditResult::cursor_only()
548 }
549 KeyCode::End => {
550 self.move_line_end(text);
551 EditResult::cursor_only()
552 }
553
554 KeyCode::Enter => {
556 let pos = self.cursor;
557 self.cursor = pos + 1;
558 EditResult::edit(TextEdit::Insert {
559 at: pos,
560 text: "\n".to_string(),
561 })
562 }
563
564 _ => EditResult::none(),
565 }
566 }
567
568 fn handle_operator_pending(&mut self, op: Operator, key: Key, text: &str) -> EditResult {
570 if key.code == KeyCode::Escape {
572 self.mode = Mode::Normal;
573 return EditResult::none();
574 }
575
576 let is_line_op = matches!(
578 (op, key.code),
579 (Operator::Delete, KeyCode::Char('d'))
580 | (Operator::Change, KeyCode::Char('c'))
581 | (Operator::Yank, KeyCode::Char('y'))
582 );
583
584 if is_line_op {
585 self.mode = Mode::Normal;
586 return self.apply_operator_line(op, text);
587 }
588
589 let start = self.cursor;
591 match key.code {
592 KeyCode::Char('w') => self.move_word_forward(text),
593 KeyCode::Char('b') => self.move_word_backward(text),
594 KeyCode::Char('e') => {
595 self.move_word_end(text);
596 if self.cursor < text.len() {
598 self.cursor += 1;
599 }
600 }
601 KeyCode::Char('0') | KeyCode::Home => self.move_line_start(text),
602 KeyCode::Char('$') | KeyCode::End => self.move_line_end(text),
603 KeyCode::Char('^') => self.move_first_non_blank(text),
604 KeyCode::Char('h') | KeyCode::Left => self.move_left(text),
605 KeyCode::Char('l') | KeyCode::Right => self.move_right(text),
606 KeyCode::Char('j') => self.move_down(text),
607 KeyCode::Char('k') => self.move_up(text),
608 _ => {
609 self.mode = Mode::Normal;
611 return EditResult::none();
612 }
613 }
614
615 let end = self.cursor;
616 self.mode = Mode::Normal;
617
618 if start == end {
619 return EditResult::none();
620 }
621
622 let (range_start, range_end) = if start < end {
623 (start, end)
624 } else {
625 (end, start)
626 };
627
628 self.apply_operator(op, range_start, range_end, text)
629 }
630
631 fn apply_operator(&mut self, op: Operator, start: usize, end: usize, text: &str) -> EditResult {
633 let affected = text[start..end].to_string();
634 self.yank_buffer = affected.clone();
635 self.cursor = start;
636
637 match op {
638 Operator::Delete => {
639 EditResult::edit_and_yank(TextEdit::Delete { start, end }, affected)
640 }
641 Operator::Change => {
642 self.mode = Mode::Insert;
643 EditResult::edit_and_yank(TextEdit::Delete { start, end }, affected)
644 }
645 Operator::Yank => {
646 EditResult {
648 yanked: Some(affected),
649 ..Default::default()
650 }
651 }
652 }
653 }
654
655 fn apply_operator_line(&mut self, op: Operator, text: &str) -> EditResult {
657 match op {
658 Operator::Delete => self.delete_line(text),
659 Operator::Change => {
660 let result = self.delete_line(text);
661 self.mode = Mode::Insert;
662 result
663 }
664 Operator::Yank => {
665 let line_start = text[..self.cursor].rfind('\n').map(|i| i + 1).unwrap_or(0);
666 let line_end = text[self.cursor..]
667 .find('\n')
668 .map(|i| self.cursor + i + 1)
669 .unwrap_or(text.len());
670 let line = text[line_start..line_end].to_string();
671 self.yank_buffer = line.clone();
672 EditResult {
673 yanked: Some(line),
674 ..Default::default()
675 }
676 }
677 }
678 }
679
680 fn handle_visual(&mut self, key: Key, text: &str) -> EditResult {
682 match key.code {
683 KeyCode::Escape => {
684 self.mode = Mode::Normal;
685 self.visual_anchor = None;
686 EditResult::none()
687 }
688
689 KeyCode::Char('h') | KeyCode::Left => {
691 self.move_left(text);
692 EditResult::cursor_only()
693 }
694 KeyCode::Char('l') | KeyCode::Right => {
695 self.move_right(text);
696 EditResult::cursor_only()
697 }
698 KeyCode::Char('j') => {
699 self.move_down(text);
700 EditResult::cursor_only()
701 }
702 KeyCode::Char('k') => {
703 self.move_up(text);
704 EditResult::cursor_only()
705 }
706 KeyCode::Char('w') => {
707 self.move_word_forward(text);
708 EditResult::cursor_only()
709 }
710 KeyCode::Char('b') => {
711 self.move_word_backward(text);
712 EditResult::cursor_only()
713 }
714 KeyCode::Char('e') => {
715 self.move_word_end(text);
716 EditResult::cursor_only()
717 }
718 KeyCode::Char('0') | KeyCode::Home => {
719 self.move_line_start(text);
720 EditResult::cursor_only()
721 }
722 KeyCode::Char('$') | KeyCode::End => {
723 self.move_line_end(text);
724 EditResult::cursor_only()
725 }
726
727 KeyCode::Char('d') | KeyCode::Char('x') => {
729 let (start, end) = self.selection_range();
730 self.mode = Mode::Normal;
731 self.visual_anchor = None;
732 self.apply_operator(Operator::Delete, start, end, text)
733 }
734 KeyCode::Char('c') => {
735 let (start, end) = self.selection_range();
736 self.mode = Mode::Normal;
737 self.visual_anchor = None;
738 self.apply_operator(Operator::Change, start, end, text)
739 }
740 KeyCode::Char('y') => {
741 let (start, end) = self.selection_range();
742 self.mode = Mode::Normal;
743 self.visual_anchor = None;
744 self.apply_operator(Operator::Yank, start, end, text)
745 }
746
747 _ => EditResult::none(),
748 }
749 }
750
751 fn selection_range(&self) -> (usize, usize) {
753 let anchor = self.visual_anchor.unwrap_or(self.cursor);
754 if self.cursor < anchor {
755 (self.cursor, anchor)
756 } else {
757 (anchor, self.cursor + 1) }
759 }
760}
761
762impl LineEditor for VimLineEditor {
763 fn handle_key(&mut self, key: Key, text: &str) -> EditResult {
764 self.clamp_cursor(text);
765
766 let result = match self.mode {
767 Mode::Normal => self.handle_normal(key, text),
768 Mode::Insert => self.handle_insert(key, text),
769 Mode::OperatorPending(op) => self.handle_operator_pending(op, key, text),
770 Mode::Visual => self.handle_visual(key, text),
771 };
772
773 if let Some(ref yanked) = result.yanked {
775 self.yank_buffer = yanked.clone();
776 }
777
778 result
779 }
780
781 fn cursor(&self) -> usize {
782 self.cursor
783 }
784
785 fn status(&self) -> &str {
786 match self.mode {
787 Mode::Normal => "NORMAL",
788 Mode::Insert => "INSERT",
789 Mode::OperatorPending(Operator::Delete) => "d...",
790 Mode::OperatorPending(Operator::Change) => "c...",
791 Mode::OperatorPending(Operator::Yank) => "y...",
792 Mode::Visual => "VISUAL",
793 }
794 }
795
796 fn selection(&self) -> Option<Range<usize>> {
797 if self.mode == Mode::Visual {
798 let (start, end) = self.selection_range();
799 Some(start..end)
800 } else {
801 None
802 }
803 }
804
805 fn reset(&mut self) {
806 self.cursor = 0;
807 self.mode = Mode::Normal;
808 self.visual_anchor = None;
809 }
811
812 fn set_cursor(&mut self, pos: usize, text: &str) {
813 let pos = pos.min(text.len());
815 self.cursor = if text.is_char_boundary(pos) {
816 pos
817 } else {
818 let mut p = pos;
820 while p > 0 && !text.is_char_boundary(p) {
821 p -= 1;
822 }
823 p
824 };
825 }
826}
827
828#[cfg(test)]
829mod tests {
830 use super::*;
831
832 #[test]
833 fn test_basic_motion() {
834 let mut editor = VimLineEditor::new();
835 let text = "hello world";
836
837 editor.handle_key(Key::char('l'), text);
839 assert_eq!(editor.cursor(), 1);
840
841 editor.handle_key(Key::char('w'), text);
843 assert_eq!(editor.cursor(), 6); editor.handle_key(Key::char('$'), text);
847 assert_eq!(editor.cursor(), 11);
848
849 editor.handle_key(Key::char('0'), text);
851 assert_eq!(editor.cursor(), 0);
852 }
853
854 #[test]
855 fn test_mode_switching() {
856 let mut editor = VimLineEditor::new();
857 let text = "hello";
858
859 assert_eq!(editor.mode(), Mode::Normal);
860
861 editor.handle_key(Key::char('i'), text);
862 assert_eq!(editor.mode(), Mode::Insert);
863
864 editor.handle_key(Key::code(KeyCode::Escape), text);
865 assert_eq!(editor.mode(), Mode::Normal);
866 }
867
868 #[test]
869 fn test_delete_word() {
870 let mut editor = VimLineEditor::new();
871 let text = "hello world";
872
873 editor.handle_key(Key::char('d'), text);
875 editor.handle_key(Key::char('w'), text);
876
877 assert_eq!(editor.mode(), Mode::Normal);
879 }
880
881 #[test]
882 fn test_insert_char() {
883 let mut editor = VimLineEditor::new();
884 let text = "";
885
886 editor.handle_key(Key::char('i'), text);
887 let result = editor.handle_key(Key::char('x'), text);
888
889 assert_eq!(result.edits.len(), 1);
890 match &result.edits[0] {
891 TextEdit::Insert { at, text } => {
892 assert_eq!(*at, 0);
893 assert_eq!(text, "x");
894 }
895 _ => panic!("Expected Insert"),
896 }
897 }
898
899 #[test]
900 fn test_visual_mode() {
901 let mut editor = VimLineEditor::new();
902 let text = "hello world";
903
904 editor.handle_key(Key::char('v'), text);
906 assert_eq!(editor.mode(), Mode::Visual);
907
908 editor.handle_key(Key::char('w'), text);
910
911 let sel = editor.selection().unwrap();
913 assert_eq!(sel.start, 0);
914 assert!(sel.end > 0);
915 }
916
917 #[test]
918 fn test_backspace_ascii() {
919 let mut editor = VimLineEditor::new();
920 let mut text = String::from("abc");
921
922 editor.handle_key(Key::char('i'), &text);
924 editor.handle_key(Key::code(KeyCode::End), &text);
925 assert_eq!(editor.cursor(), 3);
926
927 let result = editor.handle_key(Key::code(KeyCode::Backspace), &text);
929 for edit in result.edits.into_iter().rev() {
930 edit.apply(&mut text);
931 }
932 assert_eq!(text, "ab");
933 assert_eq!(editor.cursor(), 2);
934 }
935
936 #[test]
937 fn test_backspace_unicode() {
938 let mut editor = VimLineEditor::new();
939 let mut text = String::from("a😀b");
940
941 editor.handle_key(Key::char('i'), &text);
943 editor.handle_key(Key::code(KeyCode::End), &text);
944 editor.handle_key(Key::code(KeyCode::Left), &text); assert_eq!(editor.cursor(), 5); let result = editor.handle_key(Key::code(KeyCode::Backspace), &text);
949 for edit in result.edits.into_iter().rev() {
950 edit.apply(&mut text);
951 }
952 assert_eq!(text, "ab");
953 assert_eq!(editor.cursor(), 1);
954 }
955
956 #[test]
957 fn test_yank_and_paste() {
958 let mut editor = VimLineEditor::new();
959 let mut text = String::from("hello world");
960
961 editor.handle_key(Key::char('y'), &text);
963 let result = editor.handle_key(Key::char('w'), &text);
964 assert!(result.yanked.is_some());
965 assert_eq!(result.yanked.unwrap(), "hello ");
966
967 editor.handle_key(Key::char('$'), &text);
969 let result = editor.handle_key(Key::char('p'), &text);
970
971 for edit in result.edits.into_iter().rev() {
972 edit.apply(&mut text);
973 }
974 assert_eq!(text, "hello worldhello ");
975 }
976
977 #[test]
978 fn test_visual_mode_delete() {
979 let mut editor = VimLineEditor::new();
980 let mut text = String::from("hello world");
981
982 editor.handle_key(Key::char('v'), &text);
984 assert_eq!(editor.mode(), Mode::Visual);
985
986 editor.handle_key(Key::char('e'), &text);
988
989 let result = editor.handle_key(Key::char('d'), &text);
991
992 for edit in result.edits.into_iter().rev() {
993 edit.apply(&mut text);
994 }
995 assert_eq!(text, " world");
996 assert_eq!(editor.mode(), Mode::Normal);
997 }
998
999 #[test]
1000 fn test_operator_pending_escape() {
1001 let mut editor = VimLineEditor::new();
1002 let text = "hello world";
1003
1004 editor.handle_key(Key::char('d'), text);
1006 assert!(matches!(editor.mode(), Mode::OperatorPending(_)));
1007
1008 editor.handle_key(Key::code(KeyCode::Escape), text);
1010 assert_eq!(editor.mode(), Mode::Normal);
1011 }
1012
1013 #[test]
1014 fn test_paste_at_empty_buffer() {
1015 let mut editor = VimLineEditor::new();
1016
1017 let yank_text = String::from("test");
1019 editor.handle_key(Key::char('y'), &yank_text);
1020 editor.handle_key(Key::char('w'), &yank_text);
1021
1022 let mut text = String::new();
1024 editor.set_cursor(0, &text);
1025 let result = editor.handle_key(Key::char('p'), &text);
1026
1027 for edit in result.edits.into_iter().rev() {
1028 edit.apply(&mut text);
1029 }
1030 assert_eq!(text, "test");
1031 }
1032}