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