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