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