vim.rs

  1//! Vim support for Zed.
  2
  3#[cfg(test)]
  4mod test;
  5
  6mod command;
  7mod editor_events;
  8mod insert;
  9mod mode_indicator;
 10mod motion;
 11mod normal;
 12mod object;
 13mod replace;
 14mod state;
 15mod surrounds;
 16mod utils;
 17mod visual;
 18
 19use anyhow::Result;
 20use collections::HashMap;
 21use command_palette_hooks::{CommandPaletteFilter, CommandPaletteInterceptor};
 22use editor::{
 23    movement::{self, FindRange},
 24    Anchor, Bias, Editor, EditorEvent, EditorMode, ToPoint,
 25};
 26use gpui::{
 27    actions, impl_actions, Action, AppContext, EntityId, FocusableView, Global, KeystrokeEvent,
 28    Subscription, View, ViewContext, WeakView, WindowContext,
 29};
 30use language::{CursorShape, Point, SelectionGoal, TransactionId};
 31pub use mode_indicator::ModeIndicator;
 32use motion::Motion;
 33use normal::normal_replace;
 34use replace::multi_replace;
 35use schemars::JsonSchema;
 36use serde::Deserialize;
 37use serde_derive::Serialize;
 38use settings::{update_settings_file, Settings, SettingsSources, SettingsStore};
 39use state::{EditorState, Mode, Operator, RecordedSelection, WorkspaceState};
 40use std::{ops::Range, sync::Arc};
 41use surrounds::{add_surrounds, change_surrounds, delete_surrounds};
 42use ui::BorrowAppContext;
 43use visual::{visual_block_motion, visual_replace};
 44use workspace::{self, Workspace};
 45
 46use crate::state::ReplayableAction;
 47
 48/// Whether or not to enable Vim mode (work in progress).
 49///
 50/// Default: false
 51pub struct VimModeSetting(pub bool);
 52
 53/// An Action to Switch between modes
 54#[derive(Clone, Deserialize, PartialEq)]
 55pub struct SwitchMode(pub Mode);
 56
 57/// PushOperator is used to put vim into a "minor" mode,
 58/// where it's waiting for a specific next set of keystrokes.
 59/// For example 'd' needs a motion to complete.
 60#[derive(Clone, Deserialize, PartialEq)]
 61pub struct PushOperator(pub Operator);
 62
 63/// Number is used to manage vim's count. Pushing a digit
 64/// multiplis the current value by 10 and adds the digit.
 65#[derive(Clone, Deserialize, PartialEq)]
 66struct Number(usize);
 67
 68actions!(
 69    vim,
 70    [
 71        Tab,
 72        Enter,
 73        Object,
 74        InnerObject,
 75        FindForward,
 76        FindBackward,
 77        OpenDefaultKeymap
 78    ]
 79);
 80
 81// in the workspace namespace so it's not filtered out when vim is disabled.
 82actions!(workspace, [ToggleVimMode]);
 83
 84impl_actions!(vim, [SwitchMode, PushOperator, Number]);
 85
 86/// Initializes the `vim` crate.
 87pub fn init(cx: &mut AppContext) {
 88    cx.set_global(Vim::default());
 89    VimModeSetting::register(cx);
 90    VimSettings::register(cx);
 91
 92    cx.observe_keystrokes(observe_keystrokes).detach();
 93    editor_events::init(cx);
 94
 95    cx.observe_new_views(|workspace: &mut Workspace, cx| register(workspace, cx))
 96        .detach();
 97
 98    // Any time settings change, update vim mode to match. The Vim struct
 99    // will be initialized as disabled by default, so we filter its commands
100    // out when starting up.
101    CommandPaletteFilter::update_global(cx, |filter, _| {
102        filter.hide_namespace(Vim::NAMESPACE);
103    });
104    cx.update_global(|vim: &mut Vim, cx: &mut AppContext| {
105        vim.set_enabled(VimModeSetting::get_global(cx).0, cx)
106    });
107    cx.observe_global::<SettingsStore>(|cx| {
108        cx.update_global(|vim: &mut Vim, cx: &mut AppContext| {
109            vim.set_enabled(VimModeSetting::get_global(cx).0, cx)
110        });
111    })
112    .detach();
113}
114
115fn register(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
116    workspace.register_action(|_: &mut Workspace, &SwitchMode(mode): &SwitchMode, cx| {
117        Vim::update(cx, |vim, cx| vim.switch_mode(mode, false, cx))
118    });
119    workspace.register_action(
120        |_: &mut Workspace, PushOperator(operator): &PushOperator, cx| {
121            Vim::update(cx, |vim, cx| vim.push_operator(operator.clone(), cx))
122        },
123    );
124    workspace.register_action(|_: &mut Workspace, n: &Number, cx: _| {
125        Vim::update(cx, |vim, cx| vim.push_count_digit(n.0, cx));
126    });
127
128    workspace.register_action(|_: &mut Workspace, _: &Tab, cx| {
129        Vim::active_editor_input_ignored(" ".into(), cx)
130    });
131
132    workspace.register_action(|_: &mut Workspace, _: &Enter, cx| {
133        Vim::active_editor_input_ignored("\n".into(), cx)
134    });
135
136    workspace.register_action(|workspace: &mut Workspace, _: &ToggleVimMode, cx| {
137        let fs = workspace.app_state().fs.clone();
138        let currently_enabled = VimModeSetting::get_global(cx).0;
139        update_settings_file::<VimModeSetting>(fs, cx, move |setting| {
140            *setting = Some(!currently_enabled)
141        })
142    });
143
144    workspace.register_action(|_: &mut Workspace, _: &OpenDefaultKeymap, cx| {
145        cx.emit(workspace::Event::OpenBundledFile {
146            text: settings::vim_keymap(),
147            title: "Default Vim Bindings",
148            language: "JSON",
149        });
150    });
151
152    normal::register(workspace, cx);
153    insert::register(workspace, cx);
154    motion::register(workspace, cx);
155    command::register(workspace, cx);
156    replace::register(workspace, cx);
157    object::register(workspace, cx);
158    visual::register(workspace, cx);
159}
160
161/// Called whenever an keystroke is typed so vim can observe all actions
162/// and keystrokes accordingly.
163fn observe_keystrokes(keystroke_event: &KeystrokeEvent, cx: &mut WindowContext) {
164    if let Some(action) = keystroke_event
165        .action
166        .as_ref()
167        .map(|action| action.boxed_clone())
168    {
169        Vim::update(cx, |vim, _| {
170            if vim.workspace_state.recording {
171                vim.workspace_state
172                    .recorded_actions
173                    .push(ReplayableAction::Action(action.boxed_clone()));
174
175                if vim.workspace_state.stop_recording_after_next_action {
176                    vim.workspace_state.recording = false;
177                    vim.workspace_state.stop_recording_after_next_action = false;
178                }
179            }
180        });
181
182        // Keystroke is handled by the vim system, so continue forward
183        if action.name().starts_with("vim::") {
184            return;
185        }
186    } else if cx.has_pending_keystrokes() {
187        return;
188    }
189
190    Vim::update(cx, |vim, cx| match vim.active_operator() {
191        Some(
192            Operator::FindForward { .. }
193            | Operator::FindBackward { .. }
194            | Operator::Replace
195            | Operator::AddSurrounds { .. }
196            | Operator::ChangeSurrounds { .. }
197            | Operator::DeleteSurrounds,
198        ) => {}
199        Some(_) => {
200            vim.clear_operator(cx);
201        }
202        _ => {}
203    });
204}
205
206/// The state pertaining to Vim mode.
207#[derive(Default)]
208struct Vim {
209    active_editor: Option<WeakView<Editor>>,
210    editor_subscription: Option<Subscription>,
211    enabled: bool,
212    editor_states: HashMap<EntityId, EditorState>,
213    workspace_state: WorkspaceState,
214    default_state: EditorState,
215}
216
217impl Global for Vim {}
218
219impl Vim {
220    /// The namespace for Vim actions.
221    const NAMESPACE: &'static str = "vim";
222
223    fn read(cx: &mut AppContext) -> &Self {
224        cx.global::<Self>()
225    }
226
227    fn update<F, S>(cx: &mut WindowContext, update: F) -> S
228    where
229        F: FnOnce(&mut Self, &mut WindowContext) -> S,
230    {
231        cx.update_global(update)
232    }
233
234    fn activate_editor(&mut self, editor: View<Editor>, cx: &mut WindowContext) {
235        if !editor.read(cx).use_modal_editing() {
236            return;
237        }
238
239        self.active_editor = Some(editor.clone().downgrade());
240        self.editor_subscription = Some(cx.subscribe(&editor, |editor, event, cx| match event {
241            EditorEvent::SelectionsChanged { local: true } => {
242                if editor.read(cx).leader_peer_id().is_none() {
243                    Vim::update(cx, |vim, cx| {
244                        vim.local_selections_changed(editor, cx);
245                    })
246                }
247            }
248            EditorEvent::InputIgnored { text } => {
249                Vim::active_editor_input_ignored(text.clone(), cx);
250                Vim::record_insertion(text, None, cx)
251            }
252            EditorEvent::InputHandled {
253                text,
254                utf16_range_to_replace: range_to_replace,
255            } => Vim::record_insertion(text, range_to_replace.clone(), cx),
256            EditorEvent::TransactionBegun { transaction_id } => Vim::update(cx, |vim, cx| {
257                vim.transaction_begun(*transaction_id, cx);
258            }),
259            EditorEvent::TransactionUndone { transaction_id } => Vim::update(cx, |vim, cx| {
260                vim.transaction_undone(transaction_id, cx);
261            }),
262            _ => {}
263        }));
264
265        let editor = editor.read(cx);
266        let editor_mode = editor.mode();
267        let newest_selection_empty = editor.selections.newest::<usize>(cx).is_empty();
268
269        if editor_mode == EditorMode::Full
270                && !newest_selection_empty
271                && self.state().mode == Mode::Normal
272                // When following someone, don't switch vim mode.
273                && editor.leader_peer_id().is_none()
274        {
275            self.switch_mode(Mode::Visual, true, cx);
276        }
277
278        self.sync_vim_settings(cx);
279    }
280
281    fn record_insertion(
282        text: &Arc<str>,
283        range_to_replace: Option<Range<isize>>,
284        cx: &mut WindowContext,
285    ) {
286        Vim::update(cx, |vim, _| {
287            if vim.workspace_state.recording {
288                vim.workspace_state
289                    .recorded_actions
290                    .push(ReplayableAction::Insertion {
291                        text: text.clone(),
292                        utf16_range_to_replace: range_to_replace,
293                    });
294                if vim.workspace_state.stop_recording_after_next_action {
295                    vim.workspace_state.recording = false;
296                    vim.workspace_state.stop_recording_after_next_action = false;
297                }
298            }
299        });
300    }
301
302    fn update_active_editor<S>(
303        &mut self,
304        cx: &mut WindowContext,
305        update: impl FnOnce(&mut Vim, &mut Editor, &mut ViewContext<Editor>) -> S,
306    ) -> Option<S> {
307        let editor = self.active_editor.clone()?.upgrade()?;
308        Some(editor.update(cx, |editor, cx| update(self, editor, cx)))
309    }
310
311    fn editor_selections(&mut self, cx: &mut WindowContext) -> Vec<Range<Anchor>> {
312        self.update_active_editor(cx, |_, editor, _| {
313            editor
314                .selections
315                .disjoint_anchors()
316                .iter()
317                .map(|selection| selection.tail()..selection.head())
318                .collect()
319        })
320        .unwrap_or_default()
321    }
322
323    /// When doing an action that modifies the buffer, we start recording so that `.`
324    /// will replay the action.
325    pub fn start_recording(&mut self, cx: &mut WindowContext) {
326        if !self.workspace_state.replaying {
327            self.workspace_state.recording = true;
328            self.workspace_state.recorded_actions = Default::default();
329            self.workspace_state.recorded_count = None;
330
331            let selections = self
332                .active_editor
333                .as_ref()
334                .and_then(|editor| editor.upgrade())
335                .map(|editor| {
336                    let editor = editor.read(cx);
337                    (
338                        editor.selections.oldest::<Point>(cx),
339                        editor.selections.newest::<Point>(cx),
340                    )
341                });
342
343            if let Some((oldest, newest)) = selections {
344                self.workspace_state.recorded_selection = match self.state().mode {
345                    Mode::Visual if newest.end.row == newest.start.row => {
346                        RecordedSelection::SingleLine {
347                            cols: newest.end.column - newest.start.column,
348                        }
349                    }
350                    Mode::Visual => RecordedSelection::Visual {
351                        rows: newest.end.row - newest.start.row,
352                        cols: newest.end.column,
353                    },
354                    Mode::VisualLine => RecordedSelection::VisualLine {
355                        rows: newest.end.row - newest.start.row,
356                    },
357                    Mode::VisualBlock => RecordedSelection::VisualBlock {
358                        rows: newest.end.row.abs_diff(oldest.start.row),
359                        cols: newest.end.column.abs_diff(oldest.start.column),
360                    },
361                    _ => RecordedSelection::None,
362                }
363            } else {
364                self.workspace_state.recorded_selection = RecordedSelection::None;
365            }
366        }
367    }
368
369    pub fn stop_replaying(&mut self) {
370        self.workspace_state.replaying = false;
371    }
372
373    /// When finishing an action that modifies the buffer, stop recording.
374    /// as you usually call this within a keystroke handler we also ensure that
375    /// the current action is recorded.
376    pub fn stop_recording(&mut self) {
377        if self.workspace_state.recording {
378            self.workspace_state.stop_recording_after_next_action = true;
379        }
380    }
381
382    /// Stops recording actions immediately rather than waiting until after the
383    /// next action to stop recording.
384    ///
385    /// This doesn't include the current action.
386    pub fn stop_recording_immediately(&mut self, action: Box<dyn Action>) {
387        if self.workspace_state.recording {
388            self.workspace_state
389                .recorded_actions
390                .push(ReplayableAction::Action(action.boxed_clone()));
391            self.workspace_state.recording = false;
392            self.workspace_state.stop_recording_after_next_action = false;
393        }
394    }
395
396    /// Explicitly record one action (equivalents to start_recording and stop_recording)
397    pub fn record_current_action(&mut self, cx: &mut WindowContext) {
398        self.start_recording(cx);
399        self.stop_recording();
400    }
401
402    fn switch_mode(&mut self, mode: Mode, leave_selections: bool, cx: &mut WindowContext) {
403        let state = self.state();
404        let last_mode = state.mode;
405        let prior_mode = state.last_mode;
406        let prior_tx = state.current_tx;
407        self.update_state(|state| {
408            state.last_mode = last_mode;
409            state.mode = mode;
410            state.operator_stack.clear();
411            state.current_tx.take();
412            state.current_anchor.take();
413        });
414        if mode != Mode::Insert {
415            self.take_count(cx);
416        }
417
418        // Sync editor settings like clip mode
419        self.sync_vim_settings(cx);
420
421        if leave_selections {
422            return;
423        }
424
425        // Adjust selections
426        self.update_active_editor(cx, |_, editor, cx| {
427            if last_mode != Mode::VisualBlock && last_mode.is_visual() && mode == Mode::VisualBlock
428            {
429                visual_block_motion(true, editor, cx, |_, point, goal| Some((point, goal)))
430            }
431            if last_mode == Mode::Insert || last_mode == Mode::Replace {
432                if let Some(prior_tx) = prior_tx {
433                    editor.group_until_transaction(prior_tx, cx)
434                }
435            }
436
437            editor.change_selections(None, cx, |s| {
438                // we cheat with visual block mode and use multiple cursors.
439                // the cost of this cheat is we need to convert back to a single
440                // cursor whenever vim would.
441                if last_mode == Mode::VisualBlock
442                    && (mode != Mode::VisualBlock && mode != Mode::Insert)
443                {
444                    let tail = s.oldest_anchor().tail();
445                    let head = s.newest_anchor().head();
446                    s.select_anchor_ranges(vec![tail..head]);
447                } else if last_mode == Mode::Insert
448                    && prior_mode == Mode::VisualBlock
449                    && mode != Mode::VisualBlock
450                {
451                    let pos = s.first_anchor().head();
452                    s.select_anchor_ranges(vec![pos..pos])
453                }
454
455                let snapshot = s.display_map();
456                if let Some(pending) = s.pending.as_mut() {
457                    if pending.selection.reversed && mode.is_visual() && !last_mode.is_visual() {
458                        let mut end = pending.selection.end.to_point(&snapshot.buffer_snapshot);
459                        end = snapshot
460                            .buffer_snapshot
461                            .clip_point(end + Point::new(0, 1), Bias::Right);
462                        pending.selection.end = snapshot.buffer_snapshot.anchor_before(end);
463                    }
464                }
465
466                s.move_with(|map, selection| {
467                    if last_mode.is_visual() && !mode.is_visual() {
468                        let mut point = selection.head();
469                        if !selection.reversed && !selection.is_empty() {
470                            point = movement::left(map, selection.head());
471                        }
472                        selection.collapse_to(point, selection.goal)
473                    } else if !last_mode.is_visual() && mode.is_visual() {
474                        if selection.is_empty() {
475                            selection.end = movement::right(map, selection.start);
476                        }
477                    } else if last_mode == Mode::Replace {
478                        if selection.head().column() != 0 {
479                            let point = movement::left(map, selection.head());
480                            selection.collapse_to(point, selection.goal)
481                        }
482                    }
483                });
484            })
485        });
486    }
487
488    fn push_count_digit(&mut self, number: usize, cx: &mut WindowContext) {
489        if self.active_operator().is_some() {
490            self.update_state(|state| {
491                state.post_count = Some(state.post_count.unwrap_or(0) * 10 + number)
492            })
493        } else {
494            self.update_state(|state| {
495                state.pre_count = Some(state.pre_count.unwrap_or(0) * 10 + number)
496            })
497        }
498        // update the keymap so that 0 works
499        self.sync_vim_settings(cx)
500    }
501
502    fn take_count(&mut self, cx: &mut WindowContext) -> Option<usize> {
503        if self.workspace_state.replaying {
504            return self.workspace_state.recorded_count;
505        }
506
507        let count = if self.state().post_count == None && self.state().pre_count == None {
508            return None;
509        } else {
510            Some(self.update_state(|state| {
511                state.post_count.take().unwrap_or(1) * state.pre_count.take().unwrap_or(1)
512            }))
513        };
514        if self.workspace_state.recording {
515            self.workspace_state.recorded_count = count;
516        }
517        self.sync_vim_settings(cx);
518        count
519    }
520
521    fn push_operator(&mut self, operator: Operator, cx: &mut WindowContext) {
522        if matches!(
523            operator,
524            Operator::Change | Operator::Delete | Operator::Replace
525        ) {
526            self.start_recording(cx)
527        };
528        // Since these operations can only be entered with pre-operators,
529        // we need to clear the previous operators when pushing,
530        // so that the current stack is the most correct
531        if matches!(
532            operator,
533            Operator::AddSurrounds { .. }
534                | Operator::ChangeSurrounds { .. }
535                | Operator::DeleteSurrounds
536        ) {
537            self.update_state(|state| state.operator_stack.clear());
538        };
539        self.update_state(|state| state.operator_stack.push(operator));
540        self.sync_vim_settings(cx);
541    }
542
543    fn maybe_pop_operator(&mut self) -> Option<Operator> {
544        self.update_state(|state| state.operator_stack.pop())
545    }
546
547    fn pop_operator(&mut self, cx: &mut WindowContext) -> Operator {
548        let popped_operator = self.update_state(|state| state.operator_stack.pop())
549            .expect("Operator popped when no operator was on the stack. This likely means there is an invalid keymap config");
550        self.sync_vim_settings(cx);
551        popped_operator
552    }
553
554    fn clear_operator(&mut self, cx: &mut WindowContext) {
555        self.take_count(cx);
556        self.update_state(|state| state.operator_stack.clear());
557        self.sync_vim_settings(cx);
558    }
559
560    fn active_operator(&self) -> Option<Operator> {
561        self.state().operator_stack.last().cloned()
562    }
563
564    fn transaction_begun(&mut self, transaction_id: TransactionId, _: &mut WindowContext) {
565        self.update_state(|state| {
566            let mode = if (state.mode == Mode::Insert
567                || state.mode == Mode::Replace
568                || state.mode == Mode::Normal)
569                && state.current_tx.is_none()
570            {
571                state.current_tx = Some(transaction_id);
572                state.last_mode
573            } else {
574                state.mode
575            };
576            if mode == Mode::VisualLine || mode == Mode::VisualBlock {
577                state.undo_modes.insert(transaction_id, mode);
578            }
579        });
580    }
581
582    fn transaction_undone(&mut self, transaction_id: &TransactionId, cx: &mut WindowContext) {
583        if !self.state().mode.is_visual() {
584            return;
585        };
586        self.update_active_editor(cx, |vim, editor, cx| {
587            let original_mode = vim.state().undo_modes.get(transaction_id);
588            editor.change_selections(None, cx, |s| match original_mode {
589                Some(Mode::VisualLine) => {
590                    s.move_with(|map, selection| {
591                        selection.collapse_to(
592                            map.prev_line_boundary(selection.start.to_point(map)).1,
593                            SelectionGoal::None,
594                        )
595                    });
596                }
597                Some(Mode::VisualBlock) => {
598                    let mut first = s.first_anchor();
599                    first.collapse_to(first.start, first.goal);
600                    s.select_anchors(vec![first]);
601                }
602                _ => {
603                    s.move_with(|_, selection| {
604                        selection.collapse_to(selection.start, selection.goal);
605                    });
606                }
607            });
608        });
609        self.switch_mode(Mode::Normal, true, cx)
610    }
611
612    fn local_selections_changed(&mut self, editor: View<Editor>, cx: &mut WindowContext) {
613        let newest = editor.read(cx).selections.newest_anchor().clone();
614        let is_multicursor = editor.read(cx).selections.count() > 1;
615
616        let state = self.state();
617        if state.mode == Mode::Insert && state.current_tx.is_some() {
618            if state.current_anchor.is_none() {
619                self.update_state(|state| state.current_anchor = Some(newest));
620            } else if state.current_anchor.as_ref().unwrap() != &newest {
621                if let Some(tx_id) = self.update_state(|state| state.current_tx.take()) {
622                    self.update_active_editor(cx, |_, editor, cx| {
623                        editor.group_until_transaction(tx_id, cx)
624                    });
625                }
626            }
627        } else if state.mode == Mode::Normal && newest.start != newest.end {
628            if matches!(newest.goal, SelectionGoal::HorizontalRange { .. }) {
629                self.switch_mode(Mode::VisualBlock, false, cx);
630            } else {
631                self.switch_mode(Mode::Visual, false, cx)
632            }
633        } else if newest.start == newest.end
634            && !is_multicursor
635            && [Mode::Visual, Mode::VisualLine, Mode::VisualBlock].contains(&state.mode)
636        {
637            self.switch_mode(Mode::Normal, true, cx)
638        }
639    }
640
641    fn active_editor_input_ignored(text: Arc<str>, cx: &mut WindowContext) {
642        if text.is_empty() {
643            return;
644        }
645
646        match Vim::read(cx).active_operator() {
647            Some(Operator::FindForward { before }) => {
648                let find = Motion::FindForward {
649                    before,
650                    char: text.chars().next().unwrap(),
651                    mode: if VimSettings::get_global(cx).use_multiline_find {
652                        FindRange::MultiLine
653                    } else {
654                        FindRange::SingleLine
655                    },
656                    smartcase: VimSettings::get_global(cx).use_smartcase_find,
657                };
658                Vim::update(cx, |vim, _| {
659                    vim.workspace_state.last_find = Some(find.clone())
660                });
661                motion::motion(find, cx)
662            }
663            Some(Operator::FindBackward { after }) => {
664                let find = Motion::FindBackward {
665                    after,
666                    char: text.chars().next().unwrap(),
667                    mode: if VimSettings::get_global(cx).use_multiline_find {
668                        FindRange::MultiLine
669                    } else {
670                        FindRange::SingleLine
671                    },
672                    smartcase: VimSettings::get_global(cx).use_smartcase_find,
673                };
674                Vim::update(cx, |vim, _| {
675                    vim.workspace_state.last_find = Some(find.clone())
676                });
677                motion::motion(find, cx)
678            }
679            Some(Operator::Replace) => match Vim::read(cx).state().mode {
680                Mode::Normal => normal_replace(text, cx),
681                Mode::Visual | Mode::VisualLine | Mode::VisualBlock => visual_replace(text, cx),
682                _ => Vim::update(cx, |vim, cx| vim.clear_operator(cx)),
683            },
684            Some(Operator::AddSurrounds { target }) => match Vim::read(cx).state().mode {
685                Mode::Normal => {
686                    if let Some(target) = target {
687                        add_surrounds(text, target, cx);
688                        Vim::update(cx, |vim, cx| vim.clear_operator(cx));
689                    }
690                }
691                _ => Vim::update(cx, |vim, cx| vim.clear_operator(cx)),
692            },
693            Some(Operator::ChangeSurrounds { target }) => match Vim::read(cx).state().mode {
694                Mode::Normal => {
695                    if let Some(target) = target {
696                        change_surrounds(text, target, cx);
697                        Vim::update(cx, |vim, cx| vim.clear_operator(cx));
698                    }
699                }
700                _ => Vim::update(cx, |vim, cx| vim.clear_operator(cx)),
701            },
702            Some(Operator::DeleteSurrounds) => match Vim::read(cx).state().mode {
703                Mode::Normal => {
704                    delete_surrounds(text, cx);
705                    Vim::update(cx, |vim, cx| vim.clear_operator(cx));
706                }
707                _ => Vim::update(cx, |vim, cx| vim.clear_operator(cx)),
708            },
709            _ => match Vim::read(cx).state().mode {
710                Mode::Replace => multi_replace(text, cx),
711                _ => {}
712            },
713        }
714    }
715
716    fn set_enabled(&mut self, enabled: bool, cx: &mut AppContext) {
717        if self.enabled == enabled {
718            return;
719        }
720        if !enabled {
721            CommandPaletteInterceptor::update_global(cx, |interceptor, _| {
722                interceptor.clear();
723            });
724            CommandPaletteFilter::update_global(cx, |filter, _| {
725                filter.hide_namespace(Self::NAMESPACE);
726            });
727            *self = Default::default();
728            return;
729        }
730
731        self.enabled = true;
732        CommandPaletteFilter::update_global(cx, |filter, _| {
733            filter.show_namespace(Self::NAMESPACE);
734        });
735        CommandPaletteInterceptor::update_global(cx, |interceptor, _| {
736            interceptor.set(Box::new(command::command_interceptor));
737        });
738
739        if let Some(active_window) = cx
740            .active_window()
741            .and_then(|window| window.downcast::<Workspace>())
742        {
743            active_window
744                .update(cx, |workspace, cx| {
745                    let active_editor = workspace.active_item_as::<Editor>(cx);
746                    if let Some(active_editor) = active_editor {
747                        self.activate_editor(active_editor, cx);
748                        self.switch_mode(Mode::Normal, false, cx);
749                    }
750                })
751                .ok();
752        }
753    }
754
755    /// Returns the state of the active editor.
756    pub fn state(&self) -> &EditorState {
757        if let Some(active_editor) = self.active_editor.as_ref() {
758            if let Some(state) = self.editor_states.get(&active_editor.entity_id()) {
759                return state;
760            }
761        }
762
763        &self.default_state
764    }
765
766    /// Updates the state of the active editor.
767    pub fn update_state<T>(&mut self, func: impl FnOnce(&mut EditorState) -> T) -> T {
768        let mut state = self.state().clone();
769        let ret = func(&mut state);
770
771        if let Some(active_editor) = self.active_editor.as_ref() {
772            self.editor_states.insert(active_editor.entity_id(), state);
773        }
774
775        ret
776    }
777
778    fn sync_vim_settings(&mut self, cx: &mut WindowContext) {
779        self.update_active_editor(cx, |vim, editor, cx| {
780            let state = vim.state();
781            editor.set_cursor_shape(state.cursor_shape(), cx);
782            editor.set_clip_at_line_ends(state.clip_at_line_ends(), cx);
783            editor.set_collapse_matches(true);
784            editor.set_input_enabled(!state.vim_controlled());
785            editor.set_autoindent(state.should_autoindent());
786            editor.selections.line_mode = matches!(state.mode, Mode::VisualLine);
787            if editor.is_focused(cx) {
788                editor.set_keymap_context_layer::<Self>(state.keymap_context_layer(), cx);
789            // disables vim if the rename editor is focused,
790            // but not if the command palette is open.
791            } else if editor.focus_handle(cx).contains_focused(cx) {
792                editor.remove_keymap_context_layer::<Self>(cx)
793            }
794        });
795    }
796
797    fn unhook_vim_settings(editor: &mut Editor, cx: &mut ViewContext<Editor>) {
798        if editor.mode() == EditorMode::Full {
799            editor.set_cursor_shape(CursorShape::Bar, cx);
800            editor.set_clip_at_line_ends(false, cx);
801            editor.set_collapse_matches(false);
802            editor.set_input_enabled(true);
803            editor.set_autoindent(true);
804            editor.selections.line_mode = false;
805        }
806        editor.remove_keymap_context_layer::<Self>(cx)
807    }
808}
809
810impl Settings for VimModeSetting {
811    const KEY: Option<&'static str> = Some("vim_mode");
812
813    type FileContent = Option<bool>;
814
815    fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
816        Ok(Self(sources.user.copied().flatten().unwrap_or(
817            sources.default.ok_or_else(Self::missing_default)?,
818        )))
819    }
820}
821
822/// Controls when to use system clipboard.
823#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
824#[serde(rename_all = "snake_case")]
825pub enum UseSystemClipboard {
826    /// Don't use system clipboard.
827    Never,
828    /// Use system clipboard.
829    Always,
830    /// Use system clipboard for yank operations.
831    OnYank,
832}
833
834#[derive(Deserialize)]
835struct VimSettings {
836    // all vim uses vim clipboard
837    // vim always uses system cliupbaord
838    // some magic where yy is system and dd is not.
839    pub use_system_clipboard: UseSystemClipboard,
840    pub use_multiline_find: bool,
841    pub use_smartcase_find: bool,
842}
843
844#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
845struct VimSettingsContent {
846    pub use_system_clipboard: Option<UseSystemClipboard>,
847    pub use_multiline_find: Option<bool>,
848    pub use_smartcase_find: Option<bool>,
849}
850
851impl Settings for VimSettings {
852    const KEY: Option<&'static str> = Some("vim");
853
854    type FileContent = VimSettingsContent;
855
856    fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
857        sources.json_merge()
858    }
859}