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