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