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