1use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
2
3use crate::text::{
4 next_char_boundary, vim_current_line_bounds, vim_current_line_full_range, vim_end_word,
5 vim_find_char, vim_is_linewise_range, vim_line_end, vim_line_first_non_ws, vim_line_start,
6 vim_motion_range, vim_next_word_start, vim_prev_word_start, vim_text_object_range,
7};
8use crate::types::{
9 ChangeTarget, ClipboardKind, FindState, InsertCapture, InsertKind, InsertRepeat, Motion,
10 Operator, PendingState, RepeatableCommand, TextObjectSpec, VimMode, VimState,
11};
12
13const INDENT: &str = " ";
14
15pub trait Editor {
17 fn content(&self) -> &str;
18 fn cursor(&self) -> usize;
19 fn set_cursor(&mut self, pos: usize);
20 fn move_left(&mut self);
21 fn move_right(&mut self);
22 fn delete_char_forward(&mut self);
23 fn insert_text(&mut self, text: &str);
24 fn replace(&mut self, content: String, cursor: usize);
25
26 fn replace_range(&mut self, start: usize, end: usize, text: &str) {
29 let mut content = self.content().to_string();
30 content.replace_range(start..end, text);
31 let cursor = start + text.len();
32 self.replace(content, cursor);
33 }
34}
35
36#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
38pub struct HandleKeyOutcome {
39 pub handled: bool,
40 pub clear_selection: bool,
41}
42
43#[must_use]
45pub fn handle_key<E: Editor>(
46 state: &mut VimState,
47 editor: &mut E,
48 clipboard: &mut String,
49 key: &KeyEvent,
50) -> HandleKeyOutcome {
51 if !state.enabled() {
52 return HandleKeyOutcome::default();
53 }
54
55 if key
56 .modifiers
57 .intersects(KeyModifiers::CONTROL | KeyModifiers::ALT | KeyModifiers::SUPER)
58 {
59 return HandleKeyOutcome::default();
60 }
61
62 let mut ctx = VimContext {
63 state,
64 editor,
65 clipboard,
66 };
67 ctx.handle_key(key)
68}
69
70struct VimContext<'a, E> {
71 state: &'a mut VimState,
72 editor: &'a mut E,
73 clipboard: &'a mut String,
74}
75
76impl<E: Editor> VimContext<'_, E> {
77 fn handle_key(&mut self, key: &KeyEvent) -> HandleKeyOutcome {
78 match self.state.mode() {
79 VimMode::Insert => self.handle_insert_key(key),
80 VimMode::Normal => self.handle_normal_key(key),
81 }
82 }
83
84 fn handle_insert_key(&mut self, key: &KeyEvent) -> HandleKeyOutcome {
85 if matches!(key.code, KeyCode::Esc) {
86 self.finish_insert_capture();
87 self.state.set_mode(VimMode::Normal);
88 self.state.pending = None;
89 return HandleKeyOutcome {
90 handled: true,
91 clear_selection: true,
92 };
93 }
94 HandleKeyOutcome::default()
95 }
96
97 fn handle_normal_key(&mut self, key: &KeyEvent) -> HandleKeyOutcome {
98 let handled = match key.code {
99 KeyCode::Esc => {
100 self.state.pending = None;
101 return HandleKeyOutcome {
102 handled: true,
103 clear_selection: true,
104 };
105 }
106 KeyCode::Char(ch) => {
107 if let Some(pending) = self.state.pending.take() {
108 self.handle_pending(pending, ch)
109 } else {
110 self.handle_normal_char(ch)
111 }
112 }
113 KeyCode::Left => {
114 self.editor.move_left();
115 self.state.preferred_column = None;
116 true
117 }
118 KeyCode::Right => {
119 self.editor.move_right();
120 self.state.preferred_column = None;
121 true
122 }
123 KeyCode::Up => {
124 self.move_vertical(false);
125 true
126 }
127 KeyCode::Down => {
128 self.move_vertical(true);
129 true
130 }
131 _ => false,
132 };
133
134 HandleKeyOutcome {
135 handled,
136 clear_selection: false,
137 }
138 }
139
140 fn handle_pending(&mut self, pending: PendingState, ch: char) -> bool {
141 match pending {
142 PendingState::Operator(operator) => self.handle_operator(operator, ch),
143 PendingState::TextObject(operator, around) => {
144 self.handle_text_object(operator, around, ch)
145 }
146 PendingState::Find { till, forward } => self.handle_find(forward, till, ch),
147 PendingState::GoToLine => {
148 if ch == 'g' {
149 self.move_to_line(true);
150 true
151 } else {
152 false
153 }
154 }
155 }
156 }
157
158 fn handle_normal_char(&mut self, ch: char) -> bool {
159 match ch {
160 'h' => {
161 self.editor.move_left();
162 self.state.preferred_column = None;
163 true
164 }
165 'l' => {
166 self.editor.move_right();
167 self.state.preferred_column = None;
168 true
169 }
170 'j' => {
171 self.move_vertical(true);
172 true
173 }
174 'k' => {
175 self.move_vertical(false);
176 true
177 }
178 'w' => {
179 self.move_motion(Motion::WordForward);
180 true
181 }
182 'e' => {
183 self.move_motion(Motion::EndWord);
184 true
185 }
186 'b' => {
187 self.move_motion(Motion::WordBackward);
188 true
189 }
190 '0' => {
191 self.editor
192 .set_cursor(vim_line_start(self.editor.content(), self.editor.cursor()));
193 self.state.preferred_column = None;
194 true
195 }
196 '^' => {
197 self.editor.set_cursor(vim_line_first_non_ws(
198 self.editor.content(),
199 self.editor.cursor(),
200 ));
201 self.state.preferred_column = None;
202 true
203 }
204 '$' => {
205 self.editor
206 .set_cursor(vim_line_end(self.editor.content(), self.editor.cursor()));
207 self.state.preferred_column = None;
208 true
209 }
210 'g' => {
211 self.state.pending = Some(PendingState::GoToLine);
212 true
213 }
214 'G' => {
215 self.move_to_line(false);
216 true
217 }
218 'f' => {
219 self.state.pending = Some(PendingState::Find {
220 till: false,
221 forward: true,
222 });
223 true
224 }
225 'F' => {
226 self.state.pending = Some(PendingState::Find {
227 till: false,
228 forward: false,
229 });
230 true
231 }
232 't' => {
233 self.state.pending = Some(PendingState::Find {
234 till: true,
235 forward: true,
236 });
237 true
238 }
239 'T' => {
240 self.state.pending = Some(PendingState::Find {
241 till: true,
242 forward: false,
243 });
244 true
245 }
246 ';' => self.repeat_find(false),
247 ',' => self.repeat_find(true),
248 'x' => {
249 if self.editor.cursor() < self.editor.content().len() {
250 self.editor.delete_char_forward();
251 self.state.last_change = Some(RepeatableCommand::DeleteChar);
252 }
253 true
254 }
255 'd' => {
256 self.state.pending = Some(PendingState::Operator(Operator::Delete));
257 true
258 }
259 'c' => {
260 self.state.pending = Some(PendingState::Operator(Operator::Change));
261 true
262 }
263 'y' => {
264 self.state.pending = Some(PendingState::Operator(Operator::Yank));
265 true
266 }
267 '>' => {
268 self.state.pending = Some(PendingState::Operator(Operator::Indent));
269 true
270 }
271 '<' => {
272 self.state.pending = Some(PendingState::Operator(Operator::Outdent));
273 true
274 }
275 'D' => {
276 self.delete_to_line_end();
277 self.state.last_change = Some(RepeatableCommand::DeleteToLineEnd);
278 true
279 }
280 'C' => {
281 self.change_to_line_end();
282 true
283 }
284 'Y' => {
285 self.yank_current_line();
286 true
287 }
288 'p' => {
289 self.paste(true);
290 self.state.last_change = Some(RepeatableCommand::PasteAfter);
291 true
292 }
293 'P' => {
294 self.paste(false);
295 self.state.last_change = Some(RepeatableCommand::PasteBefore);
296 true
297 }
298 'J' => {
299 self.join_lines();
300 self.state.last_change = Some(RepeatableCommand::JoinLines);
301 true
302 }
303 '.' => {
304 self.repeat_last_change();
305 true
306 }
307 'i' => {
308 self.start_insert(InsertKind::Insert);
309 true
310 }
311 'I' => {
312 let start = vim_line_first_non_ws(self.editor.content(), self.editor.cursor());
313 self.editor.set_cursor(start);
314 self.start_insert(InsertKind::InsertStart);
315 true
316 }
317 'a' => {
318 if self.editor.cursor() < self.editor.content().len() {
319 self.editor.move_right();
320 }
321 self.start_insert(InsertKind::Append);
322 true
323 }
324 'A' => {
325 let end = vim_line_end(self.editor.content(), self.editor.cursor());
326 self.editor.set_cursor(end);
327 self.start_insert(InsertKind::AppendEnd);
328 true
329 }
330 'o' => {
331 self.open_line(true);
332 self.start_insert(InsertKind::OpenBelow);
333 true
334 }
335 'O' => {
336 self.open_line(false);
337 self.start_insert(InsertKind::OpenAbove);
338 true
339 }
340 _ => false,
341 }
342 }
343
344 fn handle_operator(&mut self, operator: Operator, ch: char) -> bool {
345 match (operator, ch) {
346 (Operator::Delete, 'd') | (Operator::Change, 'c') | (Operator::Yank, 'y') => {
347 self.apply_line_operator(operator);
348 true
349 }
350 (Operator::Indent, '>') | (Operator::Outdent, '<') => {
351 self.apply_line_operator(operator);
352 true
353 }
354 (_, 'w') => self.apply_motion_operator(operator, Motion::WordForward),
355 (_, 'e') => self.apply_motion_operator(operator, Motion::EndWord),
356 (_, 'b') => self.apply_motion_operator(operator, Motion::WordBackward),
357 (_, 'i') => {
358 self.state.pending = Some(PendingState::TextObject(operator, false));
359 true
360 }
361 (_, 'a') => {
362 self.state.pending = Some(PendingState::TextObject(operator, true));
363 true
364 }
365 _ => false,
366 }
367 }
368
369 fn handle_text_object(&mut self, operator: Operator, around: bool, ch: char) -> bool {
370 let object = match ch {
371 'w' => TextObjectSpec::Word { around, big: false },
372 'W' => TextObjectSpec::Word { around, big: true },
373 '"' => TextObjectSpec::Delimited {
374 around,
375 open: '"',
376 close: '"',
377 },
378 '\'' => TextObjectSpec::Delimited {
379 around,
380 open: '\'',
381 close: '\'',
382 },
383 '(' => TextObjectSpec::Delimited {
384 around,
385 open: '(',
386 close: ')',
387 },
388 '[' => TextObjectSpec::Delimited {
389 around,
390 open: '[',
391 close: ']',
392 },
393 '{' => TextObjectSpec::Delimited {
394 around,
395 open: '{',
396 close: '}',
397 },
398 _ => return false,
399 };
400
401 let handled = self.apply_text_object_operator(operator, object);
402 if handled {
403 self.state.last_change = match operator {
404 Operator::Delete | Operator::Indent | Operator::Outdent => {
405 Some(RepeatableCommand::OperateTextObject { operator, object })
406 }
407 Operator::Change | Operator::Yank => None,
408 };
409 }
410 handled
411 }
412
413 fn handle_find(&mut self, forward: bool, till: bool, ch: char) -> bool {
414 if let Some(pos) = vim_find_char(
415 self.editor.content(),
416 self.editor.cursor(),
417 ch,
418 forward,
419 till,
420 ) {
421 self.editor.set_cursor(pos);
422 self.state.last_find = Some(FindState { ch, till, forward });
423 self.state.preferred_column = None;
424 }
425 true
426 }
427
428 fn repeat_find(&mut self, reverse: bool) -> bool {
429 let Some(find) = self.state.last_find else {
430 return true;
431 };
432 let forward = if reverse { !find.forward } else { find.forward };
433 self.handle_find(forward, find.till, find.ch)
434 }
435
436 fn start_insert(&mut self, kind: InsertKind) {
437 self.state.set_mode(VimMode::Insert);
438 self.state.pending = None;
439 self.state.insert_capture = Some(InsertCapture {
440 repeat: InsertRepeat::Insert(kind),
441 start: self.editor.cursor(),
442 });
443 }
444
445 fn finish_insert_capture(&mut self) {
446 let Some(capture) = self.state.insert_capture.take() else {
447 return;
448 };
449 let cursor = self.editor.cursor();
450 if cursor >= capture.start {
451 let inserted = self.editor.content()[capture.start..cursor].to_string();
452 self.state.last_change = match capture.repeat {
453 InsertRepeat::Insert(_) if inserted.is_empty() => None,
454 InsertRepeat::Insert(kind) => Some(RepeatableCommand::InsertText {
455 kind,
456 text: inserted,
457 }),
458 InsertRepeat::Change(target) => Some(RepeatableCommand::Change {
459 target,
460 text: inserted,
461 }),
462 };
463 }
464 }
465
466 fn begin_change(&mut self, start: usize, end: usize, target: ChangeTarget) {
467 self.capture_range(start, end);
468 self.replace_range(start, end, "");
469 self.state.set_mode(VimMode::Insert);
470 self.state.insert_capture = Some(InsertCapture {
471 repeat: InsertRepeat::Change(target),
472 start,
473 });
474 }
475
476 fn start_change(&mut self, target: ChangeTarget) -> bool {
477 match target {
478 ChangeTarget::Motion(motion) => {
479 let Some((start, end)) =
480 vim_motion_range(self.editor.content(), self.editor.cursor(), motion)
481 else {
482 return true;
483 };
484 self.begin_change(start, end, target);
485 true
486 }
487 ChangeTarget::TextObject(object) => {
488 let Some((start, end)) =
489 vim_text_object_range(self.editor.content(), self.editor.cursor(), object)
490 else {
491 return true;
492 };
493 self.begin_change(start, end, target);
494 true
495 }
496 ChangeTarget::Line => {
497 let (start, end) =
498 vim_current_line_bounds(self.editor.content(), self.editor.cursor());
499 self.begin_change(start, end, target);
500 true
501 }
502 ChangeTarget::LineEnd => {
503 let start = self.editor.cursor();
504 let end = vim_line_end(self.editor.content(), self.editor.cursor());
505 self.begin_change(start, end, target);
506 true
507 }
508 }
509 }
510
511 fn move_motion(&mut self, motion: Motion) {
512 let next = match motion {
513 Motion::WordForward => vim_next_word_start(self.editor.content(), self.editor.cursor()),
514 Motion::EndWord => vim_end_word(self.editor.content(), self.editor.cursor()),
515 Motion::WordBackward => {
516 vim_prev_word_start(self.editor.content(), self.editor.cursor())
517 }
518 };
519 self.editor.set_cursor(next);
520 self.state.preferred_column = None;
521 }
522
523 fn move_vertical(&mut self, down: bool) {
524 let content = self.editor.content();
525 let (line_start, line_end) = vim_current_line_bounds(content, self.editor.cursor());
526 let column = self
527 .state
528 .preferred_column
529 .unwrap_or_else(|| self.editor.cursor().saturating_sub(line_start));
530 let target = if down {
531 if line_end >= content.len() {
532 self.editor.cursor()
533 } else {
534 let next_start = line_end + 1;
535 let next_end = content[next_start..]
536 .find('\n')
537 .map(|idx| next_start + idx)
538 .unwrap_or(content.len());
539 (next_start + column).min(next_end)
540 }
541 } else if line_start == 0 {
542 self.editor.cursor()
543 } else {
544 let prev_end = line_start - 1;
545 let prev_start = content[..prev_end]
546 .rfind('\n')
547 .map(|idx| idx + 1)
548 .unwrap_or(0);
549 (prev_start + column).min(prev_end)
550 };
551 self.editor.set_cursor(target);
552 self.state.preferred_column = Some(column);
553 }
554
555 fn move_to_line(&mut self, first: bool) {
556 let content = self.editor.content();
557 let (current_start, _) = vim_current_line_bounds(content, self.editor.cursor());
558 let column = self
559 .state
560 .preferred_column
561 .unwrap_or_else(|| self.editor.cursor().saturating_sub(current_start));
562 let target = if first {
563 column.min(content.find('\n').unwrap_or(content.len()))
564 } else {
565 let last_start = content.rfind('\n').map(|idx| idx + 1).unwrap_or(0);
566 let last_end = content[last_start..]
567 .find('\n')
568 .map(|idx| last_start + idx)
569 .unwrap_or(content.len());
570 (last_start + column).min(last_end)
571 };
572 self.editor.set_cursor(target);
573 self.state.preferred_column = Some(column);
574 }
575
576 fn apply_motion_operator(&mut self, operator: Operator, motion: Motion) -> bool {
577 if operator == Operator::Change {
578 return self.start_change(ChangeTarget::Motion(motion));
579 }
580
581 let Some((start, end)) =
582 vim_motion_range(self.editor.content(), self.editor.cursor(), motion)
583 else {
584 return true;
585 };
586 self.apply_range_operator(operator, start, end);
587 if operator != Operator::Yank {
588 self.state.last_change = Some(RepeatableCommand::OperateMotion { operator, motion });
589 }
590 true
591 }
592
593 fn apply_text_object_operator(&mut self, operator: Operator, object: TextObjectSpec) -> bool {
594 if operator == Operator::Change {
595 return self.start_change(ChangeTarget::TextObject(object));
596 }
597
598 let Some((start, end)) =
599 vim_text_object_range(self.editor.content(), self.editor.cursor(), object)
600 else {
601 return true;
602 };
603 self.apply_range_operator(operator, start, end);
604 true
605 }
606
607 fn apply_line_operator(&mut self, operator: Operator) {
608 if operator == Operator::Change {
609 let _ = self.start_change(ChangeTarget::Line);
610 return;
611 }
612
613 let (start, end) = vim_current_line_full_range(self.editor.content(), self.editor.cursor());
614 self.apply_range_operator(operator, start, end);
615 if operator != Operator::Yank {
616 self.state.last_change = Some(RepeatableCommand::OperateLine { operator });
617 }
618 }
619
620 fn apply_range_operator(&mut self, operator: Operator, start: usize, end: usize) {
621 if start > end || end > self.editor.content().len() {
622 return;
623 }
624
625 match operator {
626 Operator::Yank => self.capture_range(start, end),
627 Operator::Delete => {
628 self.capture_range(start, end);
629 self.replace_range(start, end, "");
630 }
631 Operator::Indent => self.indent_range(start, end, true),
632 Operator::Outdent => self.indent_range(start, end, false),
633 Operator::Change => {}
634 }
635 }
636
637 fn indent_range(&mut self, start: usize, end: usize, indent: bool) {
638 let content = self.editor.content().to_string();
639 let mut out = String::with_capacity(content.len() + INDENT.len());
640 let mut line_start = 0;
641 for segment in content.split_inclusive('\n') {
642 let line_end = line_start + segment.len();
643 if line_end > start && line_start < end {
644 if indent {
645 out.push_str(INDENT);
646 out.push_str(segment);
647 } else if let Some(stripped) = segment.strip_prefix(INDENT) {
648 out.push_str(stripped);
649 } else {
650 out.push_str(segment.trim_start_matches(' '));
651 }
652 } else {
653 out.push_str(segment);
654 }
655 line_start = line_end;
656 }
657 if !content.ends_with('\n') && line_start < content.len() {
658 let tail = &content[line_start..];
659 if line_start < end && content.len() > start {
660 if indent {
661 out.push_str(INDENT);
662 out.push_str(tail);
663 } else if let Some(stripped) = tail.strip_prefix(INDENT) {
664 out.push_str(stripped);
665 } else {
666 out.push_str(tail.trim_start_matches(' '));
667 }
668 }
669 }
670 self.editor.replace(out, start.min(content.len()));
671 }
672
673 fn replace_range(&mut self, start: usize, end: usize, replacement: &str) {
674 self.editor.replace_range(start, end, replacement);
675 }
676
677 fn capture_range(&mut self, start: usize, end: usize) {
678 *self.clipboard = self.editor.content()[start..end].to_string();
679 self.state.clipboard_kind = if vim_is_linewise_range(self.editor.content(), start, end) {
680 ClipboardKind::LineWise
681 } else {
682 ClipboardKind::CharWise
683 };
684 }
685
686 fn delete_to_line_end(&mut self) {
687 let end = vim_line_end(self.editor.content(), self.editor.cursor());
688 self.capture_range(self.editor.cursor(), end);
689 self.replace_range(self.editor.cursor(), end, "");
690 }
691
692 fn change_to_line_end(&mut self) {
693 let _ = self.start_change(ChangeTarget::LineEnd);
694 }
695
696 fn yank_current_line(&mut self) {
697 let (start, end) = vim_current_line_full_range(self.editor.content(), self.editor.cursor());
698 self.capture_range(start, end);
699 }
700
701 fn open_line(&mut self, below: bool) {
702 let insert_at = if below {
703 let end = vim_line_end(self.editor.content(), self.editor.cursor());
704 if end < self.editor.content().len() {
705 end + 1
706 } else {
707 end
708 }
709 } else {
710 vim_line_start(self.editor.content(), self.editor.cursor())
711 };
712 self.editor.replace_range(insert_at, insert_at, "\n");
713 self.editor.set_cursor(insert_at + 1);
714 }
715
716 fn paste(&mut self, after: bool) {
717 if self.clipboard.is_empty() {
718 return;
719 }
720 match self.state.clipboard_kind {
721 ClipboardKind::CharWise => {
722 let insert_at = if after && self.editor.cursor() < self.editor.content().len() {
723 next_char_boundary(self.editor.content(), self.editor.cursor())
724 } else {
725 self.editor.cursor()
726 };
727 self.editor
728 .replace_range(insert_at, insert_at, self.clipboard);
729 self.editor.set_cursor(insert_at + self.clipboard.len());
730 }
731 ClipboardKind::LineWise => {
732 let (line_start, line_end) =
733 vim_current_line_bounds(self.editor.content(), self.editor.cursor());
734 let insert_at = if after {
735 if line_end < self.editor.content().len() {
736 line_end + 1
737 } else {
738 line_end
739 }
740 } else {
741 line_start
742 };
743 self.editor
744 .replace_range(insert_at, insert_at, self.clipboard);
745 let cursor = (insert_at + self.clipboard.len()).min(self.editor.content().len());
746 self.editor.set_cursor(cursor);
747 }
748 }
749 }
750
751 fn join_lines(&mut self) {
752 let (_, line_end) = vim_current_line_bounds(self.editor.content(), self.editor.cursor());
753 if line_end >= self.editor.content().len() {
754 return;
755 }
756 let next_start = line_end + 1;
757 let next_non_ws = self.editor.content()[next_start..]
758 .char_indices()
759 .find_map(|(idx, ch)| (!ch.is_whitespace()).then_some(next_start + idx))
760 .unwrap_or(next_start);
761 self.editor.replace_range(line_end, next_non_ws, " ");
762 self.editor.set_cursor(line_end + 1);
763 }
764
765 fn repeat_last_change(&mut self) {
766 let Some(command) = self.state.last_change.clone() else {
767 return;
768 };
769 match command {
770 RepeatableCommand::DeleteChar => {
771 if self.editor.cursor() < self.editor.content().len() {
772 self.editor.delete_char_forward();
773 }
774 }
775 RepeatableCommand::PasteAfter => self.paste(true),
776 RepeatableCommand::PasteBefore => self.paste(false),
777 RepeatableCommand::JoinLines => self.join_lines(),
778 RepeatableCommand::InsertText { kind, text } => self.repeat_insert(kind, &text),
779 RepeatableCommand::OperateMotion { operator, motion } => {
780 let _ = self.apply_motion_operator(operator, motion);
781 }
782 RepeatableCommand::OperateTextObject { operator, object } => {
783 let _ = self.apply_text_object_operator(operator, object);
784 }
785 RepeatableCommand::OperateLine { operator } => self.apply_line_operator(operator),
786 RepeatableCommand::DeleteToLineEnd => self.delete_to_line_end(),
787 RepeatableCommand::Change { target, text } => self.repeat_change(target, &text),
788 }
789 }
790
791 fn repeat_insert(&mut self, kind: InsertKind, text: &str) {
792 match kind {
793 InsertKind::Insert => {}
794 InsertKind::InsertStart => {
795 let start = vim_line_first_non_ws(self.editor.content(), self.editor.cursor());
796 self.editor.set_cursor(start);
797 }
798 InsertKind::Append => {
799 if self.editor.cursor() < self.editor.content().len() {
800 self.editor.move_right();
801 }
802 }
803 InsertKind::AppendEnd => {
804 let end = vim_line_end(self.editor.content(), self.editor.cursor());
805 self.editor.set_cursor(end);
806 }
807 InsertKind::OpenBelow => self.open_line(true),
808 InsertKind::OpenAbove => self.open_line(false),
809 }
810 self.editor.insert_text(text);
811 self.state.set_mode(VimMode::Normal);
812 }
813
814 fn repeat_change(&mut self, target: ChangeTarget, text: &str) {
815 if !self.start_change(target) {
816 return;
817 }
818 self.editor.insert_text(text);
819 self.finish_insert_capture();
820 self.state.set_mode(VimMode::Normal);
821 }
822}