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