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