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