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