1use crate::{Action, EditResult, Key, KeyCode, LineEditor, TextEdit};
8use std::ops::Range;
9
10mod command_line;
11mod edits;
12mod motions;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
16pub(crate) enum Mode {
17 #[default]
18 Normal,
19 Insert,
20 OperatorPending(Operator),
21 Visual,
22 ReplaceChar,
24 CommandLine,
28}
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub(crate) enum Operator {
33 Delete,
34 Change,
35 Yank,
36}
37
38#[derive(Debug, Clone)]
43pub struct VimLineEditor {
44 pub(in crate::vim) cursor: usize,
45 pub(in crate::vim) mode: Mode,
46 pub(in crate::vim) visual_anchor: Option<usize>,
48 pub(in crate::vim) yank_buffer: String,
50 pub(in crate::vim) command_mode_enabled: bool,
54 pub(in crate::vim) command_buf: String,
57 pub(in crate::vim) command_cursor: usize,
59}
60
61impl Default for VimLineEditor {
62 fn default() -> Self {
63 Self::new()
64 }
65}
66
67impl VimLineEditor {
68 pub fn new() -> Self {
70 Self {
71 cursor: 0,
72 mode: Mode::Normal,
73 visual_anchor: None,
74 yank_buffer: String::new(),
75 command_mode_enabled: false,
76 command_buf: String::new(),
77 command_cursor: 0,
78 }
79 }
80
81 pub fn with_command_mode(mut self, enabled: bool) -> Self {
84 self.command_mode_enabled = enabled;
85 self
86 }
87
88 pub fn command_line_buffer(&self) -> Option<&str> {
92 if self.mode == Mode::CommandLine {
93 Some(&self.command_buf)
94 } else {
95 None
96 }
97 }
98
99 pub fn command_line_cursor(&self) -> usize {
102 self.command_cursor
103 }
104
105 pub fn set_command_line(&mut self, buf: String, cursor: usize) {
109 if self.mode != Mode::CommandLine {
110 return;
111 }
112 let cursor = cursor.min(buf.len());
113 let cursor = if buf.is_char_boundary(cursor) {
116 cursor
117 } else {
118 let mut p = cursor;
119 while p > 0 && !buf.is_char_boundary(p) {
120 p -= 1;
121 }
122 p
123 };
124 self.command_buf = buf;
125 self.command_cursor = cursor;
126 }
127
128 #[cfg(test)]
130 fn mode(&self) -> Mode {
131 self.mode
132 }
133
134 fn clamp_cursor(&mut self, text: &str) {
136 self.cursor = self.cursor.min(text.len());
137 }
138
139 fn move_left(&mut self, text: &str) {
141 self.cursor = motions::move_left(self.cursor, text);
142 }
143
144 fn move_right(&mut self, text: &str) {
146 self.cursor = motions::move_right(self.cursor, text);
147 }
148
149 fn move_line_start(&mut self, text: &str) {
151 self.cursor = motions::move_line_start(self.cursor, text);
152 }
153
154 fn move_first_non_blank(&mut self, text: &str) {
156 self.cursor = motions::move_first_non_blank(self.cursor, text);
157 }
158
159 fn move_line_end(&mut self, text: &str) {
161 self.cursor = motions::move_line_end(self.cursor, text);
162 }
163
164 fn move_line_end_insert(&mut self, text: &str) {
166 self.cursor = motions::move_line_end_insert(self.cursor, text);
167 }
168
169 fn move_word_forward(&mut self, text: &str) {
171 self.cursor = motions::move_word_forward(self.cursor, text);
172 }
173
174 fn move_word_backward(&mut self, text: &str) {
176 self.cursor = motions::move_word_backward(self.cursor, text);
177 }
178
179 fn move_word_end(&mut self, text: &str) {
181 self.cursor = motions::move_word_end(self.cursor, text);
182 }
183
184 fn move_up(&mut self, text: &str) {
186 self.cursor = motions::move_up(self.cursor, text);
187 }
188
189 fn move_down(&mut self, text: &str) {
191 self.cursor = motions::move_down(self.cursor, text);
192 }
193
194 fn move_to_matching_bracket(&mut self, text: &str) {
197 self.cursor = motions::move_to_matching_bracket(self.cursor, text);
198 }
199
200 fn dispatch_motion(&mut self, code: KeyCode, text: &str) -> bool {
212 match code {
213 KeyCode::Char('h') | KeyCode::Left => self.move_left(text),
214 KeyCode::Char('l') | KeyCode::Right => self.move_right(text),
215 KeyCode::Char('j') => self.move_down(text),
216 KeyCode::Char('k') => self.move_up(text),
217 KeyCode::Char('0') | KeyCode::Home => self.move_line_start(text),
218 KeyCode::Char('^') => self.move_first_non_blank(text),
219 KeyCode::Char('$') | KeyCode::End => self.move_line_end(text),
220 KeyCode::Char('w') => self.move_word_forward(text),
221 KeyCode::Char('b') => self.move_word_backward(text),
222 KeyCode::Char('e') => self.move_word_end(text),
223 KeyCode::Char('%') => self.move_to_matching_bracket(text),
224 _ => return false,
225 }
226 true
227 }
228
229 fn handle_normal(&mut self, key: Key, text: &str) -> EditResult {
231 match key.code {
236 KeyCode::Char('k') if motions::is_on_first_line(self.cursor, text) => {
237 return EditResult::action(Action::HistoryPrev);
238 }
239 KeyCode::Char('j') if motions::is_on_last_line(self.cursor, text) => {
240 return EditResult::action(Action::HistoryNext);
241 }
242 _ => {}
243 }
244
245 if self.dispatch_motion(key.code, text) {
248 return EditResult::cursor_only();
249 }
250
251 match key.code {
252 KeyCode::Char('i') => {
254 self.mode = Mode::Insert;
255 EditResult::none()
256 }
257 KeyCode::Char('a') => {
258 self.mode = Mode::Insert;
259 self.move_right(text);
260 EditResult::none()
261 }
262 KeyCode::Char('A') => {
263 self.mode = Mode::Insert;
264 self.move_line_end_insert(text);
265 EditResult::none()
266 }
267 KeyCode::Char('I') => {
268 self.mode = Mode::Insert;
269 self.move_first_non_blank(text);
270 EditResult::none()
271 }
272 KeyCode::Char('o') => {
273 self.mode = Mode::Insert;
274 self.move_line_end(text);
275 let pos = self.cursor;
276 self.cursor = pos + 1;
277 EditResult::edit(TextEdit::Insert {
278 at: pos,
279 text: "\n".to_string(),
280 })
281 }
282 KeyCode::Char('O') => {
283 self.mode = Mode::Insert;
284 self.move_line_start(text);
285 let pos = self.cursor;
286 EditResult::edit(TextEdit::Insert {
287 at: pos,
288 text: "\n".to_string(),
289 })
290 }
291
292 KeyCode::Char('v') => {
294 self.mode = Mode::Visual;
295 self.visual_anchor = Some(self.cursor);
296 EditResult::none()
297 }
298
299 KeyCode::Char('c') if key.ctrl => EditResult::action(Action::Cancel),
301
302 KeyCode::Char('d') => {
304 self.mode = Mode::OperatorPending(Operator::Delete);
305 EditResult::none()
306 }
307 KeyCode::Char('c') => {
308 self.mode = Mode::OperatorPending(Operator::Change);
309 EditResult::none()
310 }
311 KeyCode::Char('y') => {
312 self.mode = Mode::OperatorPending(Operator::Yank);
313 EditResult::none()
314 }
315
316 KeyCode::Char('x') => self.delete_char(text),
318 KeyCode::Char('D') => self.delete_to_end(text),
319 KeyCode::Char('C') => {
320 self.mode = Mode::Insert;
321 self.delete_to_end(text)
322 }
323
324 KeyCode::Char('r') => {
326 self.mode = Mode::ReplaceChar;
327 EditResult::none()
328 }
329
330 KeyCode::Char(':') if self.command_mode_enabled => {
334 self.mode = Mode::CommandLine;
335 self.command_buf.clear();
336 self.command_cursor = 0;
337 EditResult::none()
338 }
339
340 KeyCode::Char('p') => self.paste_after(text),
342 KeyCode::Char('P') => self.paste_before(text),
343
344 KeyCode::Up => EditResult::action(Action::HistoryPrev),
346 KeyCode::Down => EditResult::action(Action::HistoryNext),
347
348 KeyCode::Enter if !key.shift => EditResult::action(Action::Submit),
350
351 KeyCode::Enter if key.shift => {
353 self.mode = Mode::Insert;
354 let pos = self.cursor;
355 self.cursor = pos + 1;
356 EditResult::edit(TextEdit::Insert {
357 at: pos,
358 text: "\n".to_string(),
359 })
360 }
361
362 KeyCode::Escape => EditResult::none(),
365
366 _ => EditResult::none(),
367 }
368 }
369
370 fn handle_insert(&mut self, key: Key, text: &str) -> EditResult {
372 match key.code {
373 KeyCode::Escape => {
374 self.mode = Mode::Normal;
375 if self.cursor > 0 {
377 self.move_left(text);
378 }
379 EditResult::none()
380 }
381
382 KeyCode::Char('c') if key.ctrl => {
384 self.mode = Mode::Normal;
385 EditResult::none()
386 }
387
388 KeyCode::Char(c) if !key.ctrl && !key.alt => {
389 let pos = self.cursor;
390 self.cursor = pos + c.len_utf8();
391 EditResult::edit(TextEdit::Insert {
392 at: pos,
393 text: c.to_string(),
394 })
395 }
396
397 KeyCode::Backspace => {
398 if self.cursor == 0 {
399 return EditResult::none();
400 }
401 let mut start = self.cursor - 1;
402 while start > 0 && !text.is_char_boundary(start) {
403 start -= 1;
404 }
405 let end = self.cursor; self.cursor = start;
407 EditResult::edit(TextEdit::Delete { start, end })
408 }
409
410 KeyCode::Delete => self.delete_char(text),
411
412 KeyCode::Left => {
413 self.move_left(text);
414 EditResult::cursor_only()
415 }
416 KeyCode::Right => {
417 self.move_right(text);
418 EditResult::cursor_only()
419 }
420 KeyCode::Up => {
421 self.move_up(text);
422 EditResult::cursor_only()
423 }
424 KeyCode::Down => {
425 self.move_down(text);
426 EditResult::cursor_only()
427 }
428 KeyCode::Home => {
429 self.move_line_start(text);
430 EditResult::cursor_only()
431 }
432 KeyCode::End => {
433 self.move_line_end_insert(text);
435 EditResult::cursor_only()
436 }
437
438 KeyCode::Enter => {
440 let pos = self.cursor;
441 self.cursor = pos + 1;
442 EditResult::edit(TextEdit::Insert {
443 at: pos,
444 text: "\n".to_string(),
445 })
446 }
447
448 _ => EditResult::none(),
449 }
450 }
451
452 fn handle_operator_pending(&mut self, op: Operator, key: Key, text: &str) -> EditResult {
454 if key.code == KeyCode::Escape {
456 self.mode = Mode::Normal;
457 return EditResult::none();
458 }
459
460 let is_line_op = matches!(
462 (op, key.code),
463 (Operator::Delete, KeyCode::Char('d'))
464 | (Operator::Change, KeyCode::Char('c'))
465 | (Operator::Yank, KeyCode::Char('y'))
466 );
467
468 if is_line_op {
469 self.mode = Mode::Normal;
470 return self.apply_operator_line(op, text);
471 }
472
473 let start = self.cursor;
475 match key.code {
476 KeyCode::Char('w') => {
477 if op == Operator::Change {
480 self.move_word_end(text);
481 if self.cursor < text.len() {
483 self.cursor += 1;
484 }
485 } else {
486 self.move_word_forward(text);
487 }
488 }
489 KeyCode::Char('b') => self.move_word_backward(text),
490 KeyCode::Char('e') => {
491 self.move_word_end(text);
492 if self.cursor < text.len() {
494 self.cursor += 1;
495 }
496 }
497 KeyCode::Char('0') | KeyCode::Home => self.move_line_start(text),
498 KeyCode::Char('$') | KeyCode::End => self.move_line_end(text),
499 KeyCode::Char('^') => self.move_first_non_blank(text),
500 KeyCode::Char('h') | KeyCode::Left => self.move_left(text),
501 KeyCode::Char('l') | KeyCode::Right => self.move_right(text),
502 KeyCode::Char('j') => self.move_down(text),
503 KeyCode::Char('k') => self.move_up(text),
504 _ => {
505 self.mode = Mode::Normal;
507 return EditResult::none();
508 }
509 }
510
511 let end = self.cursor;
512 self.mode = Mode::Normal;
513
514 if start == end {
515 return EditResult::none();
516 }
517
518 let (range_start, range_end) = if start < end {
519 (start, end)
520 } else {
521 (end, start)
522 };
523
524 self.apply_operator(op, range_start, range_end, text)
525 }
526
527 fn handle_visual(&mut self, key: Key, text: &str) -> EditResult {
529 match key.code {
530 KeyCode::Escape => {
531 self.mode = Mode::Normal;
532 self.visual_anchor = None;
533 EditResult::none()
534 }
535
536 KeyCode::Char('h') | KeyCode::Left => {
540 self.move_left(text);
541 EditResult::cursor_only()
542 }
543 KeyCode::Char('l') | KeyCode::Right => {
544 self.move_right(text);
545 EditResult::cursor_only()
546 }
547 KeyCode::Char('j') => {
548 self.move_down(text);
549 EditResult::cursor_only()
550 }
551 KeyCode::Char('k') => {
552 self.move_up(text);
553 EditResult::cursor_only()
554 }
555 KeyCode::Char('w') => {
556 self.move_word_forward(text);
557 EditResult::cursor_only()
558 }
559 KeyCode::Char('b') => {
560 self.move_word_backward(text);
561 EditResult::cursor_only()
562 }
563 KeyCode::Char('e') => {
564 self.move_word_end(text);
565 EditResult::cursor_only()
566 }
567 KeyCode::Char('0') | KeyCode::Home => {
568 self.move_line_start(text);
569 EditResult::cursor_only()
570 }
571 KeyCode::Char('$') | KeyCode::End => {
572 self.move_line_end(text);
573 EditResult::cursor_only()
574 }
575
576 KeyCode::Char('d') | KeyCode::Char('x') => {
578 let (start, end) = self.selection_range();
579 self.mode = Mode::Normal;
580 self.visual_anchor = None;
581 self.apply_operator(Operator::Delete, start, end, text)
582 }
583 KeyCode::Char('c') => {
584 let (start, end) = self.selection_range();
585 self.mode = Mode::Normal;
586 self.visual_anchor = None;
587 self.apply_operator(Operator::Change, start, end, text)
588 }
589 KeyCode::Char('y') => {
590 let (start, end) = self.selection_range();
591 self.mode = Mode::Normal;
592 self.visual_anchor = None;
593 self.apply_operator(Operator::Yank, start, end, text)
594 }
595
596 _ => EditResult::none(),
597 }
598 }
599
600 fn handle_replace_char(&mut self, key: Key, text: &str) -> EditResult {
602 self.mode = Mode::Normal;
603
604 match key.code {
605 KeyCode::Escape => EditResult::none(),
606 KeyCode::Char(c) if !key.ctrl && !key.alt => {
607 if self.cursor >= text.len() {
609 return EditResult::none();
610 }
611
612 let mut end = self.cursor + 1;
614 while end < text.len() && !text.is_char_boundary(end) {
615 end += 1;
616 }
617
618 EditResult {
621 edits: vec![
622 TextEdit::Insert {
623 at: self.cursor,
624 text: c.to_string(),
625 },
626 TextEdit::Delete {
627 start: self.cursor,
628 end,
629 },
630 ],
631 ..Default::default()
632 }
633 }
634 _ => EditResult::none(),
635 }
636 }
637
638 fn selection_range(&self) -> (usize, usize) {
640 let anchor = self.visual_anchor.unwrap_or(self.cursor);
641 if self.cursor < anchor {
642 (self.cursor, anchor)
643 } else {
644 (anchor, self.cursor + 1) }
646 }
647}
648
649impl LineEditor for VimLineEditor {
650 fn handle_key(&mut self, key: Key, text: &str) -> EditResult {
651 self.clamp_cursor(text);
652
653 let result = match self.mode {
654 Mode::Normal => self.handle_normal(key, text),
655 Mode::Insert => self.handle_insert(key, text),
656 Mode::OperatorPending(op) => self.handle_operator_pending(op, key, text),
657 Mode::Visual => self.handle_visual(key, text),
658 Mode::ReplaceChar => self.handle_replace_char(key, text),
659 Mode::CommandLine => self.handle_command_line(key),
660 };
661
662 if let Some(ref yanked) = result.yanked {
664 self.yank_buffer = yanked.clone();
665 }
666
667 result
668 }
669
670 fn cursor(&self) -> usize {
671 self.cursor
672 }
673
674 fn status(&self) -> &str {
675 match self.mode {
676 Mode::Normal => "NORMAL",
677 Mode::Insert => "INSERT",
678 Mode::OperatorPending(Operator::Delete) => "d...",
679 Mode::OperatorPending(Operator::Change) => "c...",
680 Mode::OperatorPending(Operator::Yank) => "y...",
681 Mode::Visual => "VISUAL",
682 Mode::ReplaceChar => "r...",
683 Mode::CommandLine => "COMMAND",
684 }
685 }
686
687 fn selection(&self) -> Option<Range<usize>> {
688 if self.mode == Mode::Visual {
689 let (start, end) = self.selection_range();
690 Some(start..end)
691 } else {
692 None
693 }
694 }
695
696 fn reset(&mut self) {
697 self.cursor = 0;
698 self.mode = Mode::Normal;
699 self.visual_anchor = None;
700 self.command_buf.clear();
701 self.command_cursor = 0;
702 }
704
705 fn set_cursor(&mut self, pos: usize, text: &str) {
706 let pos = pos.min(text.len());
708 self.cursor = if text.is_char_boundary(pos) {
709 pos
710 } else {
711 let mut p = pos;
713 while p > 0 && !text.is_char_boundary(p) {
714 p -= 1;
715 }
716 p
717 };
718 }
719}
720
721#[cfg(test)]
722mod tests;