vim.rs

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