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 if self.dispatch_motion(key.code, text) {
234 return EditResult::cursor_only();
235 }
236
237 match key.code {
238 KeyCode::Char('i') => {
240 self.mode = Mode::Insert;
241 EditResult::none()
242 }
243 KeyCode::Char('a') => {
244 self.mode = Mode::Insert;
245 self.move_right(text);
246 EditResult::none()
247 }
248 KeyCode::Char('A') => {
249 self.mode = Mode::Insert;
250 self.move_line_end_insert(text);
251 EditResult::none()
252 }
253 KeyCode::Char('I') => {
254 self.mode = Mode::Insert;
255 self.move_first_non_blank(text);
256 EditResult::none()
257 }
258 KeyCode::Char('o') => {
259 self.mode = Mode::Insert;
260 self.move_line_end(text);
261 let pos = self.cursor;
262 self.cursor = pos + 1;
263 EditResult::edit(TextEdit::Insert {
264 at: pos,
265 text: "\n".to_string(),
266 })
267 }
268 KeyCode::Char('O') => {
269 self.mode = Mode::Insert;
270 self.move_line_start(text);
271 let pos = self.cursor;
272 EditResult::edit(TextEdit::Insert {
273 at: pos,
274 text: "\n".to_string(),
275 })
276 }
277
278 KeyCode::Char('v') => {
280 self.mode = Mode::Visual;
281 self.visual_anchor = Some(self.cursor);
282 EditResult::none()
283 }
284
285 KeyCode::Char('c') if key.ctrl => EditResult::action(Action::Cancel),
287
288 KeyCode::Char('d') => {
290 self.mode = Mode::OperatorPending(Operator::Delete);
291 EditResult::none()
292 }
293 KeyCode::Char('c') => {
294 self.mode = Mode::OperatorPending(Operator::Change);
295 EditResult::none()
296 }
297 KeyCode::Char('y') => {
298 self.mode = Mode::OperatorPending(Operator::Yank);
299 EditResult::none()
300 }
301
302 KeyCode::Char('x') => self.delete_char(text),
304 KeyCode::Char('D') => self.delete_to_end(text),
305 KeyCode::Char('C') => {
306 self.mode = Mode::Insert;
307 self.delete_to_end(text)
308 }
309
310 KeyCode::Char('r') => {
312 self.mode = Mode::ReplaceChar;
313 EditResult::none()
314 }
315
316 KeyCode::Char(':') if self.command_mode_enabled => {
320 self.mode = Mode::CommandLine;
321 self.command_buf.clear();
322 self.command_cursor = 0;
323 EditResult::none()
324 }
325
326 KeyCode::Char('p') => self.paste_after(text),
328 KeyCode::Char('P') => self.paste_before(text),
329
330 KeyCode::Up => EditResult::action(Action::HistoryPrev),
332 KeyCode::Down => EditResult::action(Action::HistoryNext),
333
334 KeyCode::Enter if !key.shift => EditResult::action(Action::Submit),
336
337 KeyCode::Enter if key.shift => {
339 self.mode = Mode::Insert;
340 let pos = self.cursor;
341 self.cursor = pos + 1;
342 EditResult::edit(TextEdit::Insert {
343 at: pos,
344 text: "\n".to_string(),
345 })
346 }
347
348 KeyCode::Escape => EditResult::none(),
351
352 _ => EditResult::none(),
353 }
354 }
355
356 fn handle_insert(&mut self, key: Key, text: &str) -> EditResult {
358 match key.code {
359 KeyCode::Escape => {
360 self.mode = Mode::Normal;
361 if self.cursor > 0 {
363 self.move_left(text);
364 }
365 EditResult::none()
366 }
367
368 KeyCode::Char('c') if key.ctrl => {
370 self.mode = Mode::Normal;
371 EditResult::none()
372 }
373
374 KeyCode::Char(c) if !key.ctrl && !key.alt => {
375 let pos = self.cursor;
376 self.cursor = pos + c.len_utf8();
377 EditResult::edit(TextEdit::Insert {
378 at: pos,
379 text: c.to_string(),
380 })
381 }
382
383 KeyCode::Backspace => {
384 if self.cursor == 0 {
385 return EditResult::none();
386 }
387 let mut start = self.cursor - 1;
388 while start > 0 && !text.is_char_boundary(start) {
389 start -= 1;
390 }
391 let end = self.cursor; self.cursor = start;
393 EditResult::edit(TextEdit::Delete { start, end })
394 }
395
396 KeyCode::Delete => self.delete_char(text),
397
398 KeyCode::Left => {
399 self.move_left(text);
400 EditResult::cursor_only()
401 }
402 KeyCode::Right => {
403 self.move_right(text);
404 EditResult::cursor_only()
405 }
406 KeyCode::Up => {
407 self.move_up(text);
408 EditResult::cursor_only()
409 }
410 KeyCode::Down => {
411 self.move_down(text);
412 EditResult::cursor_only()
413 }
414 KeyCode::Home => {
415 self.move_line_start(text);
416 EditResult::cursor_only()
417 }
418 KeyCode::End => {
419 self.move_line_end_insert(text);
421 EditResult::cursor_only()
422 }
423
424 KeyCode::Enter => {
426 let pos = self.cursor;
427 self.cursor = pos + 1;
428 EditResult::edit(TextEdit::Insert {
429 at: pos,
430 text: "\n".to_string(),
431 })
432 }
433
434 _ => EditResult::none(),
435 }
436 }
437
438 fn handle_operator_pending(&mut self, op: Operator, key: Key, text: &str) -> EditResult {
440 if key.code == KeyCode::Escape {
442 self.mode = Mode::Normal;
443 return EditResult::none();
444 }
445
446 let is_line_op = matches!(
448 (op, key.code),
449 (Operator::Delete, KeyCode::Char('d'))
450 | (Operator::Change, KeyCode::Char('c'))
451 | (Operator::Yank, KeyCode::Char('y'))
452 );
453
454 if is_line_op {
455 self.mode = Mode::Normal;
456 return self.apply_operator_line(op, text);
457 }
458
459 let start = self.cursor;
461 match key.code {
462 KeyCode::Char('w') => {
463 if op == Operator::Change {
466 self.move_word_end(text);
467 if self.cursor < text.len() {
469 self.cursor += 1;
470 }
471 } else {
472 self.move_word_forward(text);
473 }
474 }
475 KeyCode::Char('b') => self.move_word_backward(text),
476 KeyCode::Char('e') => {
477 self.move_word_end(text);
478 if self.cursor < text.len() {
480 self.cursor += 1;
481 }
482 }
483 KeyCode::Char('0') | KeyCode::Home => self.move_line_start(text),
484 KeyCode::Char('$') | KeyCode::End => self.move_line_end(text),
485 KeyCode::Char('^') => self.move_first_non_blank(text),
486 KeyCode::Char('h') | KeyCode::Left => self.move_left(text),
487 KeyCode::Char('l') | KeyCode::Right => self.move_right(text),
488 KeyCode::Char('j') => self.move_down(text),
489 KeyCode::Char('k') => self.move_up(text),
490 _ => {
491 self.mode = Mode::Normal;
493 return EditResult::none();
494 }
495 }
496
497 let end = self.cursor;
498 self.mode = Mode::Normal;
499
500 if start == end {
501 return EditResult::none();
502 }
503
504 let (range_start, range_end) = if start < end {
505 (start, end)
506 } else {
507 (end, start)
508 };
509
510 self.apply_operator(op, range_start, range_end, text)
511 }
512
513 fn handle_visual(&mut self, key: Key, text: &str) -> EditResult {
515 match key.code {
516 KeyCode::Escape => {
517 self.mode = Mode::Normal;
518 self.visual_anchor = None;
519 EditResult::none()
520 }
521
522 KeyCode::Char('h') | KeyCode::Left => {
526 self.move_left(text);
527 EditResult::cursor_only()
528 }
529 KeyCode::Char('l') | KeyCode::Right => {
530 self.move_right(text);
531 EditResult::cursor_only()
532 }
533 KeyCode::Char('j') => {
534 self.move_down(text);
535 EditResult::cursor_only()
536 }
537 KeyCode::Char('k') => {
538 self.move_up(text);
539 EditResult::cursor_only()
540 }
541 KeyCode::Char('w') => {
542 self.move_word_forward(text);
543 EditResult::cursor_only()
544 }
545 KeyCode::Char('b') => {
546 self.move_word_backward(text);
547 EditResult::cursor_only()
548 }
549 KeyCode::Char('e') => {
550 self.move_word_end(text);
551 EditResult::cursor_only()
552 }
553 KeyCode::Char('0') | KeyCode::Home => {
554 self.move_line_start(text);
555 EditResult::cursor_only()
556 }
557 KeyCode::Char('$') | KeyCode::End => {
558 self.move_line_end(text);
559 EditResult::cursor_only()
560 }
561
562 KeyCode::Char('d') | KeyCode::Char('x') => {
564 let (start, end) = self.selection_range();
565 self.mode = Mode::Normal;
566 self.visual_anchor = None;
567 self.apply_operator(Operator::Delete, start, end, text)
568 }
569 KeyCode::Char('c') => {
570 let (start, end) = self.selection_range();
571 self.mode = Mode::Normal;
572 self.visual_anchor = None;
573 self.apply_operator(Operator::Change, start, end, text)
574 }
575 KeyCode::Char('y') => {
576 let (start, end) = self.selection_range();
577 self.mode = Mode::Normal;
578 self.visual_anchor = None;
579 self.apply_operator(Operator::Yank, start, end, text)
580 }
581
582 _ => EditResult::none(),
583 }
584 }
585
586 fn handle_replace_char(&mut self, key: Key, text: &str) -> EditResult {
588 self.mode = Mode::Normal;
589
590 match key.code {
591 KeyCode::Escape => EditResult::none(),
592 KeyCode::Char(c) if !key.ctrl && !key.alt => {
593 if self.cursor >= text.len() {
595 return EditResult::none();
596 }
597
598 let mut end = self.cursor + 1;
600 while end < text.len() && !text.is_char_boundary(end) {
601 end += 1;
602 }
603
604 EditResult {
607 edits: vec![
608 TextEdit::Insert {
609 at: self.cursor,
610 text: c.to_string(),
611 },
612 TextEdit::Delete {
613 start: self.cursor,
614 end,
615 },
616 ],
617 ..Default::default()
618 }
619 }
620 _ => EditResult::none(),
621 }
622 }
623
624 fn selection_range(&self) -> (usize, usize) {
626 let anchor = self.visual_anchor.unwrap_or(self.cursor);
627 if self.cursor < anchor {
628 (self.cursor, anchor)
629 } else {
630 (anchor, self.cursor + 1) }
632 }
633}
634
635impl LineEditor for VimLineEditor {
636 fn handle_key(&mut self, key: Key, text: &str) -> EditResult {
637 self.clamp_cursor(text);
638
639 let result = match self.mode {
640 Mode::Normal => self.handle_normal(key, text),
641 Mode::Insert => self.handle_insert(key, text),
642 Mode::OperatorPending(op) => self.handle_operator_pending(op, key, text),
643 Mode::Visual => self.handle_visual(key, text),
644 Mode::ReplaceChar => self.handle_replace_char(key, text),
645 Mode::CommandLine => self.handle_command_line(key),
646 };
647
648 if let Some(ref yanked) = result.yanked {
650 self.yank_buffer = yanked.clone();
651 }
652
653 result
654 }
655
656 fn cursor(&self) -> usize {
657 self.cursor
658 }
659
660 fn status(&self) -> &str {
661 match self.mode {
662 Mode::Normal => "NORMAL",
663 Mode::Insert => "INSERT",
664 Mode::OperatorPending(Operator::Delete) => "d...",
665 Mode::OperatorPending(Operator::Change) => "c...",
666 Mode::OperatorPending(Operator::Yank) => "y...",
667 Mode::Visual => "VISUAL",
668 Mode::ReplaceChar => "r...",
669 Mode::CommandLine => "COMMAND",
670 }
671 }
672
673 fn selection(&self) -> Option<Range<usize>> {
674 if self.mode == Mode::Visual {
675 let (start, end) = self.selection_range();
676 Some(start..end)
677 } else {
678 None
679 }
680 }
681
682 fn reset(&mut self) {
683 self.cursor = 0;
684 self.mode = Mode::Normal;
685 self.visual_anchor = None;
686 self.command_buf.clear();
687 self.command_cursor = 0;
688 }
690
691 fn set_cursor(&mut self, pos: usize, text: &str) {
692 let pos = pos.min(text.len());
694 self.cursor = if text.is_char_boundary(pos) {
695 pos
696 } else {
697 let mut p = pos;
699 while p > 0 && !text.is_char_boundary(p) {
700 p -= 1;
701 }
702 p
703 };
704 }
705}
706
707#[cfg(test)]
708mod tests;