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