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