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