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