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