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