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