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