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