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, HideMouseCursorOrigin, 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 self.update_editor(window, cx, |_, editor, _, _| {
771 editor.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction)
772 });
773 return;
774 }
775 } else if window.has_pending_keystrokes() || keystroke_event.keystroke.is_ime_in_progress()
776 {
777 return;
778 }
779
780 if let Some(operator) = self.active_operator() {
781 match operator {
782 Operator::Literal { prefix } => {
783 self.handle_literal_keystroke(
784 keystroke_event,
785 prefix.unwrap_or_default(),
786 window,
787 cx,
788 );
789 }
790 _ if !operator.is_waiting(self.mode) => {
791 self.clear_operator(window, cx);
792 self.stop_recording_immediately(Box::new(ClearOperators), cx)
793 }
794 _ => {}
795 }
796 }
797 }
798
799 fn handle_editor_event(
800 &mut self,
801 event: &EditorEvent,
802 window: &mut Window,
803 cx: &mut Context<Self>,
804 ) {
805 match event {
806 EditorEvent::Focused => self.focused(true, window, cx),
807 EditorEvent::Blurred => self.blurred(window, cx),
808 EditorEvent::SelectionsChanged { local: true } => {
809 self.local_selections_changed(window, cx);
810 }
811 EditorEvent::InputIgnored { text } => {
812 self.input_ignored(text.clone(), window, cx);
813 Vim::globals(cx).observe_insertion(text, None)
814 }
815 EditorEvent::InputHandled {
816 text,
817 utf16_range_to_replace: range_to_replace,
818 } => Vim::globals(cx).observe_insertion(text, range_to_replace.clone()),
819 EditorEvent::TransactionBegun { transaction_id } => {
820 self.transaction_begun(*transaction_id, window, cx)
821 }
822 EditorEvent::TransactionUndone { transaction_id } => {
823 self.transaction_undone(transaction_id, window, cx)
824 }
825 EditorEvent::Edited { .. } => self.push_to_change_list(window, cx),
826 EditorEvent::FocusedIn => self.sync_vim_settings(window, cx),
827 EditorEvent::CursorShapeChanged => self.cursor_shape_changed(window, cx),
828 EditorEvent::PushedToNavHistory {
829 anchor,
830 is_deactivate,
831 } => {
832 self.update_editor(window, cx, |vim, editor, window, cx| {
833 let mark = if *is_deactivate {
834 "\"".to_string()
835 } else {
836 "'".to_string()
837 };
838 vim.set_mark(mark, vec![*anchor], editor.buffer(), window, cx);
839 });
840 }
841 _ => {}
842 }
843 }
844
845 fn push_operator(&mut self, operator: Operator, window: &mut Window, cx: &mut Context<Self>) {
846 if operator.starts_dot_recording() {
847 self.start_recording(cx);
848 }
849 // Since these operations can only be entered with pre-operators,
850 // we need to clear the previous operators when pushing,
851 // so that the current stack is the most correct
852 if matches!(
853 operator,
854 Operator::AddSurrounds { .. }
855 | Operator::ChangeSurrounds { .. }
856 | Operator::DeleteSurrounds
857 ) {
858 self.operator_stack.clear();
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, cx: &mut App) -> 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 => {
1026 let editor_settings = EditorSettings::get_global(cx);
1027 editor_settings.cursor_shape.unwrap_or_default()
1028 }
1029 }
1030 }
1031
1032 pub fn editor_input_enabled(&self) -> bool {
1033 match self.mode {
1034 Mode::Insert => {
1035 if let Some(operator) = self.operator_stack.last() {
1036 !operator.is_waiting(self.mode)
1037 } else {
1038 true
1039 }
1040 }
1041 Mode::Normal
1042 | Mode::HelixNormal
1043 | Mode::Replace
1044 | Mode::Visual
1045 | Mode::VisualLine
1046 | Mode::VisualBlock => false,
1047 }
1048 }
1049
1050 pub fn should_autoindent(&self) -> bool {
1051 !(self.mode == Mode::Insert && self.last_mode == Mode::VisualBlock)
1052 }
1053
1054 pub fn clip_at_line_ends(&self) -> bool {
1055 match self.mode {
1056 Mode::Insert
1057 | Mode::Visual
1058 | Mode::VisualLine
1059 | Mode::VisualBlock
1060 | Mode::Replace
1061 | Mode::HelixNormal => false,
1062 Mode::Normal => true,
1063 }
1064 }
1065
1066 pub fn extend_key_context(&self, context: &mut KeyContext, cx: &App) {
1067 let mut mode = match self.mode {
1068 Mode::Normal => "normal",
1069 Mode::Visual | Mode::VisualLine | Mode::VisualBlock => "visual",
1070 Mode::Insert => "insert",
1071 Mode::Replace => "replace",
1072 Mode::HelixNormal => "helix_normal",
1073 }
1074 .to_string();
1075
1076 let mut operator_id = "none";
1077
1078 let active_operator = self.active_operator();
1079 if active_operator.is_none() && cx.global::<VimGlobals>().pre_count.is_some()
1080 || active_operator.is_some() && cx.global::<VimGlobals>().post_count.is_some()
1081 {
1082 context.add("VimCount");
1083 }
1084
1085 if let Some(active_operator) = active_operator {
1086 if active_operator.is_waiting(self.mode) {
1087 if matches!(active_operator, Operator::Literal { .. }) {
1088 mode = "literal".to_string();
1089 } else {
1090 mode = "waiting".to_string();
1091 }
1092 } else {
1093 operator_id = active_operator.id();
1094 mode = "operator".to_string();
1095 }
1096 }
1097
1098 if mode == "normal" || mode == "visual" || mode == "operator" {
1099 context.add("VimControl");
1100 }
1101 context.set("vim_mode", mode);
1102 context.set("vim_operator", operator_id);
1103 }
1104
1105 fn focused(&mut self, preserve_selection: bool, window: &mut Window, cx: &mut Context<Self>) {
1106 let Some(editor) = self.editor() else {
1107 return;
1108 };
1109 let newest_selection_empty = editor.update(cx, |editor, cx| {
1110 editor.selections.newest::<usize>(cx).is_empty()
1111 });
1112 let editor = editor.read(cx);
1113 let editor_mode = editor.mode();
1114
1115 if editor_mode == EditorMode::Full
1116 && !newest_selection_empty
1117 && self.mode == Mode::Normal
1118 // When following someone, don't switch vim mode.
1119 && editor.leader_peer_id().is_none()
1120 {
1121 if preserve_selection {
1122 self.switch_mode(Mode::Visual, true, window, cx);
1123 } else {
1124 self.update_editor(window, cx, |_, editor, window, cx| {
1125 editor.set_clip_at_line_ends(false, cx);
1126 editor.change_selections(None, window, cx, |s| {
1127 s.move_with(|_, selection| {
1128 selection.collapse_to(selection.start, selection.goal)
1129 })
1130 });
1131 });
1132 }
1133 }
1134
1135 cx.emit(VimEvent::Focused);
1136 self.sync_vim_settings(window, cx);
1137
1138 if VimSettings::get_global(cx).toggle_relative_line_numbers {
1139 if let Some(old_vim) = Vim::globals(cx).focused_vim() {
1140 if old_vim.entity_id() != cx.entity().entity_id() {
1141 old_vim.update(cx, |vim, cx| {
1142 vim.update_editor(window, cx, |_, editor, _, cx| {
1143 editor.set_relative_line_number(None, cx)
1144 });
1145 });
1146
1147 self.update_editor(window, cx, |vim, editor, _, cx| {
1148 let is_relative = vim.mode != Mode::Insert;
1149 editor.set_relative_line_number(Some(is_relative), cx)
1150 });
1151 }
1152 } else {
1153 self.update_editor(window, cx, |vim, editor, _, cx| {
1154 let is_relative = vim.mode != Mode::Insert;
1155 editor.set_relative_line_number(Some(is_relative), cx)
1156 });
1157 }
1158 }
1159 Vim::globals(cx).focused_vim = Some(cx.entity().downgrade());
1160 }
1161
1162 fn blurred(&mut self, window: &mut Window, cx: &mut Context<Self>) {
1163 self.stop_recording_immediately(NormalBefore.boxed_clone(), cx);
1164 self.store_visual_marks(window, cx);
1165 self.clear_operator(window, cx);
1166 self.update_editor(window, cx, |vim, editor, _, cx| {
1167 if vim.cursor_shape(cx) == CursorShape::Block {
1168 editor.set_cursor_shape(CursorShape::Hollow, cx);
1169 }
1170 });
1171 }
1172
1173 fn cursor_shape_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
1174 self.update_editor(window, cx, |vim, editor, _, cx| {
1175 editor.set_cursor_shape(vim.cursor_shape(cx), cx);
1176 });
1177 }
1178
1179 fn update_editor<S>(
1180 &mut self,
1181 window: &mut Window,
1182 cx: &mut Context<Self>,
1183 update: impl FnOnce(&mut Self, &mut Editor, &mut Window, &mut Context<Editor>) -> S,
1184 ) -> Option<S> {
1185 let editor = self.editor.upgrade()?;
1186 Some(editor.update(cx, |editor, cx| update(self, editor, window, cx)))
1187 }
1188
1189 fn editor_selections(
1190 &mut self,
1191 window: &mut Window,
1192 cx: &mut Context<Self>,
1193 ) -> Vec<Range<Anchor>> {
1194 self.update_editor(window, cx, |_, editor, _, _| {
1195 editor
1196 .selections
1197 .disjoint_anchors()
1198 .iter()
1199 .map(|selection| selection.tail()..selection.head())
1200 .collect()
1201 })
1202 .unwrap_or_default()
1203 }
1204
1205 fn editor_cursor_word(
1206 &mut self,
1207 window: &mut Window,
1208 cx: &mut Context<Self>,
1209 ) -> Option<String> {
1210 self.update_editor(window, cx, |_, editor, window, cx| {
1211 let selection = editor.selections.newest::<usize>(cx);
1212
1213 let snapshot = &editor.snapshot(window, cx).buffer_snapshot;
1214 let (range, kind) = snapshot.surrounding_word(selection.start, true);
1215 if kind == Some(CharKind::Word) {
1216 let text: String = snapshot.text_for_range(range).collect();
1217 if !text.trim().is_empty() {
1218 return Some(text);
1219 }
1220 }
1221
1222 None
1223 })
1224 .unwrap_or_default()
1225 }
1226
1227 /// When doing an action that modifies the buffer, we start recording so that `.`
1228 /// will replay the action.
1229 pub fn start_recording(&mut self, cx: &mut Context<Self>) {
1230 Vim::update_globals(cx, |globals, cx| {
1231 if !globals.dot_replaying {
1232 globals.dot_recording = true;
1233 globals.recording_actions = Default::default();
1234 globals.recorded_count = None;
1235
1236 let selections = self.editor().map(|editor| {
1237 editor.update(cx, |editor, cx| {
1238 (
1239 editor.selections.oldest::<Point>(cx),
1240 editor.selections.newest::<Point>(cx),
1241 )
1242 })
1243 });
1244
1245 if let Some((oldest, newest)) = selections {
1246 globals.recorded_selection = match self.mode {
1247 Mode::Visual if newest.end.row == newest.start.row => {
1248 RecordedSelection::SingleLine {
1249 cols: newest.end.column - newest.start.column,
1250 }
1251 }
1252 Mode::Visual => RecordedSelection::Visual {
1253 rows: newest.end.row - newest.start.row,
1254 cols: newest.end.column,
1255 },
1256 Mode::VisualLine => RecordedSelection::VisualLine {
1257 rows: newest.end.row - newest.start.row,
1258 },
1259 Mode::VisualBlock => RecordedSelection::VisualBlock {
1260 rows: newest.end.row.abs_diff(oldest.start.row),
1261 cols: newest.end.column.abs_diff(oldest.start.column),
1262 },
1263 _ => RecordedSelection::None,
1264 }
1265 } else {
1266 globals.recorded_selection = RecordedSelection::None;
1267 }
1268 }
1269 })
1270 }
1271
1272 pub fn stop_replaying(&mut self, cx: &mut Context<Self>) {
1273 let globals = Vim::globals(cx);
1274 globals.dot_replaying = false;
1275 if let Some(replayer) = globals.replayer.take() {
1276 replayer.stop();
1277 }
1278 }
1279
1280 /// When finishing an action that modifies the buffer, stop recording.
1281 /// as you usually call this within a keystroke handler we also ensure that
1282 /// the current action is recorded.
1283 pub fn stop_recording(&mut self, cx: &mut Context<Self>) {
1284 let globals = Vim::globals(cx);
1285 if globals.dot_recording {
1286 globals.stop_recording_after_next_action = true;
1287 }
1288 self.exit_temporary_mode = self.temp_mode;
1289 }
1290
1291 /// Stops recording actions immediately rather than waiting until after the
1292 /// next action to stop recording.
1293 ///
1294 /// This doesn't include the current action.
1295 pub fn stop_recording_immediately(&mut self, action: Box<dyn Action>, cx: &mut Context<Self>) {
1296 let globals = Vim::globals(cx);
1297 if globals.dot_recording {
1298 globals
1299 .recording_actions
1300 .push(ReplayableAction::Action(action.boxed_clone()));
1301 globals.recorded_actions = mem::take(&mut globals.recording_actions);
1302 globals.dot_recording = false;
1303 globals.stop_recording_after_next_action = false;
1304 }
1305 self.exit_temporary_mode = self.temp_mode;
1306 }
1307
1308 /// Explicitly record one action (equivalents to start_recording and stop_recording)
1309 pub fn record_current_action(&mut self, cx: &mut Context<Self>) {
1310 self.start_recording(cx);
1311 self.stop_recording(cx);
1312 }
1313
1314 fn push_count_digit(&mut self, number: usize, window: &mut Window, cx: &mut Context<Self>) {
1315 if self.active_operator().is_some() {
1316 let post_count = Vim::globals(cx).post_count.unwrap_or(0);
1317
1318 Vim::globals(cx).post_count = Some(
1319 post_count
1320 .checked_mul(10)
1321 .and_then(|post_count| post_count.checked_add(number))
1322 .unwrap_or(post_count),
1323 )
1324 } else {
1325 let pre_count = Vim::globals(cx).pre_count.unwrap_or(0);
1326
1327 Vim::globals(cx).pre_count = Some(
1328 pre_count
1329 .checked_mul(10)
1330 .and_then(|pre_count| pre_count.checked_add(number))
1331 .unwrap_or(pre_count),
1332 )
1333 }
1334 // update the keymap so that 0 works
1335 self.sync_vim_settings(window, cx)
1336 }
1337
1338 fn select_register(&mut self, register: Arc<str>, window: &mut Window, cx: &mut Context<Self>) {
1339 if register.chars().count() == 1 {
1340 self.selected_register
1341 .replace(register.chars().next().unwrap());
1342 }
1343 self.operator_stack.clear();
1344 self.sync_vim_settings(window, cx);
1345 }
1346
1347 fn maybe_pop_operator(&mut self) -> Option<Operator> {
1348 self.operator_stack.pop()
1349 }
1350
1351 fn pop_operator(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Operator {
1352 let popped_operator = self.operator_stack.pop()
1353 .expect("Operator popped when no operator was on the stack. This likely means there is an invalid keymap config");
1354 self.sync_vim_settings(window, cx);
1355 popped_operator
1356 }
1357
1358 fn clear_operator(&mut self, window: &mut Window, cx: &mut Context<Self>) {
1359 Vim::take_count(cx);
1360 self.selected_register.take();
1361 self.operator_stack.clear();
1362 self.sync_vim_settings(window, cx);
1363 }
1364
1365 fn active_operator(&self) -> Option<Operator> {
1366 self.operator_stack.last().cloned()
1367 }
1368
1369 fn transaction_begun(
1370 &mut self,
1371 transaction_id: TransactionId,
1372 _window: &mut Window,
1373 _: &mut Context<Self>,
1374 ) {
1375 let mode = if (self.mode == Mode::Insert
1376 || self.mode == Mode::Replace
1377 || self.mode == Mode::Normal)
1378 && self.current_tx.is_none()
1379 {
1380 self.current_tx = Some(transaction_id);
1381 self.last_mode
1382 } else {
1383 self.mode
1384 };
1385 if mode == Mode::VisualLine || mode == Mode::VisualBlock {
1386 self.undo_modes.insert(transaction_id, mode);
1387 }
1388 }
1389
1390 fn transaction_undone(
1391 &mut self,
1392 transaction_id: &TransactionId,
1393 window: &mut Window,
1394 cx: &mut Context<Self>,
1395 ) {
1396 match self.mode {
1397 Mode::VisualLine | Mode::VisualBlock | Mode::Visual => {
1398 self.update_editor(window, cx, |vim, editor, window, cx| {
1399 let original_mode = vim.undo_modes.get(transaction_id);
1400 editor.change_selections(None, window, cx, |s| match original_mode {
1401 Some(Mode::VisualLine) => {
1402 s.move_with(|map, selection| {
1403 selection.collapse_to(
1404 map.prev_line_boundary(selection.start.to_point(map)).1,
1405 SelectionGoal::None,
1406 )
1407 });
1408 }
1409 Some(Mode::VisualBlock) => {
1410 let mut first = s.first_anchor();
1411 first.collapse_to(first.start, first.goal);
1412 s.select_anchors(vec![first]);
1413 }
1414 _ => {
1415 s.move_with(|map, selection| {
1416 selection.collapse_to(
1417 map.clip_at_line_end(selection.start),
1418 selection.goal,
1419 );
1420 });
1421 }
1422 });
1423 });
1424 self.switch_mode(Mode::Normal, true, window, cx)
1425 }
1426 Mode::Normal => {
1427 self.update_editor(window, cx, |_, editor, window, cx| {
1428 editor.change_selections(None, window, cx, |s| {
1429 s.move_with(|map, selection| {
1430 selection
1431 .collapse_to(map.clip_at_line_end(selection.end), selection.goal)
1432 })
1433 })
1434 });
1435 }
1436 Mode::Insert | Mode::Replace | Mode::HelixNormal => {}
1437 }
1438 }
1439
1440 fn local_selections_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
1441 let Some(editor) = self.editor() else { return };
1442
1443 if editor.read(cx).leader_peer_id().is_some() {
1444 return;
1445 }
1446
1447 let newest = editor.read(cx).selections.newest_anchor().clone();
1448 let is_multicursor = editor.read(cx).selections.count() > 1;
1449 if self.mode == Mode::Insert && self.current_tx.is_some() {
1450 if self.current_anchor.is_none() {
1451 self.current_anchor = Some(newest);
1452 } else if self.current_anchor.as_ref().unwrap() != &newest {
1453 if let Some(tx_id) = self.current_tx.take() {
1454 self.update_editor(window, cx, |_, editor, _, cx| {
1455 editor.group_until_transaction(tx_id, cx)
1456 });
1457 }
1458 }
1459 } else if self.mode == Mode::Normal && newest.start != newest.end {
1460 if matches!(newest.goal, SelectionGoal::HorizontalRange { .. }) {
1461 self.switch_mode(Mode::VisualBlock, false, window, cx);
1462 } else {
1463 self.switch_mode(Mode::Visual, false, window, cx)
1464 }
1465 } else if newest.start == newest.end
1466 && !is_multicursor
1467 && [Mode::Visual, Mode::VisualLine, Mode::VisualBlock].contains(&self.mode)
1468 {
1469 self.switch_mode(Mode::Normal, true, window, cx);
1470 }
1471 }
1472
1473 fn input_ignored(&mut self, text: Arc<str>, window: &mut Window, cx: &mut Context<Self>) {
1474 if text.is_empty() {
1475 return;
1476 }
1477
1478 match self.active_operator() {
1479 Some(Operator::FindForward { before }) => {
1480 let find = Motion::FindForward {
1481 before,
1482 char: text.chars().next().unwrap(),
1483 mode: if VimSettings::get_global(cx).use_multiline_find {
1484 FindRange::MultiLine
1485 } else {
1486 FindRange::SingleLine
1487 },
1488 smartcase: VimSettings::get_global(cx).use_smartcase_find,
1489 };
1490 Vim::globals(cx).last_find = Some(find.clone());
1491 self.motion(find, window, cx)
1492 }
1493 Some(Operator::FindBackward { after }) => {
1494 let find = Motion::FindBackward {
1495 after,
1496 char: text.chars().next().unwrap(),
1497 mode: if VimSettings::get_global(cx).use_multiline_find {
1498 FindRange::MultiLine
1499 } else {
1500 FindRange::SingleLine
1501 },
1502 smartcase: VimSettings::get_global(cx).use_smartcase_find,
1503 };
1504 Vim::globals(cx).last_find = Some(find.clone());
1505 self.motion(find, window, cx)
1506 }
1507 Some(Operator::Sneak { first_char }) => {
1508 if let Some(first_char) = first_char {
1509 if let Some(second_char) = text.chars().next() {
1510 let sneak = Motion::Sneak {
1511 first_char,
1512 second_char,
1513 smartcase: VimSettings::get_global(cx).use_smartcase_find,
1514 };
1515 Vim::globals(cx).last_find = Some((&sneak).clone());
1516 self.motion(sneak, window, cx)
1517 }
1518 } else {
1519 let first_char = text.chars().next();
1520 self.pop_operator(window, cx);
1521 self.push_operator(Operator::Sneak { first_char }, window, cx);
1522 }
1523 }
1524 Some(Operator::SneakBackward { first_char }) => {
1525 if let Some(first_char) = first_char {
1526 if let Some(second_char) = text.chars().next() {
1527 let sneak = Motion::SneakBackward {
1528 first_char,
1529 second_char,
1530 smartcase: VimSettings::get_global(cx).use_smartcase_find,
1531 };
1532 Vim::globals(cx).last_find = Some((&sneak).clone());
1533 self.motion(sneak, window, cx)
1534 }
1535 } else {
1536 let first_char = text.chars().next();
1537 self.pop_operator(window, cx);
1538 self.push_operator(Operator::SneakBackward { first_char }, window, cx);
1539 }
1540 }
1541 Some(Operator::Replace) => match self.mode {
1542 Mode::Normal => self.normal_replace(text, window, cx),
1543 Mode::Visual | Mode::VisualLine | Mode::VisualBlock => {
1544 self.visual_replace(text, window, cx)
1545 }
1546 _ => self.clear_operator(window, cx),
1547 },
1548 Some(Operator::Digraph { first_char }) => {
1549 if let Some(first_char) = first_char {
1550 if let Some(second_char) = text.chars().next() {
1551 self.insert_digraph(first_char, second_char, window, cx);
1552 }
1553 } else {
1554 let first_char = text.chars().next();
1555 self.pop_operator(window, cx);
1556 self.push_operator(Operator::Digraph { first_char }, window, cx);
1557 }
1558 }
1559 Some(Operator::Literal { prefix }) => {
1560 self.handle_literal_input(prefix.unwrap_or_default(), &text, window, cx)
1561 }
1562 Some(Operator::AddSurrounds { target }) => match self.mode {
1563 Mode::Normal => {
1564 if let Some(target) = target {
1565 self.add_surrounds(text, target, window, cx);
1566 self.clear_operator(window, cx);
1567 }
1568 }
1569 Mode::Visual | Mode::VisualLine | Mode::VisualBlock => {
1570 self.add_surrounds(text, SurroundsType::Selection, window, cx);
1571 self.clear_operator(window, cx);
1572 }
1573 _ => self.clear_operator(window, cx),
1574 },
1575 Some(Operator::ChangeSurrounds { target }) => match self.mode {
1576 Mode::Normal => {
1577 if let Some(target) = target {
1578 self.change_surrounds(text, target, window, cx);
1579 self.clear_operator(window, cx);
1580 }
1581 }
1582 _ => self.clear_operator(window, cx),
1583 },
1584 Some(Operator::DeleteSurrounds) => match self.mode {
1585 Mode::Normal => {
1586 self.delete_surrounds(text, window, cx);
1587 self.clear_operator(window, cx);
1588 }
1589 _ => self.clear_operator(window, cx),
1590 },
1591 Some(Operator::Mark) => self.create_mark(text, window, cx),
1592 Some(Operator::RecordRegister) => {
1593 self.record_register(text.chars().next().unwrap(), window, cx)
1594 }
1595 Some(Operator::ReplayRegister) => {
1596 self.replay_register(text.chars().next().unwrap(), window, cx)
1597 }
1598 Some(Operator::Register) => match self.mode {
1599 Mode::Insert => {
1600 self.update_editor(window, cx, |_, editor, window, cx| {
1601 if let Some(register) = Vim::update_globals(cx, |globals, cx| {
1602 globals.read_register(text.chars().next(), Some(editor), cx)
1603 }) {
1604 editor.do_paste(
1605 ®ister.text.to_string(),
1606 register.clipboard_selections.clone(),
1607 false,
1608 window,
1609 cx,
1610 )
1611 }
1612 });
1613 self.clear_operator(window, cx);
1614 }
1615 _ => {
1616 self.select_register(text, window, cx);
1617 }
1618 },
1619 Some(Operator::Jump { line }) => self.jump(text, line, true, window, cx),
1620 _ => {
1621 if self.mode == Mode::Replace {
1622 self.multi_replace(text, window, cx)
1623 }
1624
1625 if self.mode == Mode::Normal {
1626 self.update_editor(window, cx, |_, editor, window, cx| {
1627 editor.accept_edit_prediction(
1628 &editor::actions::AcceptEditPrediction {},
1629 window,
1630 cx,
1631 );
1632 });
1633 }
1634 }
1635 }
1636 }
1637
1638 fn sync_vim_settings(&mut self, window: &mut Window, cx: &mut Context<Self>) {
1639 self.update_editor(window, cx, |vim, editor, window, cx| {
1640 editor.set_cursor_shape(vim.cursor_shape(cx), cx);
1641 editor.set_clip_at_line_ends(vim.clip_at_line_ends(), cx);
1642 editor.set_collapse_matches(true);
1643 editor.set_input_enabled(vim.editor_input_enabled());
1644 editor.set_autoindent(vim.should_autoindent());
1645 editor.selections.line_mode = matches!(vim.mode, Mode::VisualLine);
1646
1647 let hide_inline_completions = match vim.mode {
1648 Mode::Insert | Mode::Replace => false,
1649 _ => true,
1650 };
1651 editor.set_inline_completions_hidden_for_vim_mode(hide_inline_completions, window, cx);
1652 });
1653 cx.notify()
1654 }
1655}
1656
1657/// Controls when to use system clipboard.
1658#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
1659#[serde(rename_all = "snake_case")]
1660pub enum UseSystemClipboard {
1661 /// Don't use system clipboard.
1662 Never,
1663 /// Use system clipboard.
1664 Always,
1665 /// Use system clipboard for yank operations.
1666 OnYank,
1667}
1668
1669#[derive(Deserialize)]
1670struct VimSettings {
1671 pub default_mode: Mode,
1672 pub toggle_relative_line_numbers: bool,
1673 pub use_system_clipboard: UseSystemClipboard,
1674 pub use_multiline_find: bool,
1675 pub use_smartcase_find: bool,
1676 pub custom_digraphs: HashMap<String, Arc<str>>,
1677 pub highlight_on_yank_duration: u64,
1678}
1679
1680#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
1681struct VimSettingsContent {
1682 pub default_mode: Option<ModeContent>,
1683 pub toggle_relative_line_numbers: Option<bool>,
1684 pub use_system_clipboard: Option<UseSystemClipboard>,
1685 pub use_multiline_find: Option<bool>,
1686 pub use_smartcase_find: Option<bool>,
1687 pub custom_digraphs: Option<HashMap<String, Arc<str>>>,
1688 pub highlight_on_yank_duration: Option<u64>,
1689}
1690
1691#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
1692#[serde(rename_all = "snake_case")]
1693pub enum ModeContent {
1694 #[default]
1695 Normal,
1696 Insert,
1697 Replace,
1698 Visual,
1699 VisualLine,
1700 VisualBlock,
1701 HelixNormal,
1702}
1703
1704impl From<ModeContent> for Mode {
1705 fn from(mode: ModeContent) -> Self {
1706 match mode {
1707 ModeContent::Normal => Self::Normal,
1708 ModeContent::Insert => Self::Insert,
1709 ModeContent::Replace => Self::Replace,
1710 ModeContent::Visual => Self::Visual,
1711 ModeContent::VisualLine => Self::VisualLine,
1712 ModeContent::VisualBlock => Self::VisualBlock,
1713 ModeContent::HelixNormal => Self::HelixNormal,
1714 }
1715 }
1716}
1717
1718impl Settings for VimSettings {
1719 const KEY: Option<&'static str> = Some("vim");
1720
1721 type FileContent = VimSettingsContent;
1722
1723 fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
1724 let settings: VimSettingsContent = sources.json_merge()?;
1725
1726 Ok(Self {
1727 default_mode: settings
1728 .default_mode
1729 .ok_or_else(Self::missing_default)?
1730 .into(),
1731 toggle_relative_line_numbers: settings
1732 .toggle_relative_line_numbers
1733 .ok_or_else(Self::missing_default)?,
1734 use_system_clipboard: settings
1735 .use_system_clipboard
1736 .ok_or_else(Self::missing_default)?,
1737 use_multiline_find: settings
1738 .use_multiline_find
1739 .ok_or_else(Self::missing_default)?,
1740 use_smartcase_find: settings
1741 .use_smartcase_find
1742 .ok_or_else(Self::missing_default)?,
1743 custom_digraphs: settings.custom_digraphs.ok_or_else(Self::missing_default)?,
1744 highlight_on_yank_duration: settings
1745 .highlight_on_yank_duration
1746 .ok_or_else(Self::missing_default)?,
1747 })
1748 }
1749}