1use crate::key::{KeyCode, KeyEvent, KeyModifiers};
13
14#[derive(Clone, Copy, PartialEq, Debug)]
15pub enum Mode {
16 Insert,
17 Normal,
18}
19
20#[derive(PartialEq, Debug)]
22pub enum Act {
23 Continue,
24 Send,
26 Leave,
28}
29
30#[derive(Clone, Copy, PartialEq)]
32enum Await {
33 Find { back: bool, till: bool },
35 Replace,
37}
38
39fn class(c: char) -> u8 {
42 if c.is_whitespace() {
43 0
44 } else if c.is_alphanumeric() || c == '_' {
45 1
46 } else {
47 2
48 }
49}
50
51pub struct Line {
52 text: Vec<char>,
53 cursor: usize,
54 mode: Mode,
55 op: Option<char>,
57 count: usize,
58 awaiting: Option<Await>,
59 reg: Vec<char>,
61 last_find: Option<(char, bool, bool)>,
63 undo: Vec<(Vec<char>, usize)>,
64 history: Vec<String>,
65 hist: Option<usize>,
67 stash: Vec<char>,
69}
70
71impl Default for Line {
72 fn default() -> Self {
73 Line {
74 text: Vec::new(),
75 cursor: 0,
76 mode: Mode::Insert,
77 op: None,
78 count: 0,
79 awaiting: None,
80 reg: Vec::new(),
81 last_find: None,
82 undo: Vec::new(),
83 history: Vec::new(),
84 hist: None,
85 stash: Vec::new(),
86 }
87 }
88}
89
90impl Line {
91 pub fn text(&self) -> String {
92 self.text.iter().collect()
93 }
94
95 pub fn is_empty(&self) -> bool {
96 self.text.is_empty()
97 }
98
99 pub fn mode(&self) -> Mode {
100 self.mode
101 }
102
103 pub fn cursor(&self) -> usize {
105 self.cursor
106 }
107
108 pub fn pending(&self) -> String {
110 let mut s = String::new();
111 if self.count > 0 {
112 s.push_str(&self.count.to_string());
113 }
114 if let Some(op) = self.op {
115 s.push(op);
116 }
117 match self.awaiting {
118 Some(Await::Find { back, till }) => s.push(match (back, till) {
119 (false, false) => 'f',
120 (true, false) => 'F',
121 (false, true) => 't',
122 (true, true) => 'T',
123 }),
124 Some(Await::Replace) => s.push('r'),
125 None => {}
126 }
127 s
128 }
129
130 pub fn accept(&mut self) -> String {
133 let out: String = self.text.iter().collect();
134 if !out.trim().is_empty() && self.history.last().map(String::as_str) != Some(out.as_str()) {
135 self.history.push(out.clone());
136 }
137 self.reset();
138 out
139 }
140
141 pub fn reset(&mut self) {
143 self.text.clear();
144 self.cursor = 0;
145 self.mode = Mode::Insert;
146 self.op = None;
147 self.count = 0;
148 self.awaiting = None;
149 self.hist = None;
150 self.stash.clear();
151 self.undo.clear();
152 }
153
154 pub fn focus(&mut self) {
157 self.mode = Mode::Insert;
158 self.op = None;
159 self.count = 0;
160 self.awaiting = None;
161 }
162
163 fn snapshot(&mut self) {
164 self.undo.push((self.text.clone(), self.cursor));
165 if self.undo.len() > 100 {
166 self.undo.remove(0);
167 }
168 }
169
170 fn clamp(&mut self) {
173 let max = match self.mode {
174 Mode::Insert => self.text.len(),
175 Mode::Normal => self.text.len().saturating_sub(1),
176 };
177 self.cursor = self.cursor.min(max);
178 }
179
180 fn take_count(&mut self) -> usize {
181 let n = self.count.max(1);
182 self.count = 0;
183 n
184 }
185
186 pub fn key(&mut self, k: KeyEvent) -> Act {
187 match self.mode {
188 Mode::Insert => self.insert_key(k),
189 Mode::Normal => self.normal_key(k),
190 }
191 }
192
193 fn insert_key(&mut self, k: KeyEvent) -> Act {
194 match k.code {
195 KeyCode::Esc => {
196 self.mode = Mode::Normal;
197 self.cursor = self.cursor.saturating_sub(1);
199 self.clamp();
200 }
201 KeyCode::Enter => return Act::Send,
202 KeyCode::Backspace => {
203 if self.cursor > 0 {
204 self.snapshot();
205 self.cursor -= 1;
206 self.text.remove(self.cursor);
207 }
208 }
209 KeyCode::Delete => {
210 if self.cursor < self.text.len() {
211 self.snapshot();
212 self.text.remove(self.cursor);
213 }
214 }
215 KeyCode::Left => self.cursor = self.cursor.saturating_sub(1),
216 KeyCode::Right => self.cursor = (self.cursor + 1).min(self.text.len()),
217 KeyCode::Home => self.cursor = 0,
218 KeyCode::End => self.cursor = self.text.len(),
219 KeyCode::Char(c) if !k.modifiers.contains(KeyModifiers::CONTROL) => {
220 self.snapshot();
221 self.text.insert(self.cursor, c);
222 self.cursor += 1;
223 }
224 _ => {}
225 }
226 Act::Continue
227 }
228
229 fn normal_key(&mut self, k: KeyEvent) -> Act {
230 let KeyCode::Char(c) = k.code else {
231 return self.normal_special(k);
232 };
233 if k.modifiers.contains(KeyModifiers::CONTROL) {
234 return Act::Continue;
235 }
236
237 if let Some(a) = self.awaiting.take() {
239 match a {
240 Await::Find { back, till } => {
241 self.last_find = Some((c, back, till));
242 let n = self.take_count();
243 if let Some(t) = self.find_target(c, back, till, n) {
244 self.apply_motion(t, !back);
245 } else {
246 self.op = None;
247 }
248 }
249 Await::Replace => {
250 if self.cursor < self.text.len() {
251 self.snapshot();
252 self.text[self.cursor] = c;
253 }
254 }
255 }
256 return Act::Continue;
257 }
258
259 if c.is_ascii_digit() && !(c == '0' && self.count == 0) {
261 self.count = (self.count.saturating_mul(10))
262 .saturating_add(c.to_digit(10).unwrap_or(0) as usize)
263 .min(100_000);
264 return Act::Continue;
265 }
266
267 if let Some(op) = self.op {
269 if c == op {
270 self.op = None;
271 self.count = 0;
272 return self.whole_line(op);
273 }
274 }
275
276 match c {
277 'h' => {
279 let n = self.take_count();
280 self.apply_motion(self.cursor.saturating_sub(n), false)
281 }
282 'l' | ' ' => {
283 let n = self.take_count();
284 self.apply_motion((self.cursor + n).min(self.text.len()), false)
285 }
286 '0' => self.apply_motion(0, false),
287 '^' => {
288 let t = self.first_non_blank();
289 self.apply_motion(t, false)
290 }
291 '$' => self.apply_motion(self.text.len(), false),
292 'w' | 'W' => {
293 let n = self.take_count();
294 let t = self.word_fwd(n, c == 'W');
295 self.apply_motion(t, false)
296 }
297 'b' | 'B' => {
298 let n = self.take_count();
299 let t = self.word_back(n, c == 'B');
300 self.apply_motion(t, false)
301 }
302 'e' | 'E' => {
303 let n = self.take_count();
304 let t = self.word_end(n, c == 'E');
305 self.apply_motion(t, true)
306 }
307 'f' | 'F' | 't' | 'T' => {
308 self.awaiting = Some(Await::Find {
309 back: c == 'F' || c == 'T',
310 till: c == 't' || c == 'T',
311 });
312 }
313 ';' | ',' => {
314 if let Some((ch, back, till)) = self.last_find {
315 let back = if c == ',' { !back } else { back };
316 let n = self.take_count();
317 if let Some(t) = self.find_target(ch, back, till, n) {
318 self.apply_motion(t, !back);
319 }
320 }
321 }
322 'd' | 'c' | 'y' => self.op = Some(c),
324 'D' => return self.op_to_end('d'),
325 'C' => return self.op_to_end('c'),
326 'Y' => return self.whole_line('y'),
327 'x' => {
329 let n = self.take_count();
330 let end = (self.cursor + n).min(self.text.len());
331 self.cut(self.cursor, end, false);
332 }
333 'X' => {
334 let n = self.take_count();
335 let start = self.cursor.saturating_sub(n);
336 self.cut(start, self.cursor, false);
337 }
338 'r' => self.awaiting = Some(Await::Replace),
339 'i' => self.mode = Mode::Insert,
340 'a' => {
341 self.cursor = (self.cursor + 1).min(self.text.len());
342 self.mode = Mode::Insert;
343 }
344 'I' => {
345 self.cursor = self.first_non_blank();
346 self.mode = Mode::Insert;
347 }
348 'A' => {
349 self.cursor = self.text.len();
350 self.mode = Mode::Insert;
351 }
352 'p' | 'P' => {
353 if !self.reg.is_empty() {
354 self.snapshot();
355 let at = if c == 'p' {
356 (self.cursor + 1).min(self.text.len())
357 } else {
358 self.cursor
359 };
360 let reg = self.reg.clone();
361 let n = reg.len();
362 self.text.splice(at..at, reg);
363 self.cursor = at + n - 1;
364 self.clamp();
365 }
366 }
367 'u' => {
368 if let Some((t, cur)) = self.undo.pop() {
369 self.text = t;
370 self.cursor = cur;
371 self.clamp();
372 }
373 }
374 'k' => self.history_step(-1),
376 'j' => self.history_step(1),
377 _ => {}
378 }
379 Act::Continue
380 }
381
382 fn normal_special(&mut self, k: KeyEvent) -> Act {
383 match k.code {
384 KeyCode::Enter => Act::Send,
385 KeyCode::Esc => {
386 if self.op.is_some() || self.count > 0 || self.awaiting.is_some() {
387 self.op = None;
389 self.count = 0;
390 self.awaiting = None;
391 Act::Continue
392 } else {
393 Act::Leave
394 }
395 }
396 KeyCode::Left => {
397 self.cursor = self.cursor.saturating_sub(1);
398 Act::Continue
399 }
400 KeyCode::Right => {
401 self.cursor += 1;
402 self.clamp();
403 Act::Continue
404 }
405 KeyCode::Up => {
406 self.history_step(-1);
407 Act::Continue
408 }
409 KeyCode::Down => {
410 self.history_step(1);
411 Act::Continue
412 }
413 KeyCode::Home => {
414 self.cursor = 0;
415 Act::Continue
416 }
417 KeyCode::End => {
418 self.cursor = self.text.len();
419 self.clamp();
420 Act::Continue
421 }
422 KeyCode::Backspace => {
423 self.cursor = self.cursor.saturating_sub(1);
424 Act::Continue
425 }
426 _ => Act::Continue,
427 }
428 }
429
430 fn apply_motion(&mut self, target: usize, inclusive: bool) {
433 let target = target.min(self.text.len());
434 match self.op.take() {
435 None => {
436 self.cursor = target;
437 self.clamp();
438 }
439 Some(op) => {
440 let (lo, hi) = if target >= self.cursor {
441 (
442 self.cursor,
443 if inclusive {
444 (target + 1).min(self.text.len())
445 } else {
446 target
447 },
448 )
449 } else {
450 (target, self.cursor)
451 };
452 match op {
453 'y' => self.reg = self.text[lo..hi].to_vec(),
454 'd' => self.cut(lo, hi, false),
455 'c' => self.cut(lo, hi, true),
456 _ => {}
457 }
458 }
459 }
460 }
461
462 fn op_to_end(&mut self, op: char) -> Act {
463 self.op = Some(op);
464 self.apply_motion(self.text.len(), false);
465 Act::Continue
466 }
467
468 fn whole_line(&mut self, op: char) -> Act {
469 match op {
470 'y' => self.reg = self.text.clone(),
471 'd' => self.cut(0, self.text.len(), false),
472 'c' => self.cut(0, self.text.len(), true),
473 _ => {}
474 }
475 Act::Continue
476 }
477
478 fn cut(&mut self, lo: usize, hi: usize, into_insert: bool) {
480 let hi = hi.min(self.text.len());
481 if lo < hi {
482 self.snapshot();
483 self.reg = self.text[lo..hi].to_vec();
484 self.text.drain(lo..hi);
485 }
486 self.cursor = lo;
487 if into_insert {
488 self.mode = Mode::Insert;
489 }
490 self.clamp();
491 }
492
493 fn first_non_blank(&self) -> usize {
494 self.text
495 .iter()
496 .position(|c| !c.is_whitespace())
497 .unwrap_or(0)
498 }
499
500 fn find_target(&self, ch: char, back: bool, till: bool, n: usize) -> Option<usize> {
501 let mut at = self.cursor;
502 for _ in 0..n {
503 at = if back {
504 self.text[..at.min(self.text.len())]
505 .iter()
506 .rposition(|&c| c == ch)?
507 } else {
508 let from = at + 1;
509 from + self.text.get(from..)?.iter().position(|&c| c == ch)?
510 };
511 }
512 Some(match (back, till) {
513 (false, true) => at.saturating_sub(1),
514 (true, true) => at + 1,
515 _ => at,
516 })
517 }
518
519 fn word_fwd(&self, n: usize, big: bool) -> usize {
520 let mut i = self.cursor;
521 let len = self.text.len();
522 for _ in 0..n {
523 if i >= len {
524 break;
525 }
526 let start = if big {
527 1.min(class(self.text[i]))
528 } else {
529 class(self.text[i])
530 };
531 while i < len && cls(self.text[i], big) == start && start != 0 {
533 i += 1;
534 }
535 while i < len && self.text[i].is_whitespace() {
537 i += 1;
538 }
539 }
540 i
541 }
542
543 fn word_back(&self, n: usize, big: bool) -> usize {
544 let mut i = self.cursor;
545 for _ in 0..n {
546 if i == 0 {
547 break;
548 }
549 i -= 1;
550 while i > 0 && self.text[i].is_whitespace() {
551 i -= 1;
552 }
553 let c = cls(self.text[i], big);
554 while i > 0 && cls(self.text[i - 1], big) == c && c != 0 {
555 i -= 1;
556 }
557 }
558 i
559 }
560
561 fn word_end(&self, n: usize, big: bool) -> usize {
562 let mut i = self.cursor;
563 let len = self.text.len();
564 for _ in 0..n {
565 if i + 1 >= len {
566 break;
567 }
568 i += 1;
569 while i < len && self.text[i].is_whitespace() {
570 i += 1;
571 }
572 if i >= len {
573 break;
574 }
575 let c = cls(self.text[i], big);
576 while i + 1 < len && cls(self.text[i + 1], big) == c {
577 i += 1;
578 }
579 }
580 i.min(len.saturating_sub(1))
581 }
582
583 fn history_step(&mut self, dir: i32) {
585 if self.history.is_empty() {
586 return;
587 }
588 let last = self.history.len() - 1;
589 let next = match (self.hist, dir) {
590 (None, -1) => {
591 self.stash = self.text.clone();
592 Some(last)
593 }
594 (Some(i), -1) => Some(i.saturating_sub(1)),
595 (Some(i), 1) if i >= last => None,
596 (Some(i), 1) => Some(i + 1),
597 _ => return,
598 };
599 self.text = match next {
600 Some(i) => self.history[i].chars().collect(),
601 None => std::mem::take(&mut self.stash),
602 };
603 self.hist = next;
604 self.cursor = self.text.len();
605 self.clamp();
606 }
607}
608
609fn cls(c: char, big: bool) -> u8 {
611 let k = class(c);
612 if big && k == 2 {
613 1
614 } else {
615 k
616 }
617}
618
619#[cfg(test)]
620mod tests {
621 use super::*;
622
623 fn key(c: char) -> KeyEvent {
624 KeyEvent::new(KeyCode::Char(c), KeyModifiers::NONE)
625 }
626 fn special(k: KeyCode) -> KeyEvent {
627 KeyEvent::new(k, KeyModifiers::NONE)
628 }
629
630 fn run(l: &mut Line, keys: &str) -> Act {
632 let mut act = Act::Continue;
633 let mut it = keys.chars().peekable();
634 while let Some(c) = it.next() {
635 if c == '<' {
636 let tag: String = it.by_ref().take_while(|&c| c != '>').collect();
637 act = l.key(special(match tag.as_str() {
638 "esc" => KeyCode::Esc,
639 "cr" => KeyCode::Enter,
640 "bs" => KeyCode::Backspace,
641 "up" => KeyCode::Up,
642 "down" => KeyCode::Down,
643 other => panic!("unknown key <{other}>"),
644 }));
645 } else {
646 act = l.key(key(c));
647 }
648 }
649 act
650 }
651
652 fn line(s: &str) -> Line {
653 let mut l = Line::default();
654 run(&mut l, s);
655 l
656 }
657
658 #[test]
659 fn typing_inserts_and_starts_in_insert_mode() {
660 let l = line("hello");
661 assert_eq!(l.text(), "hello");
662 assert_eq!(l.mode(), Mode::Insert);
663 assert_eq!(l.cursor(), 5);
664 }
665
666 #[test]
667 fn escape_enters_normal_mode_and_steps_left() {
668 let l = line("hello<esc>");
669 assert_eq!(l.mode(), Mode::Normal);
670 assert_eq!(
671 l.cursor(),
672 4,
673 "vi puts the cursor on the last char, not past it"
674 );
675 }
676
677 #[test]
678 fn escape_from_normal_leaves_the_composer_keeping_the_draft() {
679 let mut l = line("wip<esc>");
680 assert_eq!(l.key(special(KeyCode::Esc)), Act::Leave);
681 assert_eq!(l.text(), "wip", "the draft survives");
682 }
683
684 #[test]
685 fn escape_first_cancels_a_half_typed_command() {
686 let mut l = line("hello world<esc>0");
687 run(&mut l, "2d");
688 assert_eq!(
689 l.key(special(KeyCode::Esc)),
690 Act::Continue,
691 "cancels the pending d"
692 );
693 assert_eq!(l.text(), "hello world");
694 assert_eq!(
695 l.key(special(KeyCode::Esc)),
696 Act::Leave,
697 "second one leaves"
698 );
699 }
700
701 #[test]
702 fn enter_sends_from_either_mode() {
703 let mut l = line("hi");
704 assert_eq!(l.key(special(KeyCode::Enter)), Act::Send);
705 let mut l = line("hi<esc>");
706 assert_eq!(l.key(special(KeyCode::Enter)), Act::Send);
707 }
708
709 #[test]
712 fn hl_and_line_ends() {
713 let mut l = line("hello world<esc>");
714 run(&mut l, "0");
715 assert_eq!(l.cursor(), 0);
716 run(&mut l, "$");
717 assert_eq!(l.cursor(), 10);
718 run(&mut l, "5h");
719 assert_eq!(l.cursor(), 5);
720 run(&mut l, "2l");
721 assert_eq!(l.cursor(), 7);
722 }
723
724 #[test]
725 fn caret_goes_to_the_first_non_blank() {
726 let mut l = line(" hi<esc>");
727 run(&mut l, "^");
728 assert_eq!(l.cursor(), 3);
729 }
730
731 #[test]
732 fn word_motions_stop_at_class_changes() {
733 let mut l = line("foo.bar baz<esc>0");
734 run(&mut l, "w");
735 assert_eq!(l.cursor(), 3, "w stops at the punctuation");
736 run(&mut l, "w");
737 assert_eq!(l.cursor(), 4);
738 run(&mut l, "w");
739 assert_eq!(l.cursor(), 8);
740 }
741
742 #[test]
743 fn big_word_motions_only_stop_at_blanks() {
744 let mut l = line("foo.bar baz<esc>0");
745 run(&mut l, "W");
746 assert_eq!(l.cursor(), 8, "W treats foo.bar as one WORD");
747 run(&mut l, "B");
748 assert_eq!(l.cursor(), 0);
749 }
750
751 #[test]
752 fn e_lands_on_the_last_char_of_a_word() {
753 let mut l = line("hello world<esc>0");
754 run(&mut l, "e");
755 assert_eq!(l.cursor(), 4);
756 run(&mut l, "e");
757 assert_eq!(l.cursor(), 10);
758 }
759
760 #[test]
761 fn find_and_till_with_repeat() {
762 let mut l = line("a,b,c<esc>0");
763 run(&mut l, "f,");
764 assert_eq!(l.cursor(), 1);
765 run(&mut l, ";");
766 assert_eq!(l.cursor(), 3);
767 run(&mut l, "0t,");
768 assert_eq!(l.cursor(), 0, "t stops before the target");
769 run(&mut l, "$F,");
770 assert_eq!(l.cursor(), 3);
771 }
772
773 #[test]
776 fn dw_deletes_a_word() {
777 let mut l = line("hello world<esc>0");
778 run(&mut l, "dw");
779 assert_eq!(l.text(), "world");
780 }
781
782 #[test]
783 fn counts_apply_to_operators() {
784 let mut l = line("one two three four<esc>0");
785 run(&mut l, "3dw");
786 assert_eq!(l.text(), "four");
787 }
788
789 #[test]
790 fn dd_and_cc_take_the_whole_line() {
791 let mut l = line("throw this away<esc>");
792 run(&mut l, "dd");
793 assert_eq!(l.text(), "");
794 let mut l = line("replace me<esc>");
795 run(&mut l, "cc");
796 assert_eq!(l.text(), "");
797 assert_eq!(l.mode(), Mode::Insert, "c leaves you typing");
798 }
799
800 #[test]
801 fn shift_d_and_c_run_to_end_of_line() {
802 let mut l = line("keep this drop that<esc>0");
803 run(&mut l, "wwD");
804 assert_eq!(l.text(), "keep this ");
805 let mut l = line("keep drop<esc>0");
806 run(&mut l, "wC");
807 assert_eq!(l.text(), "keep ");
808 assert_eq!(l.mode(), Mode::Insert);
809 }
810
811 #[test]
812 fn ci_style_change_then_type() {
813 let mut l = line("hello world<esc>0");
814 run(&mut l, "cwbye ");
815 assert_eq!(l.text(), "bye world");
816 }
817
818 #[test]
819 fn df_deletes_through_the_target() {
820 let mut l = line("a,b,c<esc>0");
821 run(&mut l, "df,");
822 assert_eq!(l.text(), "b,c", "f is inclusive");
823 }
824
825 #[test]
826 fn backwards_operator_deletes_the_span_behind() {
827 let mut l = line("hello world<esc>$");
828 run(&mut l, "db");
829 assert_eq!(l.text(), "hello d");
830 }
831
832 #[test]
835 fn x_deletes_forward_and_yanks_to_the_register() {
836 let mut l = line("abcdef<esc>0");
837 run(&mut l, "3x");
838 assert_eq!(l.text(), "def");
839 run(&mut l, "$p");
840 assert_eq!(l.text(), "defabc", "p pastes after the cursor");
841 }
842
843 #[test]
844 fn shift_p_pastes_before() {
845 let mut l = line("abcdef<esc>0");
846 run(&mut l, "3x0P");
847 assert_eq!(l.text(), "abcdef");
848 }
849
850 #[test]
851 fn r_replaces_one_char() {
852 let mut l = line("cat<esc>0");
853 run(&mut l, "rb");
854 assert_eq!(l.text(), "bat");
855 }
856
857 #[test]
858 fn insert_entry_points_land_in_the_right_place() {
859 let mut l = line("bc<esc>0");
860 run(&mut l, "ia");
861 assert_eq!(l.text(), "abc");
862 let mut l = line("ab<esc>");
863 run(&mut l, "Ac");
864 assert_eq!(l.text(), "abc");
865 let mut l = line("ac<esc>0");
866 run(&mut l, "ab");
867 assert_eq!(l.text(), "abc", "a appends after the cursor");
868 let mut l = line(" bc<esc>");
869 run(&mut l, "Ia");
870 assert_eq!(l.text(), " abc", "I goes to the first non-blank");
871 }
872
873 #[test]
874 fn u_undoes_the_last_change() {
875 let mut l = line("hello world<esc>0");
876 run(&mut l, "dw");
877 assert_eq!(l.text(), "world");
878 run(&mut l, "u");
879 assert_eq!(l.text(), "hello world");
880 }
881
882 #[test]
883 fn yank_then_paste_leaves_the_text_intact() {
884 let mut l = line("word<esc>0");
885 run(&mut l, "yw$p");
886 assert_eq!(l.text(), "wordword");
887 }
888
889 #[test]
893 fn motions_and_deletes_are_char_safe() {
894 let mut l = line("キツネス pog<esc>0");
895 run(&mut l, "x");
896 assert_eq!(l.text(), "ツネス pog");
897 run(&mut l, "dw");
898 assert_eq!(l.text(), "pog");
899 }
900
901 #[test]
902 fn emoji_are_single_positions() {
903 let mut l = line("a🔥b<esc>0");
904 run(&mut l, "l");
905 assert_eq!(l.cursor(), 1);
906 run(&mut l, "x");
907 assert_eq!(l.text(), "ab");
908 }
909
910 #[test]
913 fn accept_records_history_and_clears_the_line() {
914 let mut l = line("first");
915 assert_eq!(l.accept(), "first");
916 assert_eq!(l.text(), "");
917 assert_eq!(l.mode(), Mode::Insert, "ready to type the next one");
918 }
919
920 #[test]
921 fn k_and_j_walk_sent_history() {
922 let mut l = Line::default();
923 run(&mut l, "one");
924 l.accept();
925 run(&mut l, "two");
926 l.accept();
927 run(&mut l, "draft<esc>");
928 run(&mut l, "k");
929 assert_eq!(l.text(), "two");
930 run(&mut l, "k");
931 assert_eq!(l.text(), "one");
932 run(&mut l, "j");
933 assert_eq!(l.text(), "two");
934 run(&mut l, "j");
935 assert_eq!(l.text(), "draft", "coming back restores the draft");
936 }
937
938 #[test]
939 fn history_ignores_blanks_and_immediate_repeats() {
940 let mut l = Line::default();
941 run(&mut l, " ");
942 l.accept();
943 run(&mut l, "same");
944 l.accept();
945 run(&mut l, "same");
946 l.accept();
947 run(&mut l, "<esc>k");
948 assert_eq!(l.text(), "same");
949 run(&mut l, "k");
950 assert_eq!(l.text(), "same", "only one entry, nothing older");
951 }
952
953 #[test]
956 fn every_motion_survives_an_empty_line() {
957 let mut l = Line::default();
958 l.key(special(KeyCode::Esc));
959 for keys in [
960 "h", "l", "w", "b", "e", "$", "0", "^", "x", "X", "dw", "dd", "p", "u", "D",
961 ] {
962 run(&mut l, keys);
963 assert_eq!(l.text(), "");
964 assert_eq!(l.cursor(), 0, "{keys} moved the cursor off an empty line");
965 }
966 }
967
968 #[test]
969 fn counts_cannot_run_off_the_end() {
970 let mut l = line("ab<esc>0");
971 run(&mut l, "999l");
972 assert_eq!(l.cursor(), 1, "normal mode stops on the last char");
973 run(&mut l, "999x");
974 assert_eq!(l.text(), "a");
975 }
976}