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    cx.update_global::<CommandPaletteFilter, _>(|filter, _| {
 91        filter.hidden_namespaces.insert("vim");
 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    fn read(cx: &mut AppContext) -> &Self {
195        cx.global::<Self>()
196    }
197
198    fn update<F, S>(cx: &mut WindowContext, update: F) -> S
199    where
200        F: FnOnce(&mut Self, &mut WindowContext) -> S,
201    {
202        cx.update_global(update)
203    }
204
205    fn activate_editor(&mut self, editor: View<Editor>, cx: &mut WindowContext) {
206        if !editor.read(cx).use_modal_editing() {
207            return;
208        }
209
210        self.active_editor = Some(editor.clone().downgrade());
211        self.editor_subscription = Some(cx.subscribe(&editor, |editor, event, cx| match event {
212            EditorEvent::SelectionsChanged { local: true } => {
213                let editor = editor.read(cx);
214                if editor.leader_peer_id().is_none() {
215                    let newest = editor.selections.newest_anchor().clone();
216                    let is_multicursor = editor.selections.count() > 1;
217                    Vim::update(cx, |vim, cx| {
218                        vim.local_selections_changed(newest, is_multicursor, cx);
219                    })
220                }
221            }
222            EditorEvent::InputIgnored { text } => {
223                Vim::active_editor_input_ignored(text.clone(), cx);
224                Vim::record_insertion(text, None, cx)
225            }
226            EditorEvent::InputHandled {
227                text,
228                utf16_range_to_replace: range_to_replace,
229            } => Vim::record_insertion(text, range_to_replace.clone(), cx),
230            EditorEvent::TransactionBegun { transaction_id } => Vim::update(cx, |vim, cx| {
231                vim.transaction_begun(*transaction_id, cx);
232            }),
233            EditorEvent::TransactionUndone { transaction_id } => Vim::update(cx, |vim, cx| {
234                vim.transaction_undone(transaction_id, cx);
235            }),
236            _ => {}
237        }));
238
239        let editor = editor.read(cx);
240        let editor_mode = editor.mode();
241        let newest_selection_empty = editor.selections.newest::<usize>(cx).is_empty();
242
243        if editor_mode == EditorMode::Full
244                && !newest_selection_empty
245                && self.state().mode == Mode::Normal
246                // When following someone, don't switch vim mode.
247                && editor.leader_peer_id().is_none()
248        {
249            self.switch_mode(Mode::Visual, true, cx);
250        }
251
252        self.sync_vim_settings(cx);
253    }
254
255    fn record_insertion(
256        text: &Arc<str>,
257        range_to_replace: Option<Range<isize>>,
258        cx: &mut WindowContext,
259    ) {
260        Vim::update(cx, |vim, _| {
261            if vim.workspace_state.recording {
262                vim.workspace_state
263                    .recorded_actions
264                    .push(ReplayableAction::Insertion {
265                        text: text.clone(),
266                        utf16_range_to_replace: range_to_replace,
267                    });
268                if vim.workspace_state.stop_recording_after_next_action {
269                    vim.workspace_state.recording = false;
270                    vim.workspace_state.stop_recording_after_next_action = false;
271                }
272            }
273        });
274    }
275
276    fn update_active_editor<S>(
277        &mut self,
278        cx: &mut WindowContext,
279        update: impl FnOnce(&mut Vim, &mut Editor, &mut ViewContext<Editor>) -> S,
280    ) -> Option<S> {
281        let editor = self.active_editor.clone()?.upgrade()?;
282        Some(editor.update(cx, |editor, cx| update(self, editor, cx)))
283    }
284
285    /// When doing an action that modifies the buffer, we start recording so that `.`
286    /// will replay the action.
287    pub fn start_recording(&mut self, cx: &mut WindowContext) {
288        if !self.workspace_state.replaying {
289            self.workspace_state.recording = true;
290            self.workspace_state.recorded_actions = Default::default();
291            self.workspace_state.recorded_count = None;
292
293            let selections = self
294                .active_editor
295                .as_ref()
296                .and_then(|editor| editor.upgrade())
297                .map(|editor| {
298                    let editor = editor.read(cx);
299                    (
300                        editor.selections.oldest::<Point>(cx),
301                        editor.selections.newest::<Point>(cx),
302                    )
303                });
304
305            if let Some((oldest, newest)) = selections {
306                self.workspace_state.recorded_selection = match self.state().mode {
307                    Mode::Visual if newest.end.row == newest.start.row => {
308                        RecordedSelection::SingleLine {
309                            cols: newest.end.column - newest.start.column,
310                        }
311                    }
312                    Mode::Visual => RecordedSelection::Visual {
313                        rows: newest.end.row - newest.start.row,
314                        cols: newest.end.column,
315                    },
316                    Mode::VisualLine => RecordedSelection::VisualLine {
317                        rows: newest.end.row - newest.start.row,
318                    },
319                    Mode::VisualBlock => RecordedSelection::VisualBlock {
320                        rows: newest.end.row.abs_diff(oldest.start.row),
321                        cols: newest.end.column.abs_diff(oldest.start.column),
322                    },
323                    _ => RecordedSelection::None,
324                }
325            } else {
326                self.workspace_state.recorded_selection = RecordedSelection::None;
327            }
328        }
329    }
330
331    /// When finishing an action that modifies the buffer, stop recording.
332    /// as you usually call this within a keystroke handler we also ensure that
333    /// the current action is recorded.
334    pub fn stop_recording(&mut self) {
335        if self.workspace_state.recording {
336            self.workspace_state.stop_recording_after_next_action = true;
337        }
338    }
339
340    /// Stops recording actions immediately rather than waiting until after the
341    /// next action to stop recording.
342    ///
343    /// This doesn't include the current action.
344    pub fn stop_recording_immediately(&mut self, action: Box<dyn Action>) {
345        if self.workspace_state.recording {
346            self.workspace_state
347                .recorded_actions
348                .push(ReplayableAction::Action(action.boxed_clone()));
349            self.workspace_state.recording = false;
350            self.workspace_state.stop_recording_after_next_action = false;
351        }
352    }
353
354    /// Explicitly record one action (equivalents to start_recording and stop_recording)
355    pub fn record_current_action(&mut self, cx: &mut WindowContext) {
356        self.start_recording(cx);
357        self.stop_recording();
358    }
359
360    fn switch_mode(&mut self, mode: Mode, leave_selections: bool, cx: &mut WindowContext) {
361        let state = self.state();
362        let last_mode = state.mode;
363        let prior_mode = state.last_mode;
364        let prior_tx = state.current_tx;
365        self.update_state(|state| {
366            state.last_mode = last_mode;
367            state.mode = mode;
368            state.operator_stack.clear();
369            state.current_tx.take();
370            state.current_anchor.take();
371        });
372        if mode != Mode::Insert {
373            self.take_count(cx);
374        }
375
376        // Sync editor settings like clip mode
377        self.sync_vim_settings(cx);
378
379        if leave_selections {
380            return;
381        }
382
383        // Adjust selections
384        self.update_active_editor(cx, |_, editor, cx| {
385            if last_mode != Mode::VisualBlock && last_mode.is_visual() && mode == Mode::VisualBlock
386            {
387                visual_block_motion(true, editor, cx, |_, point, goal| Some((point, goal)))
388            }
389            if last_mode == Mode::Insert {
390                if let Some(prior_tx) = prior_tx {
391                    editor.group_until_transaction(prior_tx, cx)
392                }
393            }
394
395            editor.change_selections(None, cx, |s| {
396                // we cheat with visual block mode and use multiple cursors.
397                // the cost of this cheat is we need to convert back to a single
398                // cursor whenever vim would.
399                if last_mode == Mode::VisualBlock
400                    && (mode != Mode::VisualBlock && mode != Mode::Insert)
401                {
402                    let tail = s.oldest_anchor().tail();
403                    let head = s.newest_anchor().head();
404                    s.select_anchor_ranges(vec![tail..head]);
405                } else if last_mode == Mode::Insert
406                    && prior_mode == Mode::VisualBlock
407                    && mode != Mode::VisualBlock
408                {
409                    let pos = s.first_anchor().head();
410                    s.select_anchor_ranges(vec![pos..pos])
411                }
412
413                s.move_with(|map, selection| {
414                    if last_mode.is_visual() && !mode.is_visual() {
415                        let mut point = selection.head();
416                        if !selection.reversed && !selection.is_empty() {
417                            point = movement::left(map, selection.head());
418                        }
419                        selection.collapse_to(point, selection.goal)
420                    } else if !last_mode.is_visual() && mode.is_visual() {
421                        if selection.is_empty() {
422                            selection.end = movement::right(map, selection.start);
423                        }
424                    } else if last_mode == Mode::Replace {
425                        if selection.head().column() != 0 {
426                            let point = movement::left(map, selection.head());
427                            selection.collapse_to(point, selection.goal)
428                        }
429                    }
430                });
431            })
432        });
433    }
434
435    fn push_count_digit(&mut self, number: usize, cx: &mut WindowContext) {
436        if self.active_operator().is_some() {
437            self.update_state(|state| {
438                state.post_count = Some(state.post_count.unwrap_or(0) * 10 + number)
439            })
440        } else {
441            self.update_state(|state| {
442                state.pre_count = Some(state.pre_count.unwrap_or(0) * 10 + number)
443            })
444        }
445        // update the keymap so that 0 works
446        self.sync_vim_settings(cx)
447    }
448
449    fn take_count(&mut self, cx: &mut WindowContext) -> Option<usize> {
450        if self.workspace_state.replaying {
451            return self.workspace_state.recorded_count;
452        }
453
454        let count = if self.state().post_count == None && self.state().pre_count == None {
455            return None;
456        } else {
457            Some(self.update_state(|state| {
458                state.post_count.take().unwrap_or(1) * state.pre_count.take().unwrap_or(1)
459            }))
460        };
461        if self.workspace_state.recording {
462            self.workspace_state.recorded_count = count;
463        }
464        self.sync_vim_settings(cx);
465        count
466    }
467
468    fn push_operator(&mut self, operator: Operator, cx: &mut WindowContext) {
469        if matches!(
470            operator,
471            Operator::Change | Operator::Delete | Operator::Replace
472        ) {
473            self.start_recording(cx)
474        };
475        self.update_state(|state| state.operator_stack.push(operator));
476        self.sync_vim_settings(cx);
477    }
478
479    fn maybe_pop_operator(&mut self) -> Option<Operator> {
480        self.update_state(|state| state.operator_stack.pop())
481    }
482
483    fn pop_operator(&mut self, cx: &mut WindowContext) -> Operator {
484        let popped_operator = self.update_state(|state| state.operator_stack.pop())
485            .expect("Operator popped when no operator was on the stack. This likely means there is an invalid keymap config");
486        self.sync_vim_settings(cx);
487        popped_operator
488    }
489    fn clear_operator(&mut self, cx: &mut WindowContext) {
490        self.take_count(cx);
491        self.update_state(|state| state.operator_stack.clear());
492        self.sync_vim_settings(cx);
493    }
494
495    fn active_operator(&self) -> Option<Operator> {
496        self.state().operator_stack.last().cloned()
497    }
498
499    fn transaction_begun(&mut self, transaction_id: TransactionId, _: &mut WindowContext) {
500        self.update_state(|state| {
501            let mode = if (state.mode == Mode::Insert || state.mode == Mode::Normal)
502                && state.current_tx.is_none()
503            {
504                state.current_tx = Some(transaction_id);
505                state.last_mode
506            } else {
507                state.mode
508            };
509            if mode == Mode::VisualLine || mode == Mode::VisualBlock {
510                state.undo_modes.insert(transaction_id, mode);
511            }
512        });
513    }
514
515    fn transaction_undone(&mut self, transaction_id: &TransactionId, cx: &mut WindowContext) {
516        if !self.state().mode.is_visual() {
517            return;
518        };
519        self.update_active_editor(cx, |vim, editor, cx| {
520            let original_mode = vim.state().undo_modes.get(transaction_id);
521            editor.change_selections(None, cx, |s| match original_mode {
522                Some(Mode::VisualLine) => {
523                    s.move_with(|map, selection| {
524                        selection.collapse_to(
525                            map.prev_line_boundary(selection.start.to_point(map)).1,
526                            SelectionGoal::None,
527                        )
528                    });
529                }
530                Some(Mode::VisualBlock) => {
531                    let mut first = s.first_anchor();
532                    first.collapse_to(first.start, first.goal);
533                    s.select_anchors(vec![first]);
534                }
535                _ => {
536                    s.move_with(|_, selection| {
537                        selection.collapse_to(selection.start, selection.goal);
538                    });
539                }
540            });
541        });
542        self.switch_mode(Mode::Normal, true, cx)
543    }
544
545    fn local_selections_changed(
546        &mut self,
547        newest: Selection<editor::Anchor>,
548        is_multicursor: bool,
549        cx: &mut WindowContext,
550    ) {
551        let state = self.state();
552        if state.mode == Mode::Insert && state.current_tx.is_some() {
553            if state.current_anchor.is_none() {
554                self.update_state(|state| state.current_anchor = Some(newest));
555            } else if state.current_anchor.as_ref().unwrap() != &newest {
556                if let Some(tx_id) = self.update_state(|state| state.current_tx.take()) {
557                    self.update_active_editor(cx, |_, editor, cx| {
558                        editor.group_until_transaction(tx_id, cx)
559                    });
560                }
561            }
562        } else if state.mode == Mode::Normal && newest.start != newest.end {
563            if matches!(newest.goal, SelectionGoal::HorizontalRange { .. }) {
564                self.switch_mode(Mode::VisualBlock, false, cx);
565            } else {
566                self.switch_mode(Mode::Visual, false, cx)
567            }
568        } else if newest.start == newest.end
569            && !is_multicursor
570            && [Mode::Visual, Mode::VisualLine, Mode::VisualBlock].contains(&state.mode)
571        {
572            self.switch_mode(Mode::Normal, true, cx)
573        }
574    }
575
576    fn active_editor_input_ignored(text: Arc<str>, cx: &mut WindowContext) {
577        if text.is_empty() {
578            return;
579        }
580
581        match Vim::read(cx).active_operator() {
582            Some(Operator::FindForward { before }) => {
583                let find = Motion::FindForward {
584                    before,
585                    char: text.chars().next().unwrap(),
586                    mode: if VimSettings::get_global(cx).use_multiline_find {
587                        FindRange::MultiLine
588                    } else {
589                        FindRange::SingleLine
590                    },
591                    smartcase: VimSettings::get_global(cx).use_smartcase_find,
592                };
593                Vim::update(cx, |vim, _| {
594                    vim.workspace_state.last_find = Some(find.clone())
595                });
596                motion::motion(find, cx)
597            }
598            Some(Operator::FindBackward { after }) => {
599                let find = Motion::FindBackward {
600                    after,
601                    char: text.chars().next().unwrap(),
602                    mode: if VimSettings::get_global(cx).use_multiline_find {
603                        FindRange::MultiLine
604                    } else {
605                        FindRange::SingleLine
606                    },
607                    smartcase: VimSettings::get_global(cx).use_smartcase_find,
608                };
609                Vim::update(cx, |vim, _| {
610                    vim.workspace_state.last_find = Some(find.clone())
611                });
612                motion::motion(find, cx)
613            }
614            Some(Operator::Replace) => match Vim::read(cx).state().mode {
615                Mode::Normal => normal_replace(text, cx),
616                Mode::Visual | Mode::VisualLine | Mode::VisualBlock => visual_replace(text, cx),
617                _ => Vim::update(cx, |vim, cx| vim.clear_operator(cx)),
618            },
619            _ => match Vim::read(cx).state().mode {
620                Mode::Replace => multi_replace(text, cx),
621                _ => {}
622            },
623        }
624    }
625
626    fn set_enabled(&mut self, enabled: bool, cx: &mut AppContext) {
627        if self.enabled == enabled {
628            return;
629        }
630        if !enabled {
631            let _ = cx.remove_global::<CommandPaletteInterceptor>();
632            cx.update_global::<CommandPaletteFilter, _>(|filter, _| {
633                filter.hidden_namespaces.insert("vim");
634            });
635            *self = Default::default();
636            return;
637        }
638
639        self.enabled = true;
640        cx.update_global::<CommandPaletteFilter, _>(|filter, _| {
641            filter.hidden_namespaces.remove("vim");
642        });
643        cx.set_global::<CommandPaletteInterceptor>(CommandPaletteInterceptor(Box::new(
644            command::command_interceptor,
645        )));
646
647        if let Some(active_window) = cx
648            .active_window()
649            .and_then(|window| window.downcast::<Workspace>())
650        {
651            active_window
652                .update(cx, |workspace, cx| {
653                    let active_editor = workspace.active_item_as::<Editor>(cx);
654                    if let Some(active_editor) = active_editor {
655                        self.activate_editor(active_editor, cx);
656                        self.switch_mode(Mode::Normal, false, cx);
657                    }
658                })
659                .ok();
660        }
661    }
662
663    /// Returns the state of the active editor.
664    pub fn state(&self) -> &EditorState {
665        if let Some(active_editor) = self.active_editor.as_ref() {
666            if let Some(state) = self.editor_states.get(&active_editor.entity_id()) {
667                return state;
668            }
669        }
670
671        &self.default_state
672    }
673
674    /// Updates the state of the active editor.
675    pub fn update_state<T>(&mut self, func: impl FnOnce(&mut EditorState) -> T) -> T {
676        let mut state = self.state().clone();
677        let ret = func(&mut state);
678
679        if let Some(active_editor) = self.active_editor.as_ref() {
680            self.editor_states.insert(active_editor.entity_id(), state);
681        }
682
683        ret
684    }
685
686    fn sync_vim_settings(&mut self, cx: &mut WindowContext) {
687        self.update_active_editor(cx, |vim, editor, cx| {
688            let state = vim.state();
689            editor.set_cursor_shape(state.cursor_shape(), cx);
690            editor.set_clip_at_line_ends(state.clip_at_line_ends(), cx);
691            editor.set_collapse_matches(true);
692            editor.set_input_enabled(!state.vim_controlled());
693            editor.set_autoindent(state.should_autoindent());
694            editor.selections.line_mode = matches!(state.mode, Mode::VisualLine);
695            if editor.is_focused(cx) {
696                editor.set_keymap_context_layer::<Self>(state.keymap_context_layer(), cx);
697            } else {
698                editor.remove_keymap_context_layer::<Self>(cx);
699            }
700        });
701    }
702
703    fn unhook_vim_settings(editor: &mut Editor, cx: &mut ViewContext<Editor>) {
704        if editor.mode() == EditorMode::Full {
705            editor.set_cursor_shape(CursorShape::Bar, cx);
706            editor.set_clip_at_line_ends(false, cx);
707            editor.set_collapse_matches(false);
708            editor.set_input_enabled(true);
709            editor.set_autoindent(true);
710            editor.selections.line_mode = false;
711        }
712        editor.remove_keymap_context_layer::<Self>(cx)
713    }
714}
715
716impl Settings for VimModeSetting {
717    const KEY: Option<&'static str> = Some("vim_mode");
718
719    type FileContent = Option<bool>;
720
721    fn load(
722        default_value: &Self::FileContent,
723        user_values: &[&Self::FileContent],
724        _: &mut AppContext,
725    ) -> Result<Self> {
726        Ok(Self(user_values.iter().rev().find_map(|v| **v).unwrap_or(
727            default_value.ok_or_else(Self::missing_default)?,
728        )))
729    }
730}
731
732/// Controls when to use system clipboard.
733#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
734#[serde(rename_all = "snake_case")]
735pub enum UseSystemClipboard {
736    /// Don't use system clipboard.
737    Never,
738    /// Use system clipboard.
739    Always,
740    /// Use system clipboard for yank operations.
741    OnYank,
742}
743
744#[derive(Deserialize)]
745struct VimSettings {
746    // all vim uses vim clipboard
747    // vim always uses system cliupbaord
748    // some magic where yy is system and dd is not.
749    pub use_system_clipboard: UseSystemClipboard,
750    pub use_multiline_find: bool,
751    pub use_smartcase_find: bool,
752}
753
754#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
755struct VimSettingsContent {
756    pub use_system_clipboard: Option<UseSystemClipboard>,
757    pub use_multiline_find: Option<bool>,
758    pub use_smartcase_find: Option<bool>,
759}
760
761impl Settings for VimSettings {
762    const KEY: Option<&'static str> = Some("vim");
763
764    type FileContent = VimSettingsContent;
765
766    fn load(
767        default_value: &Self::FileContent,
768        user_values: &[&Self::FileContent],
769        _: &mut AppContext,
770    ) -> Result<Self> {
771        Self::load_via_json_merge(default_value, user_values)
772    }
773}