1//! Vim support for Zed.
2
3#[cfg(test)]
4mod test;
5
6mod change_list;
7mod command;
8mod digraph;
9mod helix;
10mod indent;
11mod insert;
12mod mode_indicator;
13mod motion;
14mod normal;
15mod object;
16mod replace;
17mod rewrap;
18mod state;
19mod surrounds;
20mod visual;
21
22use anyhow::Result;
23use collections::HashMap;
24use editor::{
25 movement::{self, FindRange},
26 Anchor, Bias, Editor, EditorEvent, EditorMode, ToPoint,
27};
28use gpui::{
29 actions, impl_actions, Action, App, AppContext as _, Axis, Context, Entity, EventEmitter,
30 KeyContext, KeystrokeEvent, Render, Subscription, Task, WeakEntity, Window,
31};
32use insert::{NormalBefore, TemporaryNormal};
33use language::{CursorShape, Point, Selection, SelectionGoal, TransactionId};
34pub use mode_indicator::ModeIndicator;
35use motion::Motion;
36use normal::search::SearchSubmit;
37use object::Object;
38use schemars::JsonSchema;
39use serde::Deserialize;
40use serde_derive::Serialize;
41use settings::{update_settings_file, Settings, SettingsSources, SettingsStore};
42use state::{Mode, Operator, RecordedSelection, SearchState, VimGlobals};
43use std::{mem, ops::Range, sync::Arc};
44use surrounds::SurroundsType;
45use theme::ThemeSettings;
46use ui::{px, IntoElement, SharedString};
47use vim_mode_setting::VimModeSetting;
48use workspace::{self, Pane, Workspace};
49
50use crate::state::ReplayableAction;
51
52/// Number is used to manage vim's count. Pushing a digit
53/// multiplies the current value by 10 and adds the digit.
54#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
55struct Number(usize);
56
57#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
58struct SelectRegister(String);
59
60#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
61#[serde(deny_unknown_fields)]
62struct PushObject {
63 around: bool,
64}
65
66#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
67#[serde(deny_unknown_fields)]
68struct PushFindForward {
69 before: bool,
70}
71
72#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
73#[serde(deny_unknown_fields)]
74struct PushFindBackward {
75 after: bool,
76}
77
78#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
79#[serde(deny_unknown_fields)]
80struct PushSneak {
81 first_char: Option<char>,
82}
83
84#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
85#[serde(deny_unknown_fields)]
86struct PushSneakBackward {
87 first_char: Option<char>,
88}
89
90#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
91#[serde(deny_unknown_fields)]
92struct PushAddSurrounds {}
93
94#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
95#[serde(deny_unknown_fields)]
96struct PushChangeSurrounds {
97 target: Option<Object>,
98}
99
100#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
101#[serde(deny_unknown_fields)]
102struct PushJump {
103 line: bool,
104}
105
106#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
107#[serde(deny_unknown_fields)]
108struct PushDigraph {
109 first_char: Option<char>,
110}
111
112#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
113#[serde(deny_unknown_fields)]
114struct PushLiteral {
115 prefix: Option<String>,
116}
117
118actions!(
119 vim,
120 [
121 SwitchToNormalMode,
122 SwitchToInsertMode,
123 SwitchToReplaceMode,
124 SwitchToVisualMode,
125 SwitchToVisualLineMode,
126 SwitchToVisualBlockMode,
127 SwitchToHelixNormalMode,
128 ClearOperators,
129 Tab,
130 Enter,
131 InnerObject,
132 MaximizePane,
133 OpenDefaultKeymap,
134 ResetPaneSizes,
135 ResizePaneRight,
136 ResizePaneLeft,
137 ResizePaneUp,
138 ResizePaneDown,
139 PushChange,
140 PushDelete,
141 PushYank,
142 PushReplace,
143 PushDeleteSurrounds,
144 PushMark,
145 PushIndent,
146 PushOutdent,
147 PushAutoIndent,
148 PushRewrap,
149 PushShellCommand,
150 PushLowercase,
151 PushUppercase,
152 PushOppositeCase,
153 PushRegister,
154 PushRecordRegister,
155 PushReplayRegister,
156 PushReplaceWithRegister,
157 PushToggleComments,
158 ]
159);
160
161// in the workspace namespace so it's not filtered out when vim is disabled.
162actions!(workspace, [ToggleVimMode,]);
163
164impl_actions!(
165 vim,
166 [
167 Number,
168 SelectRegister,
169 PushObject,
170 PushFindForward,
171 PushFindBackward,
172 PushSneak,
173 PushSneakBackward,
174 PushAddSurrounds,
175 PushChangeSurrounds,
176 PushJump,
177 PushDigraph,
178 PushLiteral
179 ]
180);
181
182/// Initializes the `vim` crate.
183pub fn init(cx: &mut App) {
184 vim_mode_setting::init(cx);
185 VimSettings::register(cx);
186 VimGlobals::register(cx);
187
188 cx.observe_new(Vim::register).detach();
189
190 cx.observe_new(|workspace: &mut Workspace, _, _| {
191 workspace.register_action(|workspace, _: &ToggleVimMode, _, cx| {
192 let fs = workspace.app_state().fs.clone();
193 let currently_enabled = Vim::enabled(cx);
194 update_settings_file::<VimModeSetting>(fs, cx, move |setting, _| {
195 *setting = Some(!currently_enabled)
196 })
197 });
198
199 workspace.register_action(|_, _: &OpenDefaultKeymap, _, cx| {
200 cx.emit(workspace::Event::OpenBundledFile {
201 text: settings::vim_keymap(),
202 title: "Default Vim Bindings",
203 language: "JSON",
204 });
205 });
206
207 workspace.register_action(|workspace, _: &ResetPaneSizes, _, cx| {
208 workspace.reset_pane_sizes(cx);
209 });
210
211 workspace.register_action(|workspace, _: &MaximizePane, window, cx| {
212 let pane = workspace.active_pane();
213 let Some(size) = workspace.bounding_box_for_pane(&pane) else {
214 return;
215 };
216
217 let theme = ThemeSettings::get_global(cx);
218 let height = theme.buffer_font_size(cx) * theme.buffer_line_height.value();
219
220 let desired_size = if let Some(count) = Vim::take_count(cx) {
221 height * count
222 } else {
223 px(10000.)
224 };
225 workspace.resize_pane(Axis::Vertical, desired_size - size.size.height, window, cx)
226 });
227
228 workspace.register_action(|workspace, _: &ResizePaneRight, window, cx| {
229 let count = Vim::take_count(cx).unwrap_or(1) as f32;
230 let theme = ThemeSettings::get_global(cx);
231 let Ok(font_id) = window.text_system().font_id(&theme.buffer_font) else {
232 return;
233 };
234 let Ok(width) = window
235 .text_system()
236 .advance(font_id, theme.buffer_font_size(cx), 'm')
237 else {
238 return;
239 };
240 workspace.resize_pane(Axis::Horizontal, width.width * count, window, cx);
241 });
242
243 workspace.register_action(|workspace, _: &ResizePaneLeft, window, cx| {
244 let count = Vim::take_count(cx).unwrap_or(1) as f32;
245 let theme = ThemeSettings::get_global(cx);
246 let Ok(font_id) = window.text_system().font_id(&theme.buffer_font) else {
247 return;
248 };
249 let Ok(width) = window
250 .text_system()
251 .advance(font_id, theme.buffer_font_size(cx), 'm')
252 else {
253 return;
254 };
255 workspace.resize_pane(Axis::Horizontal, -width.width * count, window, cx);
256 });
257
258 workspace.register_action(|workspace, _: &ResizePaneUp, window, cx| {
259 let count = Vim::take_count(cx).unwrap_or(1) as f32;
260 let theme = ThemeSettings::get_global(cx);
261 let height = theme.buffer_font_size(cx) * theme.buffer_line_height.value();
262 workspace.resize_pane(Axis::Vertical, height * count, window, cx);
263 });
264
265 workspace.register_action(|workspace, _: &ResizePaneDown, window, cx| {
266 let count = Vim::take_count(cx).unwrap_or(1) as f32;
267 let theme = ThemeSettings::get_global(cx);
268 let height = theme.buffer_font_size(cx) * theme.buffer_line_height.value();
269 workspace.resize_pane(Axis::Vertical, -height * count, window, cx);
270 });
271
272 workspace.register_action(|workspace, _: &SearchSubmit, window, cx| {
273 let vim = workspace
274 .focused_pane(window, cx)
275 .read(cx)
276 .active_item()
277 .and_then(|item| item.act_as::<Editor>(cx))
278 .and_then(|editor| editor.read(cx).addon::<VimAddon>().cloned());
279 let Some(vim) = vim else { return };
280 vim.entity.update(cx, |_, cx| {
281 cx.defer_in(window, |vim, window, cx| vim.search_submit(window, cx))
282 })
283 });
284 })
285 .detach();
286}
287
288#[derive(Clone)]
289pub(crate) struct VimAddon {
290 pub(crate) entity: Entity<Vim>,
291}
292
293impl editor::Addon for VimAddon {
294 fn extend_key_context(&self, key_context: &mut KeyContext, cx: &App) {
295 self.entity.read(cx).extend_key_context(key_context, cx)
296 }
297
298 fn to_any(&self) -> &dyn std::any::Any {
299 self
300 }
301}
302
303/// The state pertaining to Vim mode.
304pub(crate) struct Vim {
305 pub(crate) mode: Mode,
306 pub last_mode: Mode,
307 pub temp_mode: bool,
308 pub status_label: Option<SharedString>,
309 pub exit_temporary_mode: bool,
310
311 operator_stack: Vec<Operator>,
312 pub(crate) replacements: Vec<(Range<editor::Anchor>, String)>,
313
314 pub(crate) marks: HashMap<String, Vec<Anchor>>,
315 pub(crate) stored_visual_mode: Option<(Mode, Vec<bool>)>,
316 pub(crate) change_list: Vec<Vec<Anchor>>,
317 pub(crate) change_list_position: Option<usize>,
318
319 pub(crate) current_tx: Option<TransactionId>,
320 pub(crate) current_anchor: Option<Selection<Anchor>>,
321 pub(crate) undo_modes: HashMap<TransactionId, Mode>,
322
323 selected_register: Option<char>,
324 pub search: SearchState,
325
326 editor: WeakEntity<Editor>,
327
328 last_command: Option<String>,
329 running_command: Option<Task<()>>,
330 _subscriptions: Vec<Subscription>,
331}
332
333// Hack: Vim intercepts events dispatched to a window and updates the view in response.
334// This means it needs a VisualContext. The easiest way to satisfy that constraint is
335// to make Vim a "View" that is just never actually rendered.
336impl Render for Vim {
337 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
338 gpui::Empty
339 }
340}
341
342enum VimEvent {
343 Focused,
344}
345impl EventEmitter<VimEvent> for Vim {}
346
347impl Vim {
348 /// The namespace for Vim actions.
349 const NAMESPACE: &'static str = "vim";
350
351 pub fn new(window: &mut Window, cx: &mut Context<Editor>) -> Entity<Self> {
352 let editor = cx.entity().clone();
353
354 cx.new(|cx| Vim {
355 mode: Mode::Normal,
356 last_mode: Mode::Normal,
357 temp_mode: false,
358 exit_temporary_mode: false,
359 operator_stack: Vec::new(),
360 replacements: Vec::new(),
361
362 marks: HashMap::default(),
363 stored_visual_mode: None,
364 change_list: Vec::new(),
365 change_list_position: None,
366 current_tx: None,
367 current_anchor: None,
368 undo_modes: HashMap::default(),
369
370 status_label: None,
371 selected_register: None,
372 search: SearchState::default(),
373
374 last_command: None,
375 running_command: None,
376
377 editor: editor.downgrade(),
378 _subscriptions: vec![
379 cx.observe_keystrokes(Self::observe_keystrokes),
380 cx.subscribe_in(&editor, window, |this, _, event, window, cx| {
381 this.handle_editor_event(event, window, cx)
382 }),
383 ],
384 })
385 }
386
387 fn register(editor: &mut Editor, window: Option<&mut Window>, cx: &mut Context<Editor>) {
388 let Some(window) = window else {
389 return;
390 };
391
392 if !editor.use_modal_editing() {
393 return;
394 }
395
396 let mut was_enabled = Vim::enabled(cx);
397 let mut was_toggle = VimSettings::get_global(cx).toggle_relative_line_numbers;
398 cx.observe_global_in::<SettingsStore>(window, move |editor, window, cx| {
399 let enabled = Vim::enabled(cx);
400 let toggle = VimSettings::get_global(cx).toggle_relative_line_numbers;
401 if enabled && was_enabled && (toggle != was_toggle) {
402 if toggle {
403 let is_relative = editor
404 .addon::<VimAddon>()
405 .map(|vim| vim.entity.read(cx).mode != Mode::Insert);
406 editor.set_relative_line_number(is_relative, cx)
407 } else {
408 editor.set_relative_line_number(None, cx)
409 }
410 }
411 was_toggle = VimSettings::get_global(cx).toggle_relative_line_numbers;
412 if was_enabled == enabled {
413 return;
414 }
415 was_enabled = enabled;
416 if enabled {
417 Self::activate(editor, window, cx)
418 } else {
419 Self::deactivate(editor, cx)
420 }
421 })
422 .detach();
423 if was_enabled {
424 Self::activate(editor, window, cx)
425 }
426 }
427
428 fn activate(editor: &mut Editor, window: &mut Window, cx: &mut Context<Editor>) {
429 let vim = Vim::new(window, cx);
430
431 editor.register_addon(VimAddon {
432 entity: vim.clone(),
433 });
434
435 vim.update(cx, |_, cx| {
436 Vim::action(editor, cx, |vim, _: &SwitchToNormalMode, window, cx| {
437 vim.switch_mode(Mode::Normal, false, window, cx)
438 });
439
440 Vim::action(editor, cx, |vim, _: &SwitchToInsertMode, window, cx| {
441 vim.switch_mode(Mode::Insert, false, window, cx)
442 });
443
444 Vim::action(editor, cx, |vim, _: &SwitchToReplaceMode, window, cx| {
445 vim.switch_mode(Mode::Replace, false, window, cx)
446 });
447
448 Vim::action(editor, cx, |vim, _: &SwitchToVisualMode, window, cx| {
449 vim.switch_mode(Mode::Visual, false, window, cx)
450 });
451
452 Vim::action(editor, cx, |vim, _: &SwitchToVisualLineMode, window, cx| {
453 vim.switch_mode(Mode::VisualLine, false, window, cx)
454 });
455
456 Vim::action(
457 editor,
458 cx,
459 |vim, _: &SwitchToVisualBlockMode, window, cx| {
460 vim.switch_mode(Mode::VisualBlock, false, window, cx)
461 },
462 );
463
464 Vim::action(
465 editor,
466 cx,
467 |vim, _: &SwitchToHelixNormalMode, window, cx| {
468 vim.switch_mode(Mode::HelixNormal, false, window, cx)
469 },
470 );
471
472 Vim::action(editor, cx, |vim, action: &PushObject, window, cx| {
473 vim.push_operator(
474 Operator::Object {
475 around: action.around,
476 },
477 window,
478 cx,
479 )
480 });
481
482 Vim::action(editor, cx, |vim, action: &PushFindForward, window, cx| {
483 vim.push_operator(
484 Operator::FindForward {
485 before: action.before,
486 },
487 window,
488 cx,
489 )
490 });
491
492 Vim::action(editor, cx, |vim, action: &PushFindBackward, window, cx| {
493 vim.push_operator(
494 Operator::FindBackward {
495 after: action.after,
496 },
497 window,
498 cx,
499 )
500 });
501
502 Vim::action(editor, cx, |vim, action: &PushSneak, window, cx| {
503 vim.push_operator(
504 Operator::Sneak {
505 first_char: action.first_char,
506 },
507 window,
508 cx,
509 )
510 });
511
512 Vim::action(editor, cx, |vim, action: &PushSneakBackward, window, cx| {
513 vim.push_operator(
514 Operator::SneakBackward {
515 first_char: action.first_char,
516 },
517 window,
518 cx,
519 )
520 });
521
522 Vim::action(editor, cx, |vim, _: &PushAddSurrounds, window, cx| {
523 vim.push_operator(Operator::AddSurrounds { target: None }, window, cx)
524 });
525
526 Vim::action(
527 editor,
528 cx,
529 |vim, action: &PushChangeSurrounds, window, cx| {
530 vim.push_operator(
531 Operator::ChangeSurrounds {
532 target: action.target,
533 },
534 window,
535 cx,
536 )
537 },
538 );
539
540 Vim::action(editor, cx, |vim, action: &PushJump, window, cx| {
541 vim.push_operator(Operator::Jump { line: action.line }, window, cx)
542 });
543
544 Vim::action(editor, cx, |vim, action: &PushDigraph, window, cx| {
545 vim.push_operator(
546 Operator::Digraph {
547 first_char: action.first_char,
548 },
549 window,
550 cx,
551 )
552 });
553
554 Vim::action(editor, cx, |vim, action: &PushLiteral, window, cx| {
555 vim.push_operator(
556 Operator::Literal {
557 prefix: action.prefix.clone(),
558 },
559 window,
560 cx,
561 )
562 });
563
564 Vim::action(editor, cx, |vim, _: &PushChange, window, cx| {
565 vim.push_operator(Operator::Change, window, cx)
566 });
567
568 Vim::action(editor, cx, |vim, _: &PushDelete, window, cx| {
569 vim.push_operator(Operator::Delete, window, cx)
570 });
571
572 Vim::action(editor, cx, |vim, _: &PushYank, window, cx| {
573 vim.push_operator(Operator::Yank, window, cx)
574 });
575
576 Vim::action(editor, cx, |vim, _: &PushReplace, window, cx| {
577 vim.push_operator(Operator::Replace, window, cx)
578 });
579
580 Vim::action(editor, cx, |vim, _: &PushDeleteSurrounds, window, cx| {
581 vim.push_operator(Operator::DeleteSurrounds, window, cx)
582 });
583
584 Vim::action(editor, cx, |vim, _: &PushMark, window, cx| {
585 vim.push_operator(Operator::Mark, window, cx)
586 });
587
588 Vim::action(editor, cx, |vim, _: &PushIndent, window, cx| {
589 vim.push_operator(Operator::Indent, window, cx)
590 });
591
592 Vim::action(editor, cx, |vim, _: &PushOutdent, window, cx| {
593 vim.push_operator(Operator::Outdent, window, cx)
594 });
595
596 Vim::action(editor, cx, |vim, _: &PushAutoIndent, window, cx| {
597 vim.push_operator(Operator::AutoIndent, window, cx)
598 });
599
600 Vim::action(editor, cx, |vim, _: &PushRewrap, window, cx| {
601 vim.push_operator(Operator::Rewrap, window, cx)
602 });
603
604 Vim::action(editor, cx, |vim, _: &PushShellCommand, window, cx| {
605 vim.push_operator(Operator::ShellCommand, window, cx)
606 });
607
608 Vim::action(editor, cx, |vim, _: &PushLowercase, window, cx| {
609 vim.push_operator(Operator::Lowercase, window, cx)
610 });
611
612 Vim::action(editor, cx, |vim, _: &PushUppercase, window, cx| {
613 vim.push_operator(Operator::Uppercase, window, cx)
614 });
615
616 Vim::action(editor, cx, |vim, _: &PushOppositeCase, window, cx| {
617 vim.push_operator(Operator::OppositeCase, window, cx)
618 });
619
620 Vim::action(editor, cx, |vim, _: &PushRegister, window, cx| {
621 vim.push_operator(Operator::Register, window, cx)
622 });
623
624 Vim::action(editor, cx, |vim, _: &PushRecordRegister, window, cx| {
625 vim.push_operator(Operator::RecordRegister, window, cx)
626 });
627
628 Vim::action(editor, cx, |vim, _: &PushReplayRegister, window, cx| {
629 vim.push_operator(Operator::ReplayRegister, window, cx)
630 });
631
632 Vim::action(
633 editor,
634 cx,
635 |vim, _: &PushReplaceWithRegister, window, cx| {
636 vim.push_operator(Operator::ReplaceWithRegister, window, cx)
637 },
638 );
639
640 Vim::action(editor, cx, |vim, _: &PushToggleComments, window, cx| {
641 vim.push_operator(Operator::ToggleComments, window, cx)
642 });
643
644 Vim::action(editor, cx, |vim, _: &ClearOperators, window, cx| {
645 vim.clear_operator(window, cx)
646 });
647 Vim::action(editor, cx, |vim, n: &Number, window, cx| {
648 vim.push_count_digit(n.0, window, cx);
649 });
650 Vim::action(editor, cx, |vim, _: &Tab, window, cx| {
651 vim.input_ignored(" ".into(), window, cx)
652 });
653 Vim::action(
654 editor,
655 cx,
656 |vim, action: &editor::AcceptEditPrediction, window, cx| {
657 vim.update_editor(window, cx, |_, editor, window, cx| {
658 editor.accept_edit_prediction(action, window, cx);
659 });
660 // In non-insertion modes, predictions will be hidden and instead a jump will be
661 // displayed (and performed by `accept_edit_prediction`). This switches to
662 // insert mode so that the prediction is displayed after the jump.
663 match vim.mode {
664 Mode::Replace => {}
665 _ => vim.switch_mode(Mode::Insert, true, window, cx),
666 };
667 },
668 );
669 Vim::action(editor, cx, |vim, _: &Enter, window, cx| {
670 vim.input_ignored("\n".into(), window, cx)
671 });
672
673 normal::register(editor, cx);
674 insert::register(editor, cx);
675 helix::register(editor, cx);
676 motion::register(editor, cx);
677 command::register(editor, cx);
678 replace::register(editor, cx);
679 indent::register(editor, cx);
680 rewrap::register(editor, cx);
681 object::register(editor, cx);
682 visual::register(editor, cx);
683 change_list::register(editor, cx);
684 digraph::register(editor, cx);
685
686 cx.defer_in(window, |vim, window, cx| {
687 vim.focused(false, window, cx);
688 })
689 })
690 }
691
692 fn deactivate(editor: &mut Editor, cx: &mut Context<Editor>) {
693 editor.set_cursor_shape(CursorShape::Bar, cx);
694 editor.set_clip_at_line_ends(false, cx);
695 editor.set_collapse_matches(false);
696 editor.set_input_enabled(true);
697 editor.set_autoindent(true);
698 editor.selections.line_mode = false;
699 editor.unregister_addon::<VimAddon>();
700 editor.set_relative_line_number(None, cx);
701 if let Some(vim) = Vim::globals(cx).focused_vim() {
702 if vim.entity_id() == cx.entity().entity_id() {
703 Vim::globals(cx).focused_vim = None;
704 }
705 }
706 }
707
708 /// Register an action on the editor.
709 pub fn action<A: Action>(
710 editor: &mut Editor,
711 cx: &mut Context<Vim>,
712 f: impl Fn(&mut Vim, &A, &mut Window, &mut Context<Vim>) + 'static,
713 ) {
714 let subscription = editor.register_action(cx.listener(f));
715 cx.on_release(|_, _| drop(subscription)).detach();
716 }
717
718 pub fn editor(&self) -> Option<Entity<Editor>> {
719 self.editor.upgrade()
720 }
721
722 pub fn workspace(&self, window: &mut Window) -> Option<Entity<Workspace>> {
723 window.root::<Workspace>().flatten()
724 }
725
726 pub fn pane(&self, window: &mut Window, cx: &mut Context<Self>) -> Option<Entity<Pane>> {
727 self.workspace(window)
728 .map(|workspace| workspace.read(cx).focused_pane(window, cx))
729 }
730
731 pub fn enabled(cx: &mut App) -> bool {
732 VimModeSetting::get_global(cx).0
733 }
734
735 /// Called whenever an keystroke is typed so vim can observe all actions
736 /// and keystrokes accordingly.
737 fn observe_keystrokes(
738 &mut self,
739 keystroke_event: &KeystrokeEvent,
740 window: &mut Window,
741 cx: &mut Context<Self>,
742 ) {
743 if self.exit_temporary_mode {
744 self.exit_temporary_mode = false;
745 // Don't switch to insert mode if the action is temporary_normal.
746 if let Some(action) = keystroke_event.action.as_ref() {
747 if action.as_any().downcast_ref::<TemporaryNormal>().is_some() {
748 return;
749 }
750 }
751 self.switch_mode(Mode::Insert, false, window, cx)
752 }
753 if let Some(action) = keystroke_event.action.as_ref() {
754 // Keystroke is handled by the vim system, so continue forward
755 if action.name().starts_with("vim::") {
756 return;
757 }
758 } else if window.has_pending_keystrokes() || keystroke_event.keystroke.is_ime_in_progress()
759 {
760 return;
761 }
762
763 if let Some(operator) = self.active_operator() {
764 match operator {
765 Operator::Literal { prefix } => {
766 self.handle_literal_keystroke(
767 keystroke_event,
768 prefix.unwrap_or_default(),
769 window,
770 cx,
771 );
772 }
773 _ if !operator.is_waiting(self.mode) => {
774 self.clear_operator(window, cx);
775 self.stop_recording_immediately(Box::new(ClearOperators), cx)
776 }
777 _ => {}
778 }
779 }
780 }
781
782 fn handle_editor_event(
783 &mut self,
784 event: &EditorEvent,
785 window: &mut Window,
786 cx: &mut Context<Self>,
787 ) {
788 match event {
789 EditorEvent::Focused => self.focused(true, window, cx),
790 EditorEvent::Blurred => self.blurred(window, cx),
791 EditorEvent::SelectionsChanged { local: true } => {
792 self.local_selections_changed(window, cx);
793 }
794 EditorEvent::InputIgnored { text } => {
795 self.input_ignored(text.clone(), window, cx);
796 Vim::globals(cx).observe_insertion(text, None)
797 }
798 EditorEvent::InputHandled {
799 text,
800 utf16_range_to_replace: range_to_replace,
801 } => Vim::globals(cx).observe_insertion(text, range_to_replace.clone()),
802 EditorEvent::TransactionBegun { transaction_id } => {
803 self.transaction_begun(*transaction_id, window, cx)
804 }
805 EditorEvent::TransactionUndone { transaction_id } => {
806 self.transaction_undone(transaction_id, window, cx)
807 }
808 EditorEvent::Edited { .. } => self.push_to_change_list(window, cx),
809 EditorEvent::FocusedIn => self.sync_vim_settings(window, cx),
810 EditorEvent::CursorShapeChanged => self.cursor_shape_changed(window, cx),
811 _ => {}
812 }
813 }
814
815 fn push_operator(&mut self, operator: Operator, window: &mut Window, cx: &mut Context<Self>) {
816 if matches!(
817 operator,
818 Operator::Change
819 | Operator::Delete
820 | Operator::Replace
821 | Operator::Indent
822 | Operator::Outdent
823 | Operator::AutoIndent
824 | Operator::Lowercase
825 | Operator::Uppercase
826 | Operator::OppositeCase
827 | Operator::ToggleComments
828 | Operator::ReplaceWithRegister
829 ) {
830 self.start_recording(cx)
831 };
832 // Since these operations can only be entered with pre-operators,
833 // we need to clear the previous operators when pushing,
834 // so that the current stack is the most correct
835 if matches!(
836 operator,
837 Operator::AddSurrounds { .. }
838 | Operator::ChangeSurrounds { .. }
839 | Operator::DeleteSurrounds
840 ) {
841 self.operator_stack.clear();
842 if let Operator::AddSurrounds { target: None } = operator {
843 self.start_recording(cx);
844 }
845 };
846 self.operator_stack.push(operator);
847 self.sync_vim_settings(window, cx);
848 }
849
850 pub fn switch_mode(
851 &mut self,
852 mode: Mode,
853 leave_selections: bool,
854 window: &mut Window,
855 cx: &mut Context<Self>,
856 ) {
857 if self.temp_mode && mode == Mode::Normal {
858 self.temp_mode = false;
859 self.switch_mode(Mode::Normal, leave_selections, window, cx);
860 self.switch_mode(Mode::Insert, false, window, cx);
861 return;
862 } else if self.temp_mode
863 && !matches!(mode, Mode::Visual | Mode::VisualLine | Mode::VisualBlock)
864 {
865 self.temp_mode = false;
866 }
867
868 let last_mode = self.mode;
869 let prior_mode = self.last_mode;
870 let prior_tx = self.current_tx;
871 self.status_label.take();
872 self.last_mode = last_mode;
873 self.mode = mode;
874 self.operator_stack.clear();
875 self.selected_register.take();
876 self.cancel_running_command(window, cx);
877 if mode == Mode::Normal || mode != last_mode {
878 self.current_tx.take();
879 self.current_anchor.take();
880 }
881 if mode != Mode::Insert && mode != Mode::Replace {
882 Vim::take_count(cx);
883 }
884
885 // Sync editor settings like clip mode
886 self.sync_vim_settings(window, cx);
887
888 if VimSettings::get_global(cx).toggle_relative_line_numbers
889 && self.mode != self.last_mode
890 && (self.mode == Mode::Insert || self.last_mode == Mode::Insert)
891 {
892 self.update_editor(window, cx, |vim, editor, _, cx| {
893 let is_relative = vim.mode != Mode::Insert;
894 editor.set_relative_line_number(Some(is_relative), cx)
895 });
896 }
897
898 if leave_selections {
899 return;
900 }
901
902 if !mode.is_visual() && last_mode.is_visual() {
903 self.create_visual_marks(last_mode, window, cx);
904 }
905
906 // Adjust selections
907 self.update_editor(window, cx, |vim, editor, window, cx| {
908 if last_mode != Mode::VisualBlock && last_mode.is_visual() && mode == Mode::VisualBlock
909 {
910 vim.visual_block_motion(true, editor, window, cx, |_, point, goal| {
911 Some((point, goal))
912 })
913 }
914 if last_mode == Mode::Insert || last_mode == Mode::Replace {
915 if let Some(prior_tx) = prior_tx {
916 editor.group_until_transaction(prior_tx, cx)
917 }
918 }
919
920 editor.change_selections(None, window, cx, |s| {
921 // we cheat with visual block mode and use multiple cursors.
922 // the cost of this cheat is we need to convert back to a single
923 // cursor whenever vim would.
924 if last_mode == Mode::VisualBlock
925 && (mode != Mode::VisualBlock && mode != Mode::Insert)
926 {
927 let tail = s.oldest_anchor().tail();
928 let head = s.newest_anchor().head();
929 s.select_anchor_ranges(vec![tail..head]);
930 } else if last_mode == Mode::Insert
931 && prior_mode == Mode::VisualBlock
932 && mode != Mode::VisualBlock
933 {
934 let pos = s.first_anchor().head();
935 s.select_anchor_ranges(vec![pos..pos])
936 }
937
938 let snapshot = s.display_map();
939 if let Some(pending) = s.pending.as_mut() {
940 if pending.selection.reversed && mode.is_visual() && !last_mode.is_visual() {
941 let mut end = pending.selection.end.to_point(&snapshot.buffer_snapshot);
942 end = snapshot
943 .buffer_snapshot
944 .clip_point(end + Point::new(0, 1), Bias::Right);
945 pending.selection.end = snapshot.buffer_snapshot.anchor_before(end);
946 }
947 }
948
949 s.move_with(|map, selection| {
950 if last_mode.is_visual() && !mode.is_visual() {
951 let mut point = selection.head();
952 if !selection.reversed && !selection.is_empty() {
953 point = movement::left(map, selection.head());
954 }
955 selection.collapse_to(point, selection.goal)
956 } else if !last_mode.is_visual() && mode.is_visual() && selection.is_empty() {
957 selection.end = movement::right(map, selection.start);
958 }
959 });
960 })
961 });
962 }
963
964 pub fn take_count(cx: &mut App) -> Option<usize> {
965 let global_state = cx.global_mut::<VimGlobals>();
966 if global_state.dot_replaying {
967 return global_state.recorded_count;
968 }
969
970 let count = if global_state.post_count.is_none() && global_state.pre_count.is_none() {
971 return None;
972 } else {
973 Some(
974 global_state.post_count.take().unwrap_or(1)
975 * global_state.pre_count.take().unwrap_or(1),
976 )
977 };
978
979 if global_state.dot_recording {
980 global_state.recorded_count = count;
981 }
982 count
983 }
984
985 pub fn cursor_shape(&self) -> CursorShape {
986 match self.mode {
987 Mode::Normal => {
988 if let Some(operator) = self.operator_stack.last() {
989 match operator {
990 // Navigation operators -> Block cursor
991 Operator::FindForward { .. }
992 | Operator::FindBackward { .. }
993 | Operator::Mark
994 | Operator::Jump { .. }
995 | Operator::Register
996 | Operator::RecordRegister
997 | Operator::ReplayRegister => CursorShape::Block,
998
999 // All other operators -> Underline cursor
1000 _ => CursorShape::Underline,
1001 }
1002 } else {
1003 // No operator active -> Block cursor
1004 CursorShape::Block
1005 }
1006 }
1007 Mode::Replace => CursorShape::Underline,
1008 Mode::HelixNormal | Mode::Visual | Mode::VisualLine | Mode::VisualBlock => {
1009 CursorShape::Block
1010 }
1011 Mode::Insert => CursorShape::Bar,
1012 }
1013 }
1014
1015 pub fn editor_input_enabled(&self) -> bool {
1016 match self.mode {
1017 Mode::Insert => {
1018 if let Some(operator) = self.operator_stack.last() {
1019 !operator.is_waiting(self.mode)
1020 } else {
1021 true
1022 }
1023 }
1024 Mode::Normal
1025 | Mode::HelixNormal
1026 | Mode::Replace
1027 | Mode::Visual
1028 | Mode::VisualLine
1029 | Mode::VisualBlock => false,
1030 }
1031 }
1032
1033 pub fn should_autoindent(&self) -> bool {
1034 !(self.mode == Mode::Insert && self.last_mode == Mode::VisualBlock)
1035 }
1036
1037 pub fn clip_at_line_ends(&self) -> bool {
1038 match self.mode {
1039 Mode::Insert
1040 | Mode::Visual
1041 | Mode::VisualLine
1042 | Mode::VisualBlock
1043 | Mode::Replace
1044 | Mode::HelixNormal => false,
1045 Mode::Normal => true,
1046 }
1047 }
1048
1049 pub fn extend_key_context(&self, context: &mut KeyContext, cx: &App) {
1050 let mut mode = match self.mode {
1051 Mode::Normal => "normal",
1052 Mode::Visual | Mode::VisualLine | Mode::VisualBlock => "visual",
1053 Mode::Insert => "insert",
1054 Mode::Replace => "replace",
1055 Mode::HelixNormal => "helix_normal",
1056 }
1057 .to_string();
1058
1059 let mut operator_id = "none";
1060
1061 let active_operator = self.active_operator();
1062 if active_operator.is_none() && cx.global::<VimGlobals>().pre_count.is_some()
1063 || active_operator.is_some() && cx.global::<VimGlobals>().post_count.is_some()
1064 {
1065 context.add("VimCount");
1066 }
1067
1068 if let Some(active_operator) = active_operator {
1069 if active_operator.is_waiting(self.mode) {
1070 if matches!(active_operator, Operator::Literal { .. }) {
1071 mode = "literal".to_string();
1072 } else {
1073 mode = "waiting".to_string();
1074 }
1075 } else {
1076 operator_id = active_operator.id();
1077 mode = "operator".to_string();
1078 }
1079 }
1080
1081 if mode == "normal" || mode == "visual" || mode == "operator" {
1082 context.add("VimControl");
1083 }
1084 context.set("vim_mode", mode);
1085 context.set("vim_operator", operator_id);
1086 }
1087
1088 fn focused(&mut self, preserve_selection: bool, window: &mut Window, cx: &mut Context<Self>) {
1089 let Some(editor) = self.editor() else {
1090 return;
1091 };
1092 let newest_selection_empty = editor.update(cx, |editor, cx| {
1093 editor.selections.newest::<usize>(cx).is_empty()
1094 });
1095 let editor = editor.read(cx);
1096 let editor_mode = editor.mode();
1097
1098 if editor_mode == EditorMode::Full
1099 && !newest_selection_empty
1100 && self.mode == Mode::Normal
1101 // When following someone, don't switch vim mode.
1102 && editor.leader_peer_id().is_none()
1103 {
1104 if preserve_selection {
1105 self.switch_mode(Mode::Visual, true, window, cx);
1106 } else {
1107 self.update_editor(window, cx, |_, editor, window, cx| {
1108 editor.set_clip_at_line_ends(false, cx);
1109 editor.change_selections(None, window, cx, |s| {
1110 s.move_with(|_, selection| {
1111 selection.collapse_to(selection.start, selection.goal)
1112 })
1113 });
1114 });
1115 }
1116 }
1117
1118 cx.emit(VimEvent::Focused);
1119 self.sync_vim_settings(window, cx);
1120
1121 if VimSettings::get_global(cx).toggle_relative_line_numbers {
1122 if let Some(old_vim) = Vim::globals(cx).focused_vim() {
1123 if old_vim.entity_id() != cx.entity().entity_id() {
1124 old_vim.update(cx, |vim, cx| {
1125 vim.update_editor(window, cx, |_, editor, _, cx| {
1126 editor.set_relative_line_number(None, cx)
1127 });
1128 });
1129
1130 self.update_editor(window, cx, |vim, editor, _, cx| {
1131 let is_relative = vim.mode != Mode::Insert;
1132 editor.set_relative_line_number(Some(is_relative), cx)
1133 });
1134 }
1135 } else {
1136 self.update_editor(window, cx, |vim, editor, _, cx| {
1137 let is_relative = vim.mode != Mode::Insert;
1138 editor.set_relative_line_number(Some(is_relative), cx)
1139 });
1140 }
1141 }
1142 Vim::globals(cx).focused_vim = Some(cx.entity().downgrade());
1143 }
1144
1145 fn blurred(&mut self, window: &mut Window, cx: &mut Context<Self>) {
1146 self.stop_recording_immediately(NormalBefore.boxed_clone(), cx);
1147 self.store_visual_marks(window, cx);
1148 self.clear_operator(window, cx);
1149 self.update_editor(window, cx, |vim, editor, _, cx| {
1150 if vim.cursor_shape() == CursorShape::Block {
1151 editor.set_cursor_shape(CursorShape::Hollow, cx);
1152 }
1153 });
1154 }
1155
1156 fn cursor_shape_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
1157 self.update_editor(window, cx, |vim, editor, _, cx| {
1158 editor.set_cursor_shape(vim.cursor_shape(), cx);
1159 });
1160 }
1161
1162 fn update_editor<S>(
1163 &mut self,
1164 window: &mut Window,
1165 cx: &mut Context<Self>,
1166 update: impl FnOnce(&mut Self, &mut Editor, &mut Window, &mut Context<Editor>) -> S,
1167 ) -> Option<S> {
1168 let editor = self.editor.upgrade()?;
1169 Some(editor.update(cx, |editor, cx| update(self, editor, window, cx)))
1170 }
1171
1172 fn editor_selections(
1173 &mut self,
1174 window: &mut Window,
1175 cx: &mut Context<Self>,
1176 ) -> Vec<Range<Anchor>> {
1177 self.update_editor(window, cx, |_, editor, _, _| {
1178 editor
1179 .selections
1180 .disjoint_anchors()
1181 .iter()
1182 .map(|selection| selection.tail()..selection.head())
1183 .collect()
1184 })
1185 .unwrap_or_default()
1186 }
1187
1188 /// When doing an action that modifies the buffer, we start recording so that `.`
1189 /// will replay the action.
1190 pub fn start_recording(&mut self, cx: &mut Context<Self>) {
1191 Vim::update_globals(cx, |globals, cx| {
1192 if !globals.dot_replaying {
1193 globals.dot_recording = true;
1194 globals.recording_actions = Default::default();
1195 globals.recorded_count = None;
1196
1197 let selections = self.editor().map(|editor| {
1198 editor.update(cx, |editor, cx| {
1199 (
1200 editor.selections.oldest::<Point>(cx),
1201 editor.selections.newest::<Point>(cx),
1202 )
1203 })
1204 });
1205
1206 if let Some((oldest, newest)) = selections {
1207 globals.recorded_selection = match self.mode {
1208 Mode::Visual if newest.end.row == newest.start.row => {
1209 RecordedSelection::SingleLine {
1210 cols: newest.end.column - newest.start.column,
1211 }
1212 }
1213 Mode::Visual => RecordedSelection::Visual {
1214 rows: newest.end.row - newest.start.row,
1215 cols: newest.end.column,
1216 },
1217 Mode::VisualLine => RecordedSelection::VisualLine {
1218 rows: newest.end.row - newest.start.row,
1219 },
1220 Mode::VisualBlock => RecordedSelection::VisualBlock {
1221 rows: newest.end.row.abs_diff(oldest.start.row),
1222 cols: newest.end.column.abs_diff(oldest.start.column),
1223 },
1224 _ => RecordedSelection::None,
1225 }
1226 } else {
1227 globals.recorded_selection = RecordedSelection::None;
1228 }
1229 }
1230 })
1231 }
1232
1233 pub fn stop_replaying(&mut self, cx: &mut Context<Self>) {
1234 let globals = Vim::globals(cx);
1235 globals.dot_replaying = false;
1236 if let Some(replayer) = globals.replayer.take() {
1237 replayer.stop();
1238 }
1239 }
1240
1241 /// When finishing an action that modifies the buffer, stop recording.
1242 /// as you usually call this within a keystroke handler we also ensure that
1243 /// the current action is recorded.
1244 pub fn stop_recording(&mut self, cx: &mut Context<Self>) {
1245 let globals = Vim::globals(cx);
1246 if globals.dot_recording {
1247 globals.stop_recording_after_next_action = true;
1248 }
1249 self.exit_temporary_mode = self.temp_mode;
1250 }
1251
1252 /// Stops recording actions immediately rather than waiting until after the
1253 /// next action to stop recording.
1254 ///
1255 /// This doesn't include the current action.
1256 pub fn stop_recording_immediately(&mut self, action: Box<dyn Action>, cx: &mut Context<Self>) {
1257 let globals = Vim::globals(cx);
1258 if globals.dot_recording {
1259 globals
1260 .recording_actions
1261 .push(ReplayableAction::Action(action.boxed_clone()));
1262 globals.recorded_actions = mem::take(&mut globals.recording_actions);
1263 globals.dot_recording = false;
1264 globals.stop_recording_after_next_action = false;
1265 }
1266 self.exit_temporary_mode = self.temp_mode;
1267 }
1268
1269 /// Explicitly record one action (equivalents to start_recording and stop_recording)
1270 pub fn record_current_action(&mut self, cx: &mut Context<Self>) {
1271 self.start_recording(cx);
1272 self.stop_recording(cx);
1273 }
1274
1275 fn push_count_digit(&mut self, number: usize, window: &mut Window, cx: &mut Context<Self>) {
1276 if self.active_operator().is_some() {
1277 let post_count = Vim::globals(cx).post_count.unwrap_or(0);
1278
1279 Vim::globals(cx).post_count = Some(
1280 post_count
1281 .checked_mul(10)
1282 .and_then(|post_count| post_count.checked_add(number))
1283 .unwrap_or(post_count),
1284 )
1285 } else {
1286 let pre_count = Vim::globals(cx).pre_count.unwrap_or(0);
1287
1288 Vim::globals(cx).pre_count = Some(
1289 pre_count
1290 .checked_mul(10)
1291 .and_then(|pre_count| pre_count.checked_add(number))
1292 .unwrap_or(pre_count),
1293 )
1294 }
1295 // update the keymap so that 0 works
1296 self.sync_vim_settings(window, cx)
1297 }
1298
1299 fn select_register(&mut self, register: Arc<str>, window: &mut Window, cx: &mut Context<Self>) {
1300 if register.chars().count() == 1 {
1301 self.selected_register
1302 .replace(register.chars().next().unwrap());
1303 }
1304 self.operator_stack.clear();
1305 self.sync_vim_settings(window, cx);
1306 }
1307
1308 fn maybe_pop_operator(&mut self) -> Option<Operator> {
1309 self.operator_stack.pop()
1310 }
1311
1312 fn pop_operator(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Operator {
1313 let popped_operator = self.operator_stack.pop()
1314 .expect("Operator popped when no operator was on the stack. This likely means there is an invalid keymap config");
1315 self.sync_vim_settings(window, cx);
1316 popped_operator
1317 }
1318
1319 fn clear_operator(&mut self, window: &mut Window, cx: &mut Context<Self>) {
1320 Vim::take_count(cx);
1321 self.selected_register.take();
1322 self.operator_stack.clear();
1323 self.sync_vim_settings(window, cx);
1324 }
1325
1326 fn active_operator(&self) -> Option<Operator> {
1327 self.operator_stack.last().cloned()
1328 }
1329
1330 fn transaction_begun(
1331 &mut self,
1332 transaction_id: TransactionId,
1333 _window: &mut Window,
1334 _: &mut Context<Self>,
1335 ) {
1336 let mode = if (self.mode == Mode::Insert
1337 || self.mode == Mode::Replace
1338 || self.mode == Mode::Normal)
1339 && self.current_tx.is_none()
1340 {
1341 self.current_tx = Some(transaction_id);
1342 self.last_mode
1343 } else {
1344 self.mode
1345 };
1346 if mode == Mode::VisualLine || mode == Mode::VisualBlock {
1347 self.undo_modes.insert(transaction_id, mode);
1348 }
1349 }
1350
1351 fn transaction_undone(
1352 &mut self,
1353 transaction_id: &TransactionId,
1354 window: &mut Window,
1355 cx: &mut Context<Self>,
1356 ) {
1357 match self.mode {
1358 Mode::VisualLine | Mode::VisualBlock | Mode::Visual => {
1359 self.update_editor(window, cx, |vim, editor, window, cx| {
1360 let original_mode = vim.undo_modes.get(transaction_id);
1361 editor.change_selections(None, window, cx, |s| match original_mode {
1362 Some(Mode::VisualLine) => {
1363 s.move_with(|map, selection| {
1364 selection.collapse_to(
1365 map.prev_line_boundary(selection.start.to_point(map)).1,
1366 SelectionGoal::None,
1367 )
1368 });
1369 }
1370 Some(Mode::VisualBlock) => {
1371 let mut first = s.first_anchor();
1372 first.collapse_to(first.start, first.goal);
1373 s.select_anchors(vec![first]);
1374 }
1375 _ => {
1376 s.move_with(|map, selection| {
1377 selection.collapse_to(
1378 map.clip_at_line_end(selection.start),
1379 selection.goal,
1380 );
1381 });
1382 }
1383 });
1384 });
1385 self.switch_mode(Mode::Normal, true, window, cx)
1386 }
1387 Mode::Normal => {
1388 self.update_editor(window, cx, |_, editor, window, cx| {
1389 editor.change_selections(None, window, cx, |s| {
1390 s.move_with(|map, selection| {
1391 selection
1392 .collapse_to(map.clip_at_line_end(selection.end), selection.goal)
1393 })
1394 })
1395 });
1396 }
1397 Mode::Insert | Mode::Replace | Mode::HelixNormal => {}
1398 }
1399 }
1400
1401 fn local_selections_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
1402 let Some(editor) = self.editor() else { return };
1403
1404 if editor.read(cx).leader_peer_id().is_some() {
1405 return;
1406 }
1407
1408 let newest = editor.read(cx).selections.newest_anchor().clone();
1409 let is_multicursor = editor.read(cx).selections.count() > 1;
1410 if self.mode == Mode::Insert && self.current_tx.is_some() {
1411 if self.current_anchor.is_none() {
1412 self.current_anchor = Some(newest);
1413 } else if self.current_anchor.as_ref().unwrap() != &newest {
1414 if let Some(tx_id) = self.current_tx.take() {
1415 self.update_editor(window, cx, |_, editor, _, cx| {
1416 editor.group_until_transaction(tx_id, cx)
1417 });
1418 }
1419 }
1420 } else if self.mode == Mode::Normal && newest.start != newest.end {
1421 if matches!(newest.goal, SelectionGoal::HorizontalRange { .. }) {
1422 self.switch_mode(Mode::VisualBlock, false, window, cx);
1423 } else {
1424 self.switch_mode(Mode::Visual, false, window, cx)
1425 }
1426 } else if newest.start == newest.end
1427 && !is_multicursor
1428 && [Mode::Visual, Mode::VisualLine, Mode::VisualBlock].contains(&self.mode)
1429 {
1430 self.switch_mode(Mode::Normal, true, window, cx);
1431 }
1432 }
1433
1434 fn input_ignored(&mut self, text: Arc<str>, window: &mut Window, cx: &mut Context<Self>) {
1435 if text.is_empty() {
1436 return;
1437 }
1438
1439 match self.active_operator() {
1440 Some(Operator::FindForward { before }) => {
1441 let find = Motion::FindForward {
1442 before,
1443 char: text.chars().next().unwrap(),
1444 mode: if VimSettings::get_global(cx).use_multiline_find {
1445 FindRange::MultiLine
1446 } else {
1447 FindRange::SingleLine
1448 },
1449 smartcase: VimSettings::get_global(cx).use_smartcase_find,
1450 };
1451 Vim::globals(cx).last_find = Some(find.clone());
1452 self.motion(find, window, cx)
1453 }
1454 Some(Operator::FindBackward { after }) => {
1455 let find = Motion::FindBackward {
1456 after,
1457 char: text.chars().next().unwrap(),
1458 mode: if VimSettings::get_global(cx).use_multiline_find {
1459 FindRange::MultiLine
1460 } else {
1461 FindRange::SingleLine
1462 },
1463 smartcase: VimSettings::get_global(cx).use_smartcase_find,
1464 };
1465 Vim::globals(cx).last_find = Some(find.clone());
1466 self.motion(find, window, cx)
1467 }
1468 Some(Operator::Sneak { first_char }) => {
1469 if let Some(first_char) = first_char {
1470 if let Some(second_char) = text.chars().next() {
1471 let sneak = Motion::Sneak {
1472 first_char,
1473 second_char,
1474 smartcase: VimSettings::get_global(cx).use_smartcase_find,
1475 };
1476 Vim::globals(cx).last_find = Some((&sneak).clone());
1477 self.motion(sneak, window, cx)
1478 }
1479 } else {
1480 let first_char = text.chars().next();
1481 self.pop_operator(window, cx);
1482 self.push_operator(Operator::Sneak { first_char }, window, cx);
1483 }
1484 }
1485 Some(Operator::SneakBackward { first_char }) => {
1486 if let Some(first_char) = first_char {
1487 if let Some(second_char) = text.chars().next() {
1488 let sneak = Motion::SneakBackward {
1489 first_char,
1490 second_char,
1491 smartcase: VimSettings::get_global(cx).use_smartcase_find,
1492 };
1493 Vim::globals(cx).last_find = Some((&sneak).clone());
1494 self.motion(sneak, window, cx)
1495 }
1496 } else {
1497 let first_char = text.chars().next();
1498 self.pop_operator(window, cx);
1499 self.push_operator(Operator::SneakBackward { first_char }, window, cx);
1500 }
1501 }
1502 Some(Operator::Replace) => match self.mode {
1503 Mode::Normal => self.normal_replace(text, window, cx),
1504 Mode::Visual | Mode::VisualLine | Mode::VisualBlock => {
1505 self.visual_replace(text, window, cx)
1506 }
1507 _ => self.clear_operator(window, cx),
1508 },
1509 Some(Operator::Digraph { first_char }) => {
1510 if let Some(first_char) = first_char {
1511 if let Some(second_char) = text.chars().next() {
1512 self.insert_digraph(first_char, second_char, window, cx);
1513 }
1514 } else {
1515 let first_char = text.chars().next();
1516 self.pop_operator(window, cx);
1517 self.push_operator(Operator::Digraph { first_char }, window, cx);
1518 }
1519 }
1520 Some(Operator::Literal { prefix }) => {
1521 self.handle_literal_input(prefix.unwrap_or_default(), &text, window, cx)
1522 }
1523 Some(Operator::AddSurrounds { target }) => match self.mode {
1524 Mode::Normal => {
1525 if let Some(target) = target {
1526 self.add_surrounds(text, target, window, cx);
1527 self.clear_operator(window, cx);
1528 }
1529 }
1530 Mode::Visual | Mode::VisualLine | Mode::VisualBlock => {
1531 self.add_surrounds(text, SurroundsType::Selection, window, cx);
1532 self.clear_operator(window, cx);
1533 }
1534 _ => self.clear_operator(window, cx),
1535 },
1536 Some(Operator::ChangeSurrounds { target }) => match self.mode {
1537 Mode::Normal => {
1538 if let Some(target) = target {
1539 self.change_surrounds(text, target, window, cx);
1540 self.clear_operator(window, cx);
1541 }
1542 }
1543 _ => self.clear_operator(window, cx),
1544 },
1545 Some(Operator::DeleteSurrounds) => match self.mode {
1546 Mode::Normal => {
1547 self.delete_surrounds(text, window, cx);
1548 self.clear_operator(window, cx);
1549 }
1550 _ => self.clear_operator(window, cx),
1551 },
1552 Some(Operator::Mark) => self.create_mark(text, false, window, cx),
1553 Some(Operator::RecordRegister) => {
1554 self.record_register(text.chars().next().unwrap(), window, cx)
1555 }
1556 Some(Operator::ReplayRegister) => {
1557 self.replay_register(text.chars().next().unwrap(), window, cx)
1558 }
1559 Some(Operator::Register) => match self.mode {
1560 Mode::Insert => {
1561 self.update_editor(window, cx, |_, editor, window, cx| {
1562 if let Some(register) = Vim::update_globals(cx, |globals, cx| {
1563 globals.read_register(text.chars().next(), Some(editor), cx)
1564 }) {
1565 editor.do_paste(
1566 ®ister.text.to_string(),
1567 register.clipboard_selections.clone(),
1568 false,
1569 window,
1570 cx,
1571 )
1572 }
1573 });
1574 self.clear_operator(window, cx);
1575 }
1576 _ => {
1577 self.select_register(text, window, cx);
1578 }
1579 },
1580 Some(Operator::Jump { line }) => self.jump(text, line, window, cx),
1581 _ => {
1582 if self.mode == Mode::Replace {
1583 self.multi_replace(text, window, cx)
1584 }
1585
1586 if self.mode == Mode::Normal {
1587 self.update_editor(window, cx, |_, editor, window, cx| {
1588 editor.accept_edit_prediction(
1589 &editor::actions::AcceptEditPrediction {},
1590 window,
1591 cx,
1592 );
1593 });
1594 }
1595 }
1596 }
1597 }
1598
1599 fn sync_vim_settings(&mut self, window: &mut Window, cx: &mut Context<Self>) {
1600 self.update_editor(window, cx, |vim, editor, window, cx| {
1601 editor.set_cursor_shape(vim.cursor_shape(), cx);
1602 editor.set_clip_at_line_ends(vim.clip_at_line_ends(), cx);
1603 editor.set_collapse_matches(true);
1604 editor.set_input_enabled(vim.editor_input_enabled());
1605 editor.set_autoindent(vim.should_autoindent());
1606 editor.selections.line_mode = matches!(vim.mode, Mode::VisualLine);
1607
1608 let hide_inline_completions = match vim.mode {
1609 Mode::Insert | Mode::Replace => false,
1610 _ => true,
1611 };
1612 editor.set_inline_completions_hidden_for_vim_mode(hide_inline_completions, window, cx);
1613 });
1614 cx.notify()
1615 }
1616}
1617
1618/// Controls when to use system clipboard.
1619#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
1620#[serde(rename_all = "snake_case")]
1621pub enum UseSystemClipboard {
1622 /// Don't use system clipboard.
1623 Never,
1624 /// Use system clipboard.
1625 Always,
1626 /// Use system clipboard for yank operations.
1627 OnYank,
1628}
1629
1630#[derive(Deserialize)]
1631struct VimSettings {
1632 pub toggle_relative_line_numbers: bool,
1633 pub use_system_clipboard: UseSystemClipboard,
1634 pub use_multiline_find: bool,
1635 pub use_smartcase_find: bool,
1636 pub custom_digraphs: HashMap<String, Arc<str>>,
1637 pub highlight_on_yank_duration: u64,
1638}
1639
1640#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
1641struct VimSettingsContent {
1642 pub toggle_relative_line_numbers: Option<bool>,
1643 pub use_system_clipboard: Option<UseSystemClipboard>,
1644 pub use_multiline_find: Option<bool>,
1645 pub use_smartcase_find: Option<bool>,
1646 pub custom_digraphs: Option<HashMap<String, Arc<str>>>,
1647 pub highlight_on_yank_duration: Option<u64>,
1648}
1649
1650impl Settings for VimSettings {
1651 const KEY: Option<&'static str> = Some("vim");
1652
1653 type FileContent = VimSettingsContent;
1654
1655 fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
1656 sources.json_merge()
1657 }
1658}