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