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