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