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