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