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