1use crossterm::event::KeyCode;
2use unicode_segmentation::UnicodeSegmentation;
3use unicode_width::UnicodeWidthStr;
4
5use crate::{
6 Component,
7 Event,
8 Focusable,
9 InputResult,
10 RenderError,
11 Rendered,
12 kill_ring::KillRing,
13 undo_stack::UndoStack,
14 word_navigation::{
15 find_word_backward,
16 find_word_forward,
17 },
18};
19
20#[derive(Clone)]
22pub struct EditorAction {
23 pub text: String,
25 pub cursor: usize,
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub enum VimMode {
32 Normal,
34 Insert,
36}
37
38#[derive(Clone)]
47pub struct Editor {
48 text: String,
49 cursor: usize,
50 focused: bool,
51 kill_ring: KillRing,
52 undo_stack: UndoStack<EditorAction>,
53 lines_cache: Vec<String>,
54 cache_width: u16,
55 history: Vec<String>,
56 history_index: Option<usize>,
57 max_history: usize,
58 vim_mode_enabled: bool,
60 mode: VimMode,
62 pending_cmd: Option<char>,
64}
65
66impl Editor {
67 pub fn new() -> Self {
69 Self {
70 text: String::new(),
71 cursor: 0,
72 focused: false,
73 kill_ring: KillRing::new(),
74 undo_stack: UndoStack::new(),
75 lines_cache: Vec::new(),
76 cache_width: 0,
77 history: Vec::new(),
78 history_index: None,
79 max_history: 100,
80 vim_mode_enabled: false,
81 mode: VimMode::Normal,
82 pending_cmd: None,
83 }
84 }
85
86 pub fn vim_mode_enabled(&self) -> bool {
88 self.vim_mode_enabled
89 }
90
91 pub fn set_vim_mode_enabled(&mut self, enabled: bool) {
97 self.vim_mode_enabled = enabled;
98 self.mode = VimMode::Normal;
99 self.pending_cmd = None;
100 }
101
102 pub fn mode(&self) -> VimMode {
104 self.mode
105 }
106
107 pub fn set_mode(&mut self, mode: VimMode) {
109 self.mode = mode;
110 self.pending_cmd = None;
111 }
112
113 pub fn text(&self) -> &str {
115 &self.text
116 }
117
118 pub fn cursor_grapheme(&self) -> usize {
120 self.cursor
121 }
122
123 pub fn set_text(&mut self, text: impl Into<String>) {
125 self.text = text.into();
126 self.cursor = self.graphemes().len();
127 self.lines_cache.clear();
128 self.cache_width = 0;
129 }
130
131 fn graphemes(&self) -> Vec<&str> {
132 self.text.graphemes(true).collect()
133 }
134
135 fn byte_index(&self, grapheme_idx: usize) -> usize {
136 self.text
137 .grapheme_indices(true)
138 .nth(grapheme_idx)
139 .map(|(i, _)| i)
140 .unwrap_or(self.text.len())
141 }
142
143 fn save_undo(&mut self) {
144 self.undo_stack.push(EditorAction {
145 text: self.text.clone(),
146 cursor: self.cursor,
147 });
148 }
149
150 fn insert_char(&mut self, c: char) {
151 let idx = self.byte_index(self.cursor);
152 self.text.insert(idx, c);
153 self.cursor += 1;
154 self.invalidate_cache();
155 }
156
157 fn insert_newline(&mut self) {
158 let idx = self.byte_index(self.cursor);
159 self.text.insert(idx, '\n');
160 self.cursor += 1;
161 self.invalidate_cache();
162 }
163
164 fn insert_str(&mut self, s: &str) {
165 let idx = self.byte_index(self.cursor);
166 self.text.insert_str(idx, s);
167 self.cursor += s.graphemes(true).count();
168 self.invalidate_cache();
169 }
170
171 fn delete_backward(&mut self) {
172 if self.cursor > 0 {
173 let start = self.byte_index(self.cursor - 1);
174 let end = self.byte_index(self.cursor);
175 let killed = self.text.drain(start..end).collect::<String>();
176 self.kill_ring.push(killed);
177 self.cursor -= 1;
178 self.invalidate_cache();
179 }
180 }
181
182 fn delete_forward(&mut self) {
183 if self.cursor < self.graphemes().len() {
184 let start = self.byte_index(self.cursor);
185 let end = self.byte_index(self.cursor + 1);
186 self.text.drain(start..end);
187 self.invalidate_cache();
188 }
189 }
190
191 fn move_cursor_left(&mut self) {
192 if self.cursor > 0 {
193 self.cursor -= 1;
194 }
195 }
196
197 fn move_cursor_right(&mut self) {
198 if self.cursor < self.graphemes().len() {
199 self.cursor += 1;
200 }
201 }
202
203 fn move_cursor_up(&mut self) {
204 let (line, col) = self.cursor_line_col();
205 if line == 0 {
206 return;
207 }
208 let target_line = line - 1;
209 let mut current_line = 0;
210 let mut current_col = 0;
211 for (gidx, g) in self.text.graphemes(true).enumerate() {
212 if current_line == target_line {
213 if current_col >= col || g == "\n" {
214 self.cursor = gidx;
215 return;
216 }
217 current_col += g.width();
218 } else if g == "\n" {
219 current_line += 1;
220 current_col = 0;
221 }
222 }
223 }
224
225 fn move_cursor_down(&mut self) {
226 let (line, col) = self.cursor_line_col();
227 let total_lines = self.text.lines().count();
228 if line + 1 >= total_lines {
229 return;
230 }
231 let target_line = line + 1;
232 let mut current_line = 0;
233 let mut current_col = 0;
234 let mut gidx = 0;
235 for g in self.text.graphemes(true) {
236 if current_line == target_line {
237 if current_col >= col || g == "\n" {
238 self.cursor = gidx;
239 return;
240 }
241 current_col += g.width();
242 } else if g == "\n" {
243 current_line += 1;
244 current_col = 0;
245 }
246 gidx += 1;
247 }
248 self.cursor = gidx;
249 }
250
251 fn move_cursor_home(&mut self) {
252 let (line, _) = self.cursor_line_col();
253 let mut current_line = 0;
254 for (gidx, g) in self.text.graphemes(true).enumerate() {
255 if current_line == line {
256 self.cursor = gidx;
257 return;
258 }
259 if g == "\n" {
260 current_line += 1;
261 }
262 }
263 }
264
265 fn move_cursor_end(&mut self) {
266 let (line, _) = self.cursor_line_col();
267 let mut current_line = 0;
268 let mut gidx = 0;
269 let mut found = false;
270 for g in self.text.graphemes(true) {
271 if g == "\n" {
272 if found {
273 self.cursor = gidx;
274 return;
275 }
276 current_line += 1;
277 }
278 if current_line == line {
279 found = true;
280 }
281 gidx += 1;
282 }
283 self.cursor = gidx;
284 }
285
286 fn move_word_forward(&mut self) {
287 let idx = self.byte_index(self.cursor);
288 let new_idx = find_word_forward(&self.text, idx, |c| c.is_whitespace());
289 let slice = &self.text[idx..new_idx];
290 self.cursor += slice.graphemes(true).count();
291 }
292
293 fn move_word_backward(&mut self) {
294 let idx = self.byte_index(self.cursor);
295 let new_idx = find_word_backward(&self.text, idx, |c| c.is_whitespace());
296 let slice = &self.text[new_idx..idx];
297 self.cursor -= slice.graphemes(true).count();
298 }
299
300 fn kill_word_forward(&mut self) {
301 let idx = self.byte_index(self.cursor);
302 let new_idx = find_word_forward(&self.text, idx, |c| c.is_whitespace());
303 let killed = self.text.drain(idx..new_idx).collect::<String>();
304 self.kill_ring.push(killed);
305 self.invalidate_cache();
306 }
307
308 fn kill_word_backward(&mut self) {
309 let idx = self.byte_index(self.cursor);
310 let new_idx = find_word_backward(&self.text, idx, |c| c.is_whitespace());
311 let killed = self.text.drain(new_idx..idx).collect::<String>();
312 let count = killed.graphemes(true).count();
313 self.kill_ring.push(killed);
314 self.cursor -= count;
315 self.invalidate_cache();
316 }
317
318 fn kill_to_end(&mut self) {
319 let idx = self.byte_index(self.cursor);
320 let killed = self.text.split_off(idx);
321 self.kill_ring.push(killed);
322 self.invalidate_cache();
323 }
324
325 fn yank(&mut self) {
326 if let Some(text) = self.kill_ring.yank().map(|s| s.to_string()) {
327 self.insert_str(&text);
328 }
329 }
330
331 fn yank_pop(&mut self) {
332 if let Some(text) = self.kill_ring.yank_pop().map(|s| s.to_string()) {
333 self.insert_str(&text);
334 }
335 }
336
337 fn undo(&mut self) {
338 if let Some(action) = self.undo_stack.undo() {
339 self.text = action.text.clone();
340 self.cursor = action.cursor;
341 self.invalidate_cache();
342 }
343 }
344
345 fn redo(&mut self) {
346 if let Some(action) = self.undo_stack.redo() {
347 self.text = action.text.clone();
348 self.cursor = action.cursor;
349 self.invalidate_cache();
350 }
351 }
352
353 pub fn push_history(&mut self) {
359 if !self.text.is_empty() {
360 self.history.push(self.text.clone());
361 if self.history.len() > self.max_history {
362 self.history.remove(0);
363 }
364 }
365 self.history_index = None;
366 }
367
368 fn history_up(&mut self) {
369 if self.history.is_empty() {
370 return;
371 }
372 let idx = match self.history_index {
373 | Some(i) if i > 0 => i - 1,
374 | Some(_) => return,
375 | None => self.history.len() - 1,
376 };
377 self.history_index = Some(idx);
378 self.text = self.history[idx].clone();
379 self.cursor = self.graphemes().len();
380 self.invalidate_cache();
381 }
382
383 fn history_down(&mut self) {
384 let idx = match self.history_index {
385 | Some(i) if i + 1 < self.history.len() => i + 1,
386 | Some(_) => {
387 self.history_index = None;
388 self.text.clear();
389 self.cursor = 0;
390 self.invalidate_cache();
391 return;
392 },
393 | None => return,
394 };
395 self.history_index = Some(idx);
396 self.text = self.history[idx].clone();
397 self.cursor = self.graphemes().len();
398 self.invalidate_cache();
399 }
400
401 fn invalidate_cache(&mut self) {
402 self.lines_cache.clear();
403 self.cache_width = 0;
404 }
405
406 fn delete_line(&mut self) {
408 let (line, _) = self.cursor_line_col();
409 let mut current_line = 0;
410 let mut start_byte = 0;
411 let mut byte_pos = 0;
412 for g in self.text.graphemes(true) {
413 if current_line == line {
414 start_byte = byte_pos;
415 break;
416 }
417 if g == "\n" {
418 current_line += 1;
419 }
420 byte_pos += g.len();
421 }
422 let mut end_byte = self.text.len();
424 byte_pos = 0;
425 let mut found = false;
426 for g in self.text.graphemes(true) {
427 if found && g == "\n" {
428 end_byte = byte_pos + g.len();
429 break;
430 }
431 if byte_pos >= start_byte {
432 found = true;
433 }
434 byte_pos += g.len();
435 }
436 self.cursor = self.text[..start_byte].graphemes(true).count();
437 let killed = self.text.drain(start_byte..end_byte).collect::<String>();
438 if !killed.is_empty() {
439 self.kill_ring.push(killed);
440 }
441 self.invalidate_cache();
442 }
443
444 fn yank_line(&mut self) {
446 let (line, _) = self.cursor_line_col();
447 let mut current_line = 0;
448 let mut start_byte = 0;
449 let mut byte_pos = 0;
450 for g in self.text.graphemes(true) {
451 if current_line == line {
452 start_byte = byte_pos;
453 break;
454 }
455 if g == "\n" {
456 current_line += 1;
457 }
458 byte_pos += g.len();
459 }
460 let mut end_byte = self.text.len();
461 byte_pos = 0;
462 let mut found = false;
463 for g in self.text.graphemes(true) {
464 if found && g == "\n" {
465 end_byte = byte_pos + g.len();
466 break;
467 }
468 if byte_pos >= start_byte {
469 found = true;
470 }
471 byte_pos += g.len();
472 }
473 let yanked = self.text[start_byte..end_byte].to_string();
474 if !yanked.is_empty() {
475 self.kill_ring.push(yanked);
476 }
477 }
478
479 fn open_line_below(&mut self) {
481 self.move_cursor_end();
482 self.insert_newline();
483 self.mode = VimMode::Insert;
484 }
485
486 fn open_line_above(&mut self) {
488 self.move_cursor_home();
489 if self.cursor > 0 {
490 self.cursor -= 1; self.insert_newline();
492 } else {
493 self.insert_newline();
494 self.cursor = 0;
495 }
496 self.mode = VimMode::Insert;
497 }
498
499 fn replace_char(&mut self, c: char) {
501 if self.cursor < self.graphemes().len() {
502 let start = self.byte_index(self.cursor);
503 let end = self.byte_index(self.cursor + 1);
504 self.text.drain(start..end);
505 self.text.insert(start, c);
506 self.invalidate_cache();
507 }
508 }
509
510 fn go_to_start(&mut self) {
512 self.cursor = 0;
513 }
514
515 fn go_to_end(&mut self) {
517 self.cursor = self.graphemes().len();
518 }
519
520 fn cursor_line_col(&self) -> (usize, usize) {
522 let mut current_line = 0;
523 let mut current_col = 0;
524 for (graphemes_seen, g) in self.text.graphemes(true).enumerate() {
525 if graphemes_seen >= self.cursor {
526 break;
527 }
528 if g == "\n" {
529 current_line += 1;
530 current_col = 0;
531 } else {
532 current_col += g.width();
533 }
534 }
535 (current_line, current_col)
536 }
537}
538
539impl Default for Editor {
540 fn default() -> Self {
541 Self::new()
542 }
543}
544
545impl Focusable for Editor {
546 fn focused(&self) -> bool {
547 self.focused
548 }
549
550 fn set_focused(&mut self, focused: bool) {
551 self.focused = focused;
552 }
553}
554
555impl Editor {
556 fn handle_insert_mode(&mut self, key: &crossterm::event::KeyEvent) -> InputResult {
557 use crossterm::event::KeyModifiers;
558 self.save_undo();
559 match key.code {
560 | KeyCode::Char(c) => {
561 if key.modifiers.contains(KeyModifiers::CONTROL) {
562 match c {
563 | 'a' => self.move_cursor_home(),
564 | 'e' => self.move_cursor_end(),
565 | 'b' => self.move_cursor_left(),
566 | 'f' => self.move_cursor_right(),
567 | 'n' => self.move_cursor_down(),
568 | 'p' => self.move_cursor_up(),
569 | 'd' => self.delete_forward(),
570 | 'h' => self.delete_backward(),
571 | 'k' => self.kill_to_end(),
572 | 'w' => self.kill_word_backward(),
573 | 'u' => {
574 self.move_cursor_home();
575 self.kill_to_end();
576 },
577 | 'y' => self.yank(),
578 | 'r' => self.redo(),
579 | '-' | '_' => self.undo(),
580 | _ => return InputResult::Ignored,
581 }
582 } else if key.modifiers.contains(KeyModifiers::ALT) {
583 match c {
584 | 'b' => self.move_word_backward(),
585 | 'f' => self.move_word_forward(),
586 | 'd' => self.kill_word_forward(),
587 | 'y' => self.yank_pop(),
588 | _ => return InputResult::Ignored,
589 }
590 } else {
591 self.insert_char(c);
592 }
593 InputResult::Handled
594 },
595 | KeyCode::Enter => {
596 let idx = self.byte_index(self.cursor);
597 if idx > 0 && self.text.as_bytes().get(idx - 1) == Some(&b'\\') {
598 self.text.remove(idx - 1);
599 self.cursor -= 1;
600 self.insert_newline();
601 } else {
602 self.insert_newline();
603 }
604 InputResult::Handled
605 },
606 | KeyCode::Left => {
607 self.move_cursor_left();
608 InputResult::Handled
609 },
610 | KeyCode::Right => {
611 self.move_cursor_right();
612 InputResult::Handled
613 },
614 | KeyCode::Up => {
615 if key.modifiers.contains(KeyModifiers::CONTROL) {
616 self.move_cursor_up();
617 } else {
618 self.history_up();
619 }
620 InputResult::Handled
621 },
622 | KeyCode::Down => {
623 if key.modifiers.contains(KeyModifiers::CONTROL) {
624 self.move_cursor_down();
625 } else {
626 self.history_down();
627 }
628 InputResult::Handled
629 },
630 | KeyCode::Home => {
631 self.move_cursor_home();
632 InputResult::Handled
633 },
634 | KeyCode::End => {
635 self.move_cursor_end();
636 InputResult::Handled
637 },
638 | KeyCode::Backspace => {
639 self.delete_backward();
640 InputResult::Handled
641 },
642 | KeyCode::Delete => {
643 self.delete_forward();
644 InputResult::Handled
645 },
646 | KeyCode::Esc => {
647 self.mode = VimMode::Normal;
648 InputResult::Handled
649 },
650 | _ => InputResult::Ignored,
651 }
652 }
653
654 fn handle_normal_mode(&mut self, key: &crossterm::event::KeyEvent) -> InputResult {
655 if let Some(pending) = self.pending_cmd {
657 match key.code {
658 | KeyCode::Char('d') if pending == 'd' => {
659 self.save_undo();
660 self.delete_line();
661 self.pending_cmd = None;
662 return InputResult::Handled;
663 },
664 | KeyCode::Char('y') if pending == 'y' => {
665 self.yank_line();
666 self.pending_cmd = None;
667 return InputResult::Handled;
668 },
669 | KeyCode::Char('g') if pending == 'g' => {
670 self.go_to_start();
671 self.pending_cmd = None;
672 return InputResult::Handled;
673 },
674 | KeyCode::Char('w') if pending == 'd' => {
675 self.save_undo();
676 self.kill_word_forward();
677 self.pending_cmd = None;
678 return InputResult::Handled;
679 },
680 | KeyCode::Char('w') if pending == 'y' => {
681 let start = self.cursor;
682 self.move_word_forward();
683 let end_byte = self.byte_index(self.cursor);
684 let start_byte = self.byte_index(start);
685 let yanked = self.text[start_byte..end_byte].to_string();
686 if !yanked.is_empty() {
687 self.kill_ring.push(yanked);
688 }
689 self.cursor = start;
690 self.pending_cmd = None;
691 return InputResult::Handled;
692 },
693 | KeyCode::Char(c) if pending == 'r' => {
694 self.save_undo();
695 self.replace_char(c);
696 self.pending_cmd = None;
697 return InputResult::Handled;
698 },
699 | _ => {
700 self.pending_cmd = None;
701 },
703 }
704 }
705
706 match key.code {
707 | KeyCode::Char(c) => {
708 match c {
709 | 'h' => self.move_cursor_left(),
710 | 'j' => self.move_cursor_down(),
711 | 'k' => self.move_cursor_up(),
712 | 'l' => self.move_cursor_right(),
713 | 'w' => self.move_word_forward(),
714 | 'b' => self.move_word_backward(),
715 | 'x' => {
716 self.save_undo();
717 self.delete_forward();
718 },
719 | '0' => self.move_cursor_home(),
720 | '$' => self.move_cursor_end(),
721 | 'i' => self.mode = VimMode::Insert,
722 | 'a' => {
723 self.move_cursor_right();
724 self.mode = VimMode::Insert;
725 },
726 | 'o' => {
727 self.save_undo();
728 self.open_line_below();
729 },
730 | 'O' => {
731 self.save_undo();
732 self.open_line_above();
733 },
734 | 'p' => {
735 self.save_undo();
736 self.yank();
737 },
738 | 'u' => {
739 self.save_undo();
740 self.undo();
741 },
742 | 'r' => {
743 self.pending_cmd = Some('r');
744 return InputResult::Handled;
745 },
746 | 'd' | 'y' => {
747 self.pending_cmd = Some(c);
748 return InputResult::Handled;
749 },
750 | 'g' => {
751 self.pending_cmd = Some('g');
752 return InputResult::Handled;
753 },
754 | 'G' => self.go_to_end(),
755 | _ => return InputResult::Ignored,
756 }
757 InputResult::Handled
758 },
759 | KeyCode::Left => {
760 self.move_cursor_left();
761 InputResult::Handled
762 },
763 | KeyCode::Right => {
764 self.move_cursor_right();
765 InputResult::Handled
766 },
767 | KeyCode::Up => {
768 self.move_cursor_up();
769 InputResult::Handled
770 },
771 | KeyCode::Down => {
772 self.move_cursor_down();
773 InputResult::Handled
774 },
775 | KeyCode::Home => {
776 self.move_cursor_home();
777 InputResult::Handled
778 },
779 | KeyCode::End => {
780 self.move_cursor_end();
781 InputResult::Handled
782 },
783 | KeyCode::Backspace => {
784 self.move_cursor_left();
785 InputResult::Handled
786 },
787 | _ => InputResult::Ignored,
788 }
789 }
790}
791
792impl Component for Editor {
793 fn render(&self, width: u16) -> Result<Rendered, RenderError> {
794 let editor = self.clone();
795 let (cursor_line, cursor_col) = editor.cursor_line_col();
796 let lines = if width == self.cache_width && !self.lines_cache.is_empty() {
797 self.lines_cache.clone()
798 } else {
799 crate::utils::wrap_text_with_ansi(&self.text, width)
800 };
801 Ok(Rendered {
802 lines,
803 cursor: if self.focused {
804 Some((cursor_line, cursor_col))
805 } else {
806 None
807 },
808 images: Vec::new(),
809 })
810 }
811
812 fn handle_input(&mut self, event: &Event) -> InputResult {
813 if let Event::Key(key) = event {
814 if self.vim_mode_enabled {
815 match self.mode {
816 | VimMode::Insert => self.handle_insert_mode(key),
817 | VimMode::Normal => self.handle_normal_mode(key),
818 }
819 } else {
820 self.handle_insert_mode(key)
821 }
822 } else {
823 InputResult::Ignored
824 }
825 }
826
827 fn as_focusable(&self) -> Option<&dyn Focusable> {
828 Some(self)
829 }
830
831 fn as_focusable_mut(&mut self) -> Option<&mut dyn Focusable> {
832 Some(self)
833 }
834}
835
836#[cfg(test)]
837mod tests {
838 use crossterm::event::{
839 KeyCode,
840 KeyEvent,
841 KeyModifiers,
842 };
843
844 use super::*;
845
846 fn key_event(code: KeyCode) -> Event {
847 Event::Key(code.into())
848 }
849
850 #[test]
851 fn yank_pop_cycles() {
852 let mut editor = Editor::new();
853 editor.insert_str("ab");
854 editor.move_cursor_home();
855 editor.kill_to_end();
856 assert_eq!(editor.text(), "");
857 editor.yank();
858 assert_eq!(editor.text(), "ab");
859 editor.yank_pop();
860 assert_eq!(editor.text(), "abab");
861 }
862
863 #[test]
864 fn history_down_navigation() {
865 let mut editor = Editor::new();
866 editor.insert_str("a");
867 editor.push_history();
868 editor.move_cursor_home();
869 editor.kill_to_end();
870 editor.insert_str("b");
871 editor.push_history();
872 editor.history_up();
873 assert_eq!(editor.text(), "b");
874 editor.history_up();
875 assert_eq!(editor.text(), "a");
876 editor.history_down();
877 assert_eq!(editor.text(), "b");
878 editor.history_down();
879 assert_eq!(editor.text(), "");
880 }
881
882 #[test]
883 fn history_down_empty() {
884 let mut editor = Editor::new();
885 editor.history_down();
886 assert_eq!(editor.text(), "");
887 }
888
889 #[test]
890 fn push_history_empty() {
891 let mut editor = Editor::new();
892 editor.push_history();
893 assert_eq!(editor.text(), "");
894 }
895
896 #[test]
897 fn move_cursor_up_down() {
898 let mut editor = Editor::new();
899 editor.insert_str("a\nb");
900 editor.move_cursor_up();
901 assert_eq!(editor.cursor_grapheme(), 1);
904 editor.move_cursor_down();
905 assert_eq!(editor.cursor_grapheme(), 3);
906 }
907
908 #[test]
909 fn kill_word_forward() {
910 let mut editor = Editor::new();
911 editor.insert_str("hello world");
912 editor.move_cursor_home();
913 editor.kill_word_forward();
914 assert_eq!(editor.text(), " world");
915 }
916
917 #[test]
918 fn render_unfocused() {
919 let mut editor = Editor::new();
920 editor.set_focused(false);
921 editor.insert_str("x");
922 let r = editor.render(80).unwrap();
923 assert!(r.cursor.is_none());
924 }
925
926 #[test]
927 fn ctrl_a_e_navigation() {
928 let mut editor = Editor::new();
929 editor.insert_str("abc");
930 editor.move_cursor_home();
931 assert_eq!(editor.cursor_grapheme(), 0);
932 editor.move_cursor_end();
933 assert_eq!(editor.cursor_grapheme(), 3);
934 }
935
936 #[test]
937 fn ctrl_f_b_navigation() {
938 let mut editor = Editor::new();
939 editor.insert_str("ab");
940 editor.move_cursor_home();
941 editor.move_cursor_right();
942 assert_eq!(editor.cursor_grapheme(), 1);
943 editor.move_cursor_left();
944 assert_eq!(editor.cursor_grapheme(), 0);
945 }
946
947 #[test]
948 fn alt_f_forward() {
949 let mut editor = Editor::new();
950 editor.insert_str("hi there");
951 editor.move_cursor_home();
952 editor.move_word_forward();
953 assert_eq!(editor.cursor_grapheme(), 2);
954 }
955
956 #[test]
957 fn home_end_keys() {
958 let mut editor = Editor::new();
959 editor.insert_str("ab");
960 editor.handle_input(&key_event(KeyCode::Home));
961 assert_eq!(editor.cursor_grapheme(), 0);
962 editor.handle_input(&key_event(KeyCode::End));
963 assert_eq!(editor.cursor_grapheme(), 2);
964 }
965
966 #[test]
967 fn delete_at_end() {
968 let mut editor = Editor::new();
969 editor.insert_str("a");
970 editor.move_cursor_end();
971 editor.delete_forward();
972 assert_eq!(editor.text(), "a");
973 }
974
975 #[test]
976 fn backspace_at_start() {
977 let mut editor = Editor::new();
978 editor.delete_backward();
979 assert_eq!(editor.text(), "");
980 }
981
982 #[test]
983 fn cursor_line_col_first_line() {
984 let mut editor = Editor::new();
985 editor.insert_str("hello");
986 let (line, col) = editor.cursor_line_col();
987 assert_eq!(line, 0);
988 assert_eq!(col, 5);
989 }
990
991 #[test]
992 fn cursor_line_col_second_line() {
993 let mut editor = Editor::new();
994 editor.insert_str("hello\nworld");
995 assert_eq!(editor.cursor_line_col(), (1, 5));
996 }
997
998 #[test]
999 fn graphemes_count() {
1000 let mut editor = Editor::new();
1001 editor.insert_str("éà");
1002 assert_eq!(editor.graphemes().len(), 2);
1003 }
1004
1005 #[test]
1006 fn byte_index_bounds() {
1007 let mut editor = Editor::new();
1008 editor.insert_str("ab");
1009 assert_eq!(editor.byte_index(0), 0);
1010 assert_eq!(editor.byte_index(2), 2);
1011 assert_eq!(editor.byte_index(10), 2);
1012 }
1013
1014 #[test]
1015 fn move_cursor_up_from_line_two() {
1016 let mut editor = Editor::new();
1017 editor.insert_str("a\nb\nc");
1018 editor.move_cursor_end(); editor.move_cursor_up();
1020 assert_eq!(editor.cursor_grapheme(), 3);
1022 }
1023
1024 #[test]
1025 fn move_cursor_down_from_start() {
1026 let mut editor = Editor::new();
1027 editor.insert_str("a\nb");
1028 editor.cursor = 0;
1029 editor.move_cursor_down();
1030 assert_eq!(editor.cursor_grapheme(), 2);
1031 }
1032
1033 #[test]
1034 fn move_cursor_home_multiline() {
1035 let mut editor = Editor::new();
1036 editor.insert_str("a\nb");
1037 editor.cursor = 3;
1038 editor.move_cursor_home();
1039 assert_eq!(editor.cursor_grapheme(), 2);
1040 }
1041
1042 #[test]
1043 fn move_cursor_end_multiline() {
1044 let mut editor = Editor::new();
1045 editor.insert_str("a\nb");
1046 editor.cursor = 0;
1047 editor.move_cursor_end();
1048 assert_eq!(editor.cursor_grapheme(), 1);
1049 }
1050
1051 #[test]
1052 fn history_up_past_start() {
1053 let mut editor = Editor::new();
1054 editor.insert_str("a");
1055 editor.push_history();
1056 editor.history_up();
1057 editor.history_up(); assert_eq!(editor.text(), "a");
1059 }
1060
1061 #[test]
1062 fn push_history_max_limit() {
1063 let mut editor = Editor::new();
1064 for i in 0..105 {
1065 editor.text = i.to_string();
1066 editor.cursor = 1;
1067 editor.push_history();
1068 }
1069 assert_eq!(editor.history.len(), 100);
1071 }
1072
1073 #[test]
1076 fn vim_starts_in_normal_mode() {
1077 let mut editor = Editor::new();
1078 editor.set_vim_mode_enabled(true);
1079 assert_eq!(editor.mode(), VimMode::Normal);
1080 }
1081
1082 #[test]
1083 fn vim_hjkl_navigation() {
1084 let mut editor = Editor::new();
1085 editor.set_vim_mode_enabled(true);
1086 editor.set_mode(VimMode::Insert);
1087 editor.insert_str("ab\ncd\nef");
1088 editor.set_mode(VimMode::Normal);
1089 editor.cursor = 0;
1090 editor.move_cursor_down(); assert_eq!(editor.cursor_grapheme(), 3); editor.handle_input(&Event::Key(KeyEvent::new(
1093 KeyCode::Char('l'),
1094 KeyModifiers::empty(),
1095 )));
1096 assert_eq!(editor.cursor_grapheme(), 4); editor.handle_input(&Event::Key(KeyEvent::new(
1098 KeyCode::Char('h'),
1099 KeyModifiers::empty(),
1100 )));
1101 assert_eq!(editor.cursor_grapheme(), 3); editor.handle_input(&Event::Key(KeyEvent::new(
1103 KeyCode::Char('j'),
1104 KeyModifiers::empty(),
1105 )));
1106 assert_eq!(editor.cursor_grapheme(), 6); editor.handle_input(&Event::Key(KeyEvent::new(
1108 KeyCode::Char('k'),
1109 KeyModifiers::empty(),
1110 )));
1111 assert_eq!(editor.cursor_grapheme(), 3); }
1113
1114 #[test]
1115 fn vim_i_enters_insert_mode() {
1116 let mut editor = Editor::new();
1117 editor.set_vim_mode_enabled(true);
1118 editor.handle_input(&Event::Key(KeyEvent::new(
1119 KeyCode::Char('i'),
1120 KeyModifiers::empty(),
1121 )));
1122 assert_eq!(editor.mode(), VimMode::Insert);
1123 editor.handle_input(&key_event(KeyCode::Char('x')));
1124 assert_eq!(editor.text(), "x");
1125 }
1126
1127 #[test]
1128 fn vim_esc_returns_to_normal_mode() {
1129 let mut editor = Editor::new();
1130 editor.set_vim_mode_enabled(true);
1131 editor.set_mode(VimMode::Insert);
1132 editor.handle_input(&key_event(KeyCode::Esc));
1133 assert_eq!(editor.mode(), VimMode::Normal);
1134 }
1135
1136 #[test]
1137 fn vim_x_deletes_char() {
1138 let mut editor = Editor::new();
1139 editor.set_vim_mode_enabled(true);
1140 editor.set_mode(VimMode::Insert);
1141 editor.insert_str("abc");
1142 editor.set_mode(VimMode::Normal);
1143 editor.move_cursor_home();
1144 editor.handle_input(&Event::Key(KeyEvent::new(
1145 KeyCode::Char('x'),
1146 KeyModifiers::empty(),
1147 )));
1148 assert_eq!(editor.text(), "bc");
1149 }
1150
1151 #[test]
1152 fn vim_dd_deletes_line() {
1153 let mut editor = Editor::new();
1154 editor.set_vim_mode_enabled(true);
1155 editor.set_mode(VimMode::Insert);
1156 editor.insert_str("hello\nworld");
1157 editor.set_mode(VimMode::Normal);
1158 editor.cursor = 0;
1159 editor.handle_input(&Event::Key(KeyEvent::new(
1160 KeyCode::Char('d'),
1161 KeyModifiers::empty(),
1162 )));
1163 editor.handle_input(&Event::Key(KeyEvent::new(
1164 KeyCode::Char('d'),
1165 KeyModifiers::empty(),
1166 )));
1167 assert_eq!(editor.text(), "world");
1168 }
1169
1170 #[test]
1171 fn vim_yy_yanks_line() {
1172 let mut editor = Editor::new();
1173 editor.set_vim_mode_enabled(true);
1174 editor.set_mode(VimMode::Insert);
1175 editor.insert_str("hello\nworld");
1176 editor.set_mode(VimMode::Normal);
1177 editor.cursor = 0;
1178 editor.handle_input(&Event::Key(KeyEvent::new(
1179 KeyCode::Char('y'),
1180 KeyModifiers::empty(),
1181 )));
1182 editor.handle_input(&Event::Key(KeyEvent::new(
1183 KeyCode::Char('y'),
1184 KeyModifiers::empty(),
1185 )));
1186 editor.handle_input(&Event::Key(KeyEvent::new(
1188 KeyCode::Char('p'),
1189 KeyModifiers::empty(),
1190 )));
1191 assert_eq!(editor.text(), "hello\nhello\nworld");
1192 }
1193
1194 #[test]
1195 fn vim_0_and_dollar() {
1196 let mut editor = Editor::new();
1197 editor.set_vim_mode_enabled(true);
1198 editor.set_mode(VimMode::Insert);
1199 editor.insert_str("abc");
1200 editor.set_mode(VimMode::Normal);
1201 editor.move_cursor_end();
1202 editor.handle_input(&Event::Key(KeyEvent::new(
1203 KeyCode::Char('0'),
1204 KeyModifiers::empty(),
1205 )));
1206 assert_eq!(editor.cursor_grapheme(), 0);
1207 editor.handle_input(&Event::Key(KeyEvent::new(
1208 KeyCode::Char('$'),
1209 KeyModifiers::empty(),
1210 )));
1211 assert_eq!(editor.cursor_grapheme(), 3);
1212 }
1213
1214 #[test]
1215 fn vim_gg_and_g() {
1216 let mut editor = Editor::new();
1217 editor.set_vim_mode_enabled(true);
1218 editor.set_mode(VimMode::Insert);
1219 editor.insert_str("a\nb\nc");
1220 editor.set_mode(VimMode::Normal);
1221 editor.move_cursor_end();
1222 editor.handle_input(&Event::Key(KeyEvent::new(
1223 KeyCode::Char('g'),
1224 KeyModifiers::empty(),
1225 )));
1226 editor.handle_input(&Event::Key(KeyEvent::new(
1227 KeyCode::Char('g'),
1228 KeyModifiers::empty(),
1229 )));
1230 assert_eq!(editor.cursor_grapheme(), 0);
1231 editor.handle_input(&Event::Key(KeyEvent::new(
1232 KeyCode::Char('G'),
1233 KeyModifiers::empty(),
1234 )));
1235 assert_eq!(editor.cursor_grapheme(), 5);
1236 }
1237
1238 #[test]
1239 fn vim_a_appends() {
1240 let mut editor = Editor::new();
1241 editor.set_vim_mode_enabled(true);
1242 editor.set_mode(VimMode::Insert);
1243 editor.insert_str("a");
1244 editor.set_mode(VimMode::Normal);
1245 editor.move_cursor_home();
1246 editor.handle_input(&Event::Key(KeyEvent::new(
1247 KeyCode::Char('a'),
1248 KeyModifiers::empty(),
1249 )));
1250 assert_eq!(editor.mode(), VimMode::Insert);
1251 editor.handle_input(&key_event(KeyCode::Char('b')));
1252 assert_eq!(editor.text(), "ab");
1253 }
1254
1255 #[test]
1256 fn vim_o_opens_line_below() {
1257 let mut editor = Editor::new();
1258 editor.set_vim_mode_enabled(true);
1259 editor.set_mode(VimMode::Insert);
1260 editor.insert_str("a");
1261 editor.set_mode(VimMode::Normal);
1262 editor.handle_input(&Event::Key(KeyEvent::new(
1263 KeyCode::Char('o'),
1264 KeyModifiers::empty(),
1265 )));
1266 assert_eq!(editor.mode(), VimMode::Insert);
1267 assert_eq!(editor.text(), "a\n");
1268 }
1269
1270 #[test]
1271 fn vim_o_opens_line_above() {
1272 let mut editor = Editor::new();
1273 editor.set_vim_mode_enabled(true);
1274 editor.set_mode(VimMode::Insert);
1275 editor.insert_str("a");
1276 editor.set_mode(VimMode::Normal);
1277 editor.handle_input(&Event::Key(KeyEvent::new(
1278 KeyCode::Char('O'),
1279 KeyModifiers::empty(),
1280 )));
1281 assert_eq!(editor.mode(), VimMode::Insert);
1282 assert_eq!(editor.text(), "\na");
1283 }
1284
1285 #[test]
1286 fn vim_r_replaces_char() {
1287 let mut editor = Editor::new();
1288 editor.set_vim_mode_enabled(true);
1289 editor.set_mode(VimMode::Insert);
1290 editor.insert_str("abc");
1291 editor.set_mode(VimMode::Normal);
1292 editor.move_cursor_home();
1293 editor.handle_input(&Event::Key(KeyEvent::new(
1294 KeyCode::Char('r'),
1295 KeyModifiers::empty(),
1296 )));
1297 editor.handle_input(&Event::Key(KeyEvent::new(
1298 KeyCode::Char('x'),
1299 KeyModifiers::empty(),
1300 )));
1301 assert_eq!(editor.text(), "xbc");
1302 }
1303
1304 #[test]
1305 fn vim_dw_deletes_word() {
1306 let mut editor = Editor::new();
1307 editor.set_vim_mode_enabled(true);
1308 editor.set_mode(VimMode::Insert);
1309 editor.insert_str("hello world");
1310 editor.set_mode(VimMode::Normal);
1311 editor.move_cursor_home();
1312 editor.handle_input(&Event::Key(KeyEvent::new(
1313 KeyCode::Char('d'),
1314 KeyModifiers::empty(),
1315 )));
1316 editor.handle_input(&Event::Key(KeyEvent::new(
1317 KeyCode::Char('w'),
1318 KeyModifiers::empty(),
1319 )));
1320 assert_eq!(editor.text(), " world");
1321 }
1322
1323 #[test]
1324 fn vim_u_undo() {
1325 let mut editor = Editor::new();
1326 editor.set_vim_mode_enabled(true);
1327 editor.set_mode(VimMode::Insert);
1328 editor.handle_input(&key_event(KeyCode::Char('a')));
1329 editor.handle_input(&key_event(KeyCode::Char('b')));
1330 editor.set_mode(VimMode::Normal);
1331 editor.handle_input(&Event::Key(KeyEvent::new(
1332 KeyCode::Char('u'),
1333 KeyModifiers::empty(),
1334 )));
1335 assert_eq!(editor.text(), "a");
1336 }
1337
1338 #[test]
1339 fn vim_normal_mode_arrow_keys_work() {
1340 let mut editor = Editor::new();
1341 editor.set_vim_mode_enabled(true);
1342 editor.set_mode(VimMode::Insert);
1343 editor.insert_str("ab");
1344 editor.set_mode(VimMode::Normal);
1345 editor.move_cursor_end();
1346 editor.handle_input(&key_event(KeyCode::Left));
1347 assert_eq!(editor.cursor_grapheme(), 1);
1348 }
1349}