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