1//! Vim support for Zed.
2
3#[cfg(test)]
4mod test;
5
6mod change_list;
7mod command;
8mod digraph;
9mod helix;
10mod indent;
11mod insert;
12mod mode_indicator;
13mod motion;
14mod normal;
15mod object;
16mod replace;
17mod rewrap;
18mod state;
19mod surrounds;
20mod visual;
21
22use anyhow::Result;
23use collections::HashMap;
24use editor::{
25 movement::{self, FindRange},
26 Anchor, Bias, Editor, EditorEvent, EditorMode, ToPoint,
27};
28use gpui::{
29 actions, impl_actions, Action, App, AppContext as _, Axis, Context, Entity, EventEmitter,
30 KeyContext, KeystrokeEvent, Render, Subscription, Task, WeakEntity, Window,
31};
32use insert::{NormalBefore, TemporaryNormal};
33use language::{CursorShape, Point, Selection, SelectionGoal, TransactionId};
34pub use mode_indicator::ModeIndicator;
35use motion::Motion;
36use normal::search::SearchSubmit;
37use object::Object;
38use schemars::JsonSchema;
39use serde::Deserialize;
40use serde_derive::Serialize;
41use settings::{update_settings_file, Settings, SettingsSources, SettingsStore};
42use state::{Mode, Operator, RecordedSelection, SearchState, VimGlobals};
43use std::{mem, ops::Range, sync::Arc};
44use surrounds::SurroundsType;
45use theme::ThemeSettings;
46use ui::{px, IntoElement, SharedString};
47use vim_mode_setting::VimModeSetting;
48use workspace::{self, Pane, Workspace};
49
50use crate::state::ReplayableAction;
51
52/// Number is used to manage vim's count. Pushing a digit
53/// multiplies the current value by 10 and adds the digit.
54#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
55struct Number(usize);
56
57#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
58struct SelectRegister(String);
59
60#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
61#[serde(deny_unknown_fields)]
62struct PushObject {
63 around: bool,
64}
65
66#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
67#[serde(deny_unknown_fields)]
68struct PushFindForward {
69 before: bool,
70}
71
72#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
73#[serde(deny_unknown_fields)]
74struct PushFindBackward {
75 after: bool,
76}
77
78#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
79#[serde(deny_unknown_fields)]
80struct PushSneak {
81 first_char: Option<char>,
82}
83
84#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
85#[serde(deny_unknown_fields)]
86struct PushSneakBackward {
87 first_char: Option<char>,
88}
89
90#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
91#[serde(deny_unknown_fields)]
92struct PushAddSurrounds {}
93
94#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
95#[serde(deny_unknown_fields)]
96struct PushChangeSurrounds {
97 target: Option<Object>,
98}
99
100#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
101#[serde(deny_unknown_fields)]
102struct PushJump {
103 line: bool,
104}
105
106#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
107#[serde(deny_unknown_fields)]
108struct PushDigraph {
109 first_char: Option<char>,
110}
111
112#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
113#[serde(deny_unknown_fields)]
114struct PushLiteral {
115 prefix: Option<String>,
116}
117
118actions!(
119 vim,
120 [
121 SwitchToNormalMode,
122 SwitchToInsertMode,
123 SwitchToReplaceMode,
124 SwitchToVisualMode,
125 SwitchToVisualLineMode,
126 SwitchToVisualBlockMode,
127 SwitchToHelixNormalMode,
128 ClearOperators,
129 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 PushRegister,
156 PushRecordRegister,
157 PushReplayRegister,
158 PushReplaceWithRegister,
159 PushToggleComments,
160 ]
161);
162
163// in the workspace namespace so it's not filtered out when vim is disabled.
164actions!(workspace, [ToggleVimMode,]);
165
166impl_actions!(
167 vim,
168 [
169 Number,
170 SelectRegister,
171 PushObject,
172 PushFindForward,
173 PushFindBackward,
174 PushSneak,
175 PushSneakBackward,
176 PushAddSurrounds,
177 PushChangeSurrounds,
178 PushJump,
179 PushDigraph,
180 PushLiteral
181 ]
182);
183
184/// Initializes the `vim` crate.
185pub fn init(cx: &mut App) {
186 vim_mode_setting::init(cx);
187 VimSettings::register(cx);
188 VimGlobals::register(cx);
189
190 cx.observe_new(Vim::register).detach();
191
192 cx.observe_new(|workspace: &mut Workspace, _, _| {
193 workspace.register_action(|workspace, _: &ToggleVimMode, _, cx| {
194 let fs = workspace.app_state().fs.clone();
195 let currently_enabled = Vim::enabled(cx);
196 update_settings_file::<VimModeSetting>(fs, cx, move |setting, _| {
197 *setting = Some(!currently_enabled)
198 })
199 });
200
201 workspace.register_action(|_, _: &OpenDefaultKeymap, _, cx| {
202 cx.emit(workspace::Event::OpenBundledFile {
203 text: settings::vim_keymap(),
204 title: "Default Vim Bindings",
205 language: "JSON",
206 });
207 });
208
209 workspace.register_action(|workspace, _: &ResetPaneSizes, _, cx| {
210 workspace.reset_pane_sizes(cx);
211 });
212
213 workspace.register_action(|workspace, _: &MaximizePane, window, cx| {
214 let pane = workspace.active_pane();
215 let Some(size) = workspace.bounding_box_for_pane(&pane) else {
216 return;
217 };
218
219 let theme = ThemeSettings::get_global(cx);
220 let height = theme.buffer_font_size(cx) * theme.buffer_line_height.value();
221
222 let desired_size = if let Some(count) = Vim::take_count(cx) {
223 height * count
224 } else {
225 px(10000.)
226 };
227 workspace.resize_pane(Axis::Vertical, desired_size - size.size.height, window, cx)
228 });
229
230 workspace.register_action(|workspace, _: &ResizePaneRight, window, cx| {
231 let count = Vim::take_count(cx).unwrap_or(1) as f32;
232 let theme = ThemeSettings::get_global(cx);
233 let Ok(font_id) = window.text_system().font_id(&theme.buffer_font) else {
234 return;
235 };
236 let Ok(width) = window
237 .text_system()
238 .advance(font_id, theme.buffer_font_size(cx), 'm')
239 else {
240 return;
241 };
242 workspace.resize_pane(Axis::Horizontal, width.width * count, window, cx);
243 });
244
245 workspace.register_action(|workspace, _: &ResizePaneLeft, window, cx| {
246 let count = Vim::take_count(cx).unwrap_or(1) as f32;
247 let theme = ThemeSettings::get_global(cx);
248 let Ok(font_id) = window.text_system().font_id(&theme.buffer_font) else {
249 return;
250 };
251 let Ok(width) = window
252 .text_system()
253 .advance(font_id, theme.buffer_font_size(cx), 'm')
254 else {
255 return;
256 };
257 workspace.resize_pane(Axis::Horizontal, -width.width * count, window, cx);
258 });
259
260 workspace.register_action(|workspace, _: &ResizePaneUp, window, cx| {
261 let count = Vim::take_count(cx).unwrap_or(1) as f32;
262 let theme = ThemeSettings::get_global(cx);
263 let height = theme.buffer_font_size(cx) * theme.buffer_line_height.value();
264 workspace.resize_pane(Axis::Vertical, height * count, window, cx);
265 });
266
267 workspace.register_action(|workspace, _: &ResizePaneDown, window, cx| {
268 let count = Vim::take_count(cx).unwrap_or(1) as f32;
269 let theme = ThemeSettings::get_global(cx);
270 let height = theme.buffer_font_size(cx) * theme.buffer_line_height.value();
271 workspace.resize_pane(Axis::Vertical, -height * count, window, cx);
272 });
273
274 workspace.register_action(|workspace, _: &SearchSubmit, window, cx| {
275 let vim = workspace
276 .focused_pane(window, cx)
277 .read(cx)
278 .active_item()
279 .and_then(|item| item.act_as::<Editor>(cx))
280 .and_then(|editor| editor.read(cx).addon::<VimAddon>().cloned());
281 let Some(vim) = vim else { return };
282 vim.entity.update(cx, |_, cx| {
283 cx.defer_in(window, |vim, window, cx| vim.search_submit(window, cx))
284 })
285 });
286 })
287 .detach();
288}
289
290#[derive(Clone)]
291pub(crate) struct VimAddon {
292 pub(crate) entity: Entity<Vim>,
293}
294
295impl editor::Addon for VimAddon {
296 fn extend_key_context(&self, key_context: &mut KeyContext, cx: &App) {
297 self.entity.read(cx).extend_key_context(key_context, cx)
298 }
299
300 fn to_any(&self) -> &dyn std::any::Any {
301 self
302 }
303}
304
305/// The state pertaining to Vim mode.
306pub(crate) struct Vim {
307 pub(crate) mode: Mode,
308 pub last_mode: Mode,
309 pub temp_mode: bool,
310 pub status_label: Option<SharedString>,
311 pub exit_temporary_mode: bool,
312
313 operator_stack: Vec<Operator>,
314 pub(crate) replacements: Vec<(Range<editor::Anchor>, String)>,
315
316 pub(crate) marks: HashMap<String, Vec<Anchor>>,
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 marks: HashMap::default(),
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 matches!(
831 operator,
832 Operator::Change
833 | Operator::Delete
834 | Operator::Replace
835 | Operator::Indent
836 | Operator::Outdent
837 | Operator::AutoIndent
838 | Operator::Lowercase
839 | Operator::Uppercase
840 | Operator::OppositeCase
841 | Operator::ToggleComments
842 | Operator::ReplaceWithRegister
843 ) {
844 self.start_recording(cx)
845 };
846 // Since these operations can only be entered with pre-operators,
847 // we need to clear the previous operators when pushing,
848 // so that the current stack is the most correct
849 if matches!(
850 operator,
851 Operator::AddSurrounds { .. }
852 | Operator::ChangeSurrounds { .. }
853 | Operator::DeleteSurrounds
854 ) {
855 self.operator_stack.clear();
856 if let Operator::AddSurrounds { target: None } = operator {
857 self.start_recording(cx);
858 }
859 };
860 self.operator_stack.push(operator);
861 self.sync_vim_settings(window, cx);
862 }
863
864 pub fn switch_mode(
865 &mut self,
866 mode: Mode,
867 leave_selections: bool,
868 window: &mut Window,
869 cx: &mut Context<Self>,
870 ) {
871 if self.temp_mode && mode == Mode::Normal {
872 self.temp_mode = false;
873 self.switch_mode(Mode::Normal, leave_selections, window, cx);
874 self.switch_mode(Mode::Insert, false, window, cx);
875 return;
876 } else if self.temp_mode
877 && !matches!(mode, Mode::Visual | Mode::VisualLine | Mode::VisualBlock)
878 {
879 self.temp_mode = false;
880 }
881
882 let last_mode = self.mode;
883 let prior_mode = self.last_mode;
884 let prior_tx = self.current_tx;
885 self.status_label.take();
886 self.last_mode = last_mode;
887 self.mode = mode;
888 self.operator_stack.clear();
889 self.selected_register.take();
890 self.cancel_running_command(window, cx);
891 if mode == Mode::Normal || mode != last_mode {
892 self.current_tx.take();
893 self.current_anchor.take();
894 }
895 if mode != Mode::Insert && mode != Mode::Replace {
896 Vim::take_count(cx);
897 }
898
899 // Sync editor settings like clip mode
900 self.sync_vim_settings(window, cx);
901
902 if VimSettings::get_global(cx).toggle_relative_line_numbers
903 && self.mode != self.last_mode
904 && (self.mode == Mode::Insert || self.last_mode == Mode::Insert)
905 {
906 self.update_editor(window, cx, |vim, editor, _, cx| {
907 let is_relative = vim.mode != Mode::Insert;
908 editor.set_relative_line_number(Some(is_relative), cx)
909 });
910 }
911
912 if leave_selections {
913 return;
914 }
915
916 if !mode.is_visual() && last_mode.is_visual() {
917 self.create_visual_marks(last_mode, window, cx);
918 }
919
920 // Adjust selections
921 self.update_editor(window, cx, |vim, editor, window, cx| {
922 if last_mode != Mode::VisualBlock && last_mode.is_visual() && mode == Mode::VisualBlock
923 {
924 vim.visual_block_motion(true, editor, window, cx, |_, point, goal| {
925 Some((point, goal))
926 })
927 }
928 if last_mode == Mode::Insert || last_mode == Mode::Replace {
929 if let Some(prior_tx) = prior_tx {
930 editor.group_until_transaction(prior_tx, cx)
931 }
932 }
933
934 editor.change_selections(None, window, cx, |s| {
935 // we cheat with visual block mode and use multiple cursors.
936 // the cost of this cheat is we need to convert back to a single
937 // cursor whenever vim would.
938 if last_mode == Mode::VisualBlock
939 && (mode != Mode::VisualBlock && mode != Mode::Insert)
940 {
941 let tail = s.oldest_anchor().tail();
942 let head = s.newest_anchor().head();
943 s.select_anchor_ranges(vec![tail..head]);
944 } else if last_mode == Mode::Insert
945 && prior_mode == Mode::VisualBlock
946 && mode != Mode::VisualBlock
947 {
948 let pos = s.first_anchor().head();
949 s.select_anchor_ranges(vec![pos..pos])
950 }
951
952 let snapshot = s.display_map();
953 if let Some(pending) = s.pending.as_mut() {
954 if pending.selection.reversed && mode.is_visual() && !last_mode.is_visual() {
955 let mut end = pending.selection.end.to_point(&snapshot.buffer_snapshot);
956 end = snapshot
957 .buffer_snapshot
958 .clip_point(end + Point::new(0, 1), Bias::Right);
959 pending.selection.end = snapshot.buffer_snapshot.anchor_before(end);
960 }
961 }
962
963 s.move_with(|map, selection| {
964 if last_mode.is_visual() && !mode.is_visual() {
965 let mut point = selection.head();
966 if !selection.reversed && !selection.is_empty() {
967 point = movement::left(map, selection.head());
968 }
969 selection.collapse_to(point, selection.goal)
970 } else if !last_mode.is_visual() && mode.is_visual() && selection.is_empty() {
971 selection.end = movement::right(map, selection.start);
972 }
973 });
974 })
975 });
976 }
977
978 pub fn take_count(cx: &mut App) -> Option<usize> {
979 let global_state = cx.global_mut::<VimGlobals>();
980 if global_state.dot_replaying {
981 return global_state.recorded_count;
982 }
983
984 let count = if global_state.post_count.is_none() && global_state.pre_count.is_none() {
985 return None;
986 } else {
987 Some(
988 global_state.post_count.take().unwrap_or(1)
989 * global_state.pre_count.take().unwrap_or(1),
990 )
991 };
992
993 if global_state.dot_recording {
994 global_state.recorded_count = count;
995 }
996 count
997 }
998
999 pub fn cursor_shape(&self) -> CursorShape {
1000 match self.mode {
1001 Mode::Normal => {
1002 if let Some(operator) = self.operator_stack.last() {
1003 match operator {
1004 // Navigation operators -> Block cursor
1005 Operator::FindForward { .. }
1006 | Operator::FindBackward { .. }
1007 | Operator::Mark
1008 | Operator::Jump { .. }
1009 | Operator::Register
1010 | Operator::RecordRegister
1011 | Operator::ReplayRegister => CursorShape::Block,
1012
1013 // All other operators -> Underline cursor
1014 _ => CursorShape::Underline,
1015 }
1016 } else {
1017 // No operator active -> Block cursor
1018 CursorShape::Block
1019 }
1020 }
1021 Mode::Replace => CursorShape::Underline,
1022 Mode::HelixNormal | Mode::Visual | Mode::VisualLine | Mode::VisualBlock => {
1023 CursorShape::Block
1024 }
1025 Mode::Insert => CursorShape::Bar,
1026 }
1027 }
1028
1029 pub fn editor_input_enabled(&self) -> bool {
1030 match self.mode {
1031 Mode::Insert => {
1032 if let Some(operator) = self.operator_stack.last() {
1033 !operator.is_waiting(self.mode)
1034 } else {
1035 true
1036 }
1037 }
1038 Mode::Normal
1039 | Mode::HelixNormal
1040 | Mode::Replace
1041 | Mode::Visual
1042 | Mode::VisualLine
1043 | Mode::VisualBlock => false,
1044 }
1045 }
1046
1047 pub fn should_autoindent(&self) -> bool {
1048 !(self.mode == Mode::Insert && self.last_mode == Mode::VisualBlock)
1049 }
1050
1051 pub fn clip_at_line_ends(&self) -> bool {
1052 match self.mode {
1053 Mode::Insert
1054 | Mode::Visual
1055 | Mode::VisualLine
1056 | Mode::VisualBlock
1057 | Mode::Replace
1058 | Mode::HelixNormal => false,
1059 Mode::Normal => true,
1060 }
1061 }
1062
1063 pub fn extend_key_context(&self, context: &mut KeyContext, cx: &App) {
1064 let mut mode = match self.mode {
1065 Mode::Normal => "normal",
1066 Mode::Visual | Mode::VisualLine | Mode::VisualBlock => "visual",
1067 Mode::Insert => "insert",
1068 Mode::Replace => "replace",
1069 Mode::HelixNormal => "helix_normal",
1070 }
1071 .to_string();
1072
1073 let mut operator_id = "none";
1074
1075 let active_operator = self.active_operator();
1076 if active_operator.is_none() && cx.global::<VimGlobals>().pre_count.is_some()
1077 || active_operator.is_some() && cx.global::<VimGlobals>().post_count.is_some()
1078 {
1079 context.add("VimCount");
1080 }
1081
1082 if let Some(active_operator) = active_operator {
1083 if active_operator.is_waiting(self.mode) {
1084 if matches!(active_operator, Operator::Literal { .. }) {
1085 mode = "literal".to_string();
1086 } else {
1087 mode = "waiting".to_string();
1088 }
1089 } else {
1090 operator_id = active_operator.id();
1091 mode = "operator".to_string();
1092 }
1093 }
1094
1095 if mode == "normal" || mode == "visual" || mode == "operator" {
1096 context.add("VimControl");
1097 }
1098 context.set("vim_mode", mode);
1099 context.set("vim_operator", operator_id);
1100 }
1101
1102 fn focused(&mut self, preserve_selection: bool, window: &mut Window, cx: &mut Context<Self>) {
1103 let Some(editor) = self.editor() else {
1104 return;
1105 };
1106 let newest_selection_empty = editor.update(cx, |editor, cx| {
1107 editor.selections.newest::<usize>(cx).is_empty()
1108 });
1109 let editor = editor.read(cx);
1110 let editor_mode = editor.mode();
1111
1112 if editor_mode == EditorMode::Full
1113 && !newest_selection_empty
1114 && self.mode == Mode::Normal
1115 // When following someone, don't switch vim mode.
1116 && editor.leader_peer_id().is_none()
1117 {
1118 if preserve_selection {
1119 self.switch_mode(Mode::Visual, true, window, cx);
1120 } else {
1121 self.update_editor(window, cx, |_, editor, window, cx| {
1122 editor.set_clip_at_line_ends(false, cx);
1123 editor.change_selections(None, window, cx, |s| {
1124 s.move_with(|_, selection| {
1125 selection.collapse_to(selection.start, selection.goal)
1126 })
1127 });
1128 });
1129 }
1130 }
1131
1132 cx.emit(VimEvent::Focused);
1133 self.sync_vim_settings(window, cx);
1134
1135 if VimSettings::get_global(cx).toggle_relative_line_numbers {
1136 if let Some(old_vim) = Vim::globals(cx).focused_vim() {
1137 if old_vim.entity_id() != cx.entity().entity_id() {
1138 old_vim.update(cx, |vim, cx| {
1139 vim.update_editor(window, cx, |_, editor, _, cx| {
1140 editor.set_relative_line_number(None, cx)
1141 });
1142 });
1143
1144 self.update_editor(window, cx, |vim, editor, _, cx| {
1145 let is_relative = vim.mode != Mode::Insert;
1146 editor.set_relative_line_number(Some(is_relative), cx)
1147 });
1148 }
1149 } else {
1150 self.update_editor(window, cx, |vim, editor, _, cx| {
1151 let is_relative = vim.mode != Mode::Insert;
1152 editor.set_relative_line_number(Some(is_relative), cx)
1153 });
1154 }
1155 }
1156 Vim::globals(cx).focused_vim = Some(cx.entity().downgrade());
1157 }
1158
1159 fn blurred(&mut self, window: &mut Window, cx: &mut Context<Self>) {
1160 self.stop_recording_immediately(NormalBefore.boxed_clone(), cx);
1161 self.store_visual_marks(window, cx);
1162 self.clear_operator(window, cx);
1163 self.update_editor(window, cx, |vim, editor, _, cx| {
1164 if vim.cursor_shape() == CursorShape::Block {
1165 editor.set_cursor_shape(CursorShape::Hollow, cx);
1166 }
1167 });
1168 }
1169
1170 fn cursor_shape_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
1171 self.update_editor(window, cx, |vim, editor, _, cx| {
1172 editor.set_cursor_shape(vim.cursor_shape(), cx);
1173 });
1174 }
1175
1176 fn update_editor<S>(
1177 &mut self,
1178 window: &mut Window,
1179 cx: &mut Context<Self>,
1180 update: impl FnOnce(&mut Self, &mut Editor, &mut Window, &mut Context<Editor>) -> S,
1181 ) -> Option<S> {
1182 let editor = self.editor.upgrade()?;
1183 Some(editor.update(cx, |editor, cx| update(self, editor, window, cx)))
1184 }
1185
1186 fn editor_selections(
1187 &mut self,
1188 window: &mut Window,
1189 cx: &mut Context<Self>,
1190 ) -> Vec<Range<Anchor>> {
1191 self.update_editor(window, cx, |_, editor, _, _| {
1192 editor
1193 .selections
1194 .disjoint_anchors()
1195 .iter()
1196 .map(|selection| selection.tail()..selection.head())
1197 .collect()
1198 })
1199 .unwrap_or_default()
1200 }
1201
1202 /// When doing an action that modifies the buffer, we start recording so that `.`
1203 /// will replay the action.
1204 pub fn start_recording(&mut self, cx: &mut Context<Self>) {
1205 Vim::update_globals(cx, |globals, cx| {
1206 if !globals.dot_replaying {
1207 globals.dot_recording = true;
1208 globals.recording_actions = Default::default();
1209 globals.recorded_count = None;
1210
1211 let selections = self.editor().map(|editor| {
1212 editor.update(cx, |editor, cx| {
1213 (
1214 editor.selections.oldest::<Point>(cx),
1215 editor.selections.newest::<Point>(cx),
1216 )
1217 })
1218 });
1219
1220 if let Some((oldest, newest)) = selections {
1221 globals.recorded_selection = match self.mode {
1222 Mode::Visual if newest.end.row == newest.start.row => {
1223 RecordedSelection::SingleLine {
1224 cols: newest.end.column - newest.start.column,
1225 }
1226 }
1227 Mode::Visual => RecordedSelection::Visual {
1228 rows: newest.end.row - newest.start.row,
1229 cols: newest.end.column,
1230 },
1231 Mode::VisualLine => RecordedSelection::VisualLine {
1232 rows: newest.end.row - newest.start.row,
1233 },
1234 Mode::VisualBlock => RecordedSelection::VisualBlock {
1235 rows: newest.end.row.abs_diff(oldest.start.row),
1236 cols: newest.end.column.abs_diff(oldest.start.column),
1237 },
1238 _ => RecordedSelection::None,
1239 }
1240 } else {
1241 globals.recorded_selection = RecordedSelection::None;
1242 }
1243 }
1244 })
1245 }
1246
1247 pub fn stop_replaying(&mut self, cx: &mut Context<Self>) {
1248 let globals = Vim::globals(cx);
1249 globals.dot_replaying = false;
1250 if let Some(replayer) = globals.replayer.take() {
1251 replayer.stop();
1252 }
1253 }
1254
1255 /// When finishing an action that modifies the buffer, stop recording.
1256 /// as you usually call this within a keystroke handler we also ensure that
1257 /// the current action is recorded.
1258 pub fn stop_recording(&mut self, cx: &mut Context<Self>) {
1259 let globals = Vim::globals(cx);
1260 if globals.dot_recording {
1261 globals.stop_recording_after_next_action = true;
1262 }
1263 self.exit_temporary_mode = self.temp_mode;
1264 }
1265
1266 /// Stops recording actions immediately rather than waiting until after the
1267 /// next action to stop recording.
1268 ///
1269 /// This doesn't include the current action.
1270 pub fn stop_recording_immediately(&mut self, action: Box<dyn Action>, cx: &mut Context<Self>) {
1271 let globals = Vim::globals(cx);
1272 if globals.dot_recording {
1273 globals
1274 .recording_actions
1275 .push(ReplayableAction::Action(action.boxed_clone()));
1276 globals.recorded_actions = mem::take(&mut globals.recording_actions);
1277 globals.dot_recording = false;
1278 globals.stop_recording_after_next_action = false;
1279 }
1280 self.exit_temporary_mode = self.temp_mode;
1281 }
1282
1283 /// Explicitly record one action (equivalents to start_recording and stop_recording)
1284 pub fn record_current_action(&mut self, cx: &mut Context<Self>) {
1285 self.start_recording(cx);
1286 self.stop_recording(cx);
1287 }
1288
1289 fn push_count_digit(&mut self, number: usize, window: &mut Window, cx: &mut Context<Self>) {
1290 if self.active_operator().is_some() {
1291 let post_count = Vim::globals(cx).post_count.unwrap_or(0);
1292
1293 Vim::globals(cx).post_count = Some(
1294 post_count
1295 .checked_mul(10)
1296 .and_then(|post_count| post_count.checked_add(number))
1297 .unwrap_or(post_count),
1298 )
1299 } else {
1300 let pre_count = Vim::globals(cx).pre_count.unwrap_or(0);
1301
1302 Vim::globals(cx).pre_count = Some(
1303 pre_count
1304 .checked_mul(10)
1305 .and_then(|pre_count| pre_count.checked_add(number))
1306 .unwrap_or(pre_count),
1307 )
1308 }
1309 // update the keymap so that 0 works
1310 self.sync_vim_settings(window, cx)
1311 }
1312
1313 fn select_register(&mut self, register: Arc<str>, window: &mut Window, cx: &mut Context<Self>) {
1314 if register.chars().count() == 1 {
1315 self.selected_register
1316 .replace(register.chars().next().unwrap());
1317 }
1318 self.operator_stack.clear();
1319 self.sync_vim_settings(window, cx);
1320 }
1321
1322 fn maybe_pop_operator(&mut self) -> Option<Operator> {
1323 self.operator_stack.pop()
1324 }
1325
1326 fn pop_operator(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Operator {
1327 let popped_operator = self.operator_stack.pop()
1328 .expect("Operator popped when no operator was on the stack. This likely means there is an invalid keymap config");
1329 self.sync_vim_settings(window, cx);
1330 popped_operator
1331 }
1332
1333 fn clear_operator(&mut self, window: &mut Window, cx: &mut Context<Self>) {
1334 Vim::take_count(cx);
1335 self.selected_register.take();
1336 self.operator_stack.clear();
1337 self.sync_vim_settings(window, cx);
1338 }
1339
1340 fn active_operator(&self) -> Option<Operator> {
1341 self.operator_stack.last().cloned()
1342 }
1343
1344 fn transaction_begun(
1345 &mut self,
1346 transaction_id: TransactionId,
1347 _window: &mut Window,
1348 _: &mut Context<Self>,
1349 ) {
1350 let mode = if (self.mode == Mode::Insert
1351 || self.mode == Mode::Replace
1352 || self.mode == Mode::Normal)
1353 && self.current_tx.is_none()
1354 {
1355 self.current_tx = Some(transaction_id);
1356 self.last_mode
1357 } else {
1358 self.mode
1359 };
1360 if mode == Mode::VisualLine || mode == Mode::VisualBlock {
1361 self.undo_modes.insert(transaction_id, mode);
1362 }
1363 }
1364
1365 fn transaction_undone(
1366 &mut self,
1367 transaction_id: &TransactionId,
1368 window: &mut Window,
1369 cx: &mut Context<Self>,
1370 ) {
1371 match self.mode {
1372 Mode::VisualLine | Mode::VisualBlock | Mode::Visual => {
1373 self.update_editor(window, cx, |vim, editor, window, cx| {
1374 let original_mode = vim.undo_modes.get(transaction_id);
1375 editor.change_selections(None, window, cx, |s| match original_mode {
1376 Some(Mode::VisualLine) => {
1377 s.move_with(|map, selection| {
1378 selection.collapse_to(
1379 map.prev_line_boundary(selection.start.to_point(map)).1,
1380 SelectionGoal::None,
1381 )
1382 });
1383 }
1384 Some(Mode::VisualBlock) => {
1385 let mut first = s.first_anchor();
1386 first.collapse_to(first.start, first.goal);
1387 s.select_anchors(vec![first]);
1388 }
1389 _ => {
1390 s.move_with(|map, selection| {
1391 selection.collapse_to(
1392 map.clip_at_line_end(selection.start),
1393 selection.goal,
1394 );
1395 });
1396 }
1397 });
1398 });
1399 self.switch_mode(Mode::Normal, true, window, cx)
1400 }
1401 Mode::Normal => {
1402 self.update_editor(window, cx, |_, editor, window, cx| {
1403 editor.change_selections(None, window, cx, |s| {
1404 s.move_with(|map, selection| {
1405 selection
1406 .collapse_to(map.clip_at_line_end(selection.end), selection.goal)
1407 })
1408 })
1409 });
1410 }
1411 Mode::Insert | Mode::Replace | Mode::HelixNormal => {}
1412 }
1413 }
1414
1415 fn local_selections_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
1416 let Some(editor) = self.editor() else { return };
1417
1418 if editor.read(cx).leader_peer_id().is_some() {
1419 return;
1420 }
1421
1422 let newest = editor.read(cx).selections.newest_anchor().clone();
1423 let is_multicursor = editor.read(cx).selections.count() > 1;
1424 if self.mode == Mode::Insert && self.current_tx.is_some() {
1425 if self.current_anchor.is_none() {
1426 self.current_anchor = Some(newest);
1427 } else if self.current_anchor.as_ref().unwrap() != &newest {
1428 if let Some(tx_id) = self.current_tx.take() {
1429 self.update_editor(window, cx, |_, editor, _, cx| {
1430 editor.group_until_transaction(tx_id, cx)
1431 });
1432 }
1433 }
1434 } else if self.mode == Mode::Normal && newest.start != newest.end {
1435 if matches!(newest.goal, SelectionGoal::HorizontalRange { .. }) {
1436 self.switch_mode(Mode::VisualBlock, false, window, cx);
1437 } else {
1438 self.switch_mode(Mode::Visual, false, window, cx)
1439 }
1440 } else if newest.start == newest.end
1441 && !is_multicursor
1442 && [Mode::Visual, Mode::VisualLine, Mode::VisualBlock].contains(&self.mode)
1443 {
1444 self.switch_mode(Mode::Normal, true, window, cx);
1445 }
1446 }
1447
1448 fn input_ignored(&mut self, text: Arc<str>, window: &mut Window, cx: &mut Context<Self>) {
1449 if text.is_empty() {
1450 return;
1451 }
1452
1453 match self.active_operator() {
1454 Some(Operator::FindForward { before }) => {
1455 let find = Motion::FindForward {
1456 before,
1457 char: text.chars().next().unwrap(),
1458 mode: if VimSettings::get_global(cx).use_multiline_find {
1459 FindRange::MultiLine
1460 } else {
1461 FindRange::SingleLine
1462 },
1463 smartcase: VimSettings::get_global(cx).use_smartcase_find,
1464 };
1465 Vim::globals(cx).last_find = Some(find.clone());
1466 self.motion(find, window, cx)
1467 }
1468 Some(Operator::FindBackward { after }) => {
1469 let find = Motion::FindBackward {
1470 after,
1471 char: text.chars().next().unwrap(),
1472 mode: if VimSettings::get_global(cx).use_multiline_find {
1473 FindRange::MultiLine
1474 } else {
1475 FindRange::SingleLine
1476 },
1477 smartcase: VimSettings::get_global(cx).use_smartcase_find,
1478 };
1479 Vim::globals(cx).last_find = Some(find.clone());
1480 self.motion(find, window, cx)
1481 }
1482 Some(Operator::Sneak { first_char }) => {
1483 if let Some(first_char) = first_char {
1484 if let Some(second_char) = text.chars().next() {
1485 let sneak = Motion::Sneak {
1486 first_char,
1487 second_char,
1488 smartcase: VimSettings::get_global(cx).use_smartcase_find,
1489 };
1490 Vim::globals(cx).last_find = Some((&sneak).clone());
1491 self.motion(sneak, window, cx)
1492 }
1493 } else {
1494 let first_char = text.chars().next();
1495 self.pop_operator(window, cx);
1496 self.push_operator(Operator::Sneak { first_char }, window, cx);
1497 }
1498 }
1499 Some(Operator::SneakBackward { first_char }) => {
1500 if let Some(first_char) = first_char {
1501 if let Some(second_char) = text.chars().next() {
1502 let sneak = Motion::SneakBackward {
1503 first_char,
1504 second_char,
1505 smartcase: VimSettings::get_global(cx).use_smartcase_find,
1506 };
1507 Vim::globals(cx).last_find = Some((&sneak).clone());
1508 self.motion(sneak, window, cx)
1509 }
1510 } else {
1511 let first_char = text.chars().next();
1512 self.pop_operator(window, cx);
1513 self.push_operator(Operator::SneakBackward { first_char }, window, cx);
1514 }
1515 }
1516 Some(Operator::Replace) => match self.mode {
1517 Mode::Normal => self.normal_replace(text, window, cx),
1518 Mode::Visual | Mode::VisualLine | Mode::VisualBlock => {
1519 self.visual_replace(text, window, cx)
1520 }
1521 _ => self.clear_operator(window, cx),
1522 },
1523 Some(Operator::Digraph { first_char }) => {
1524 if let Some(first_char) = first_char {
1525 if let Some(second_char) = text.chars().next() {
1526 self.insert_digraph(first_char, second_char, window, cx);
1527 }
1528 } else {
1529 let first_char = text.chars().next();
1530 self.pop_operator(window, cx);
1531 self.push_operator(Operator::Digraph { first_char }, window, cx);
1532 }
1533 }
1534 Some(Operator::Literal { prefix }) => {
1535 self.handle_literal_input(prefix.unwrap_or_default(), &text, window, cx)
1536 }
1537 Some(Operator::AddSurrounds { target }) => match self.mode {
1538 Mode::Normal => {
1539 if let Some(target) = target {
1540 self.add_surrounds(text, target, window, cx);
1541 self.clear_operator(window, cx);
1542 }
1543 }
1544 Mode::Visual | Mode::VisualLine | Mode::VisualBlock => {
1545 self.add_surrounds(text, SurroundsType::Selection, window, cx);
1546 self.clear_operator(window, cx);
1547 }
1548 _ => self.clear_operator(window, cx),
1549 },
1550 Some(Operator::ChangeSurrounds { target }) => match self.mode {
1551 Mode::Normal => {
1552 if let Some(target) = target {
1553 self.change_surrounds(text, target, window, cx);
1554 self.clear_operator(window, cx);
1555 }
1556 }
1557 _ => self.clear_operator(window, cx),
1558 },
1559 Some(Operator::DeleteSurrounds) => match self.mode {
1560 Mode::Normal => {
1561 self.delete_surrounds(text, window, cx);
1562 self.clear_operator(window, cx);
1563 }
1564 _ => self.clear_operator(window, cx),
1565 },
1566 Some(Operator::Mark) => self.create_mark(text, false, window, cx),
1567 Some(Operator::RecordRegister) => {
1568 self.record_register(text.chars().next().unwrap(), window, cx)
1569 }
1570 Some(Operator::ReplayRegister) => {
1571 self.replay_register(text.chars().next().unwrap(), window, cx)
1572 }
1573 Some(Operator::Register) => match self.mode {
1574 Mode::Insert => {
1575 self.update_editor(window, cx, |_, editor, window, cx| {
1576 if let Some(register) = Vim::update_globals(cx, |globals, cx| {
1577 globals.read_register(text.chars().next(), Some(editor), cx)
1578 }) {
1579 editor.do_paste(
1580 ®ister.text.to_string(),
1581 register.clipboard_selections.clone(),
1582 false,
1583 window,
1584 cx,
1585 )
1586 }
1587 });
1588 self.clear_operator(window, cx);
1589 }
1590 _ => {
1591 self.select_register(text, window, cx);
1592 }
1593 },
1594 Some(Operator::Jump { line }) => self.jump(text, line, window, cx),
1595 _ => {
1596 if self.mode == Mode::Replace {
1597 self.multi_replace(text, window, cx)
1598 }
1599
1600 if self.mode == Mode::Normal {
1601 self.update_editor(window, cx, |_, editor, window, cx| {
1602 editor.accept_edit_prediction(
1603 &editor::actions::AcceptEditPrediction {},
1604 window,
1605 cx,
1606 );
1607 });
1608 }
1609 }
1610 }
1611 }
1612
1613 fn sync_vim_settings(&mut self, window: &mut Window, cx: &mut Context<Self>) {
1614 self.update_editor(window, cx, |vim, editor, window, cx| {
1615 editor.set_cursor_shape(vim.cursor_shape(), cx);
1616 editor.set_clip_at_line_ends(vim.clip_at_line_ends(), cx);
1617 editor.set_collapse_matches(true);
1618 editor.set_input_enabled(vim.editor_input_enabled());
1619 editor.set_autoindent(vim.should_autoindent());
1620 editor.selections.line_mode = matches!(vim.mode, Mode::VisualLine);
1621
1622 let hide_inline_completions = match vim.mode {
1623 Mode::Insert | Mode::Replace => false,
1624 _ => true,
1625 };
1626 editor.set_inline_completions_hidden_for_vim_mode(hide_inline_completions, window, cx);
1627 });
1628 cx.notify()
1629 }
1630}
1631
1632/// Controls when to use system clipboard.
1633#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
1634#[serde(rename_all = "snake_case")]
1635pub enum UseSystemClipboard {
1636 /// Don't use system clipboard.
1637 Never,
1638 /// Use system clipboard.
1639 Always,
1640 /// Use system clipboard for yank operations.
1641 OnYank,
1642}
1643
1644#[derive(Deserialize)]
1645struct VimSettings {
1646 pub default_mode: Mode,
1647 pub toggle_relative_line_numbers: bool,
1648 pub use_system_clipboard: UseSystemClipboard,
1649 pub use_multiline_find: bool,
1650 pub use_smartcase_find: bool,
1651 pub custom_digraphs: HashMap<String, Arc<str>>,
1652 pub highlight_on_yank_duration: u64,
1653}
1654
1655#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
1656struct VimSettingsContent {
1657 pub default_mode: Option<ModeContent>,
1658 pub toggle_relative_line_numbers: Option<bool>,
1659 pub use_system_clipboard: Option<UseSystemClipboard>,
1660 pub use_multiline_find: Option<bool>,
1661 pub use_smartcase_find: Option<bool>,
1662 pub custom_digraphs: Option<HashMap<String, Arc<str>>>,
1663 pub highlight_on_yank_duration: Option<u64>,
1664}
1665
1666#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
1667#[serde(rename_all = "snake_case")]
1668pub enum ModeContent {
1669 #[default]
1670 Normal,
1671 Insert,
1672 Replace,
1673 Visual,
1674 VisualLine,
1675 VisualBlock,
1676 HelixNormal,
1677}
1678
1679impl From<ModeContent> for Mode {
1680 fn from(mode: ModeContent) -> Self {
1681 match mode {
1682 ModeContent::Normal => Self::Normal,
1683 ModeContent::Insert => Self::Insert,
1684 ModeContent::Replace => Self::Replace,
1685 ModeContent::Visual => Self::Visual,
1686 ModeContent::VisualLine => Self::VisualLine,
1687 ModeContent::VisualBlock => Self::VisualBlock,
1688 ModeContent::HelixNormal => Self::HelixNormal,
1689 }
1690 }
1691}
1692
1693impl Settings for VimSettings {
1694 const KEY: Option<&'static str> = Some("vim");
1695
1696 type FileContent = VimSettingsContent;
1697
1698 fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
1699 let settings: VimSettingsContent = sources.json_merge()?;
1700
1701 Ok(Self {
1702 default_mode: settings
1703 .default_mode
1704 .ok_or_else(Self::missing_default)?
1705 .into(),
1706 toggle_relative_line_numbers: settings
1707 .toggle_relative_line_numbers
1708 .ok_or_else(Self::missing_default)?,
1709 use_system_clipboard: settings
1710 .use_system_clipboard
1711 .ok_or_else(Self::missing_default)?,
1712 use_multiline_find: settings
1713 .use_multiline_find
1714 .ok_or_else(Self::missing_default)?,
1715 use_smartcase_find: settings
1716 .use_smartcase_find
1717 .ok_or_else(Self::missing_default)?,
1718 custom_digraphs: settings.custom_digraphs.ok_or_else(Self::missing_default)?,
1719 highlight_on_yank_duration: settings
1720 .highlight_on_yank_duration
1721 .ok_or_else(Self::missing_default)?,
1722 })
1723 }
1724}