vim.rs

  1mod editor_events;
  2mod insert;
  3mod mode;
  4mod normal;
  5#[cfg(test)]
  6mod vim_test_context;
  7
  8use collections::HashMap;
  9use editor::{CursorShape, Editor};
 10use gpui::{action, MutableAppContext, ViewContext, WeakViewHandle};
 11
 12use mode::Mode;
 13use settings::Settings;
 14use workspace::{self, Workspace};
 15
 16action!(SwitchMode, Mode);
 17
 18pub fn init(cx: &mut MutableAppContext) {
 19    editor_events::init(cx);
 20    insert::init(cx);
 21    normal::init(cx);
 22
 23    cx.add_action(|_: &mut Workspace, action: &SwitchMode, cx| {
 24        VimState::update_global(cx, |state, cx| state.switch_mode(action, cx))
 25    });
 26
 27    cx.observe_global::<Settings, _>(|settings, cx| {
 28        VimState::update_global(cx, |state, cx| state.set_enabled(settings.vim_mode, cx))
 29    })
 30    .detach();
 31}
 32
 33#[derive(Default)]
 34pub struct VimState {
 35    editors: HashMap<usize, WeakViewHandle<Editor>>,
 36    active_editor: Option<WeakViewHandle<Editor>>,
 37
 38    enabled: bool,
 39    mode: Mode,
 40}
 41
 42impl VimState {
 43    fn update_global<F, S>(cx: &mut MutableAppContext, update: F) -> S
 44    where
 45        F: FnOnce(&mut Self, &mut MutableAppContext) -> S,
 46    {
 47        cx.update_default_global(update)
 48    }
 49
 50    fn update_active_editor<S>(
 51        &self,
 52        cx: &mut MutableAppContext,
 53        update: impl FnOnce(&mut Editor, &mut ViewContext<Editor>) -> S,
 54    ) -> Option<S> {
 55        self.active_editor
 56            .clone()
 57            .and_then(|ae| ae.upgrade(cx))
 58            .map(|ae| ae.update(cx, update))
 59    }
 60
 61    fn switch_mode(&mut self, SwitchMode(mode): &SwitchMode, cx: &mut MutableAppContext) {
 62        self.mode = *mode;
 63        self.sync_editor_options(cx);
 64    }
 65
 66    fn set_enabled(&mut self, enabled: bool, cx: &mut MutableAppContext) {
 67        if self.enabled != enabled {
 68            self.enabled = enabled;
 69            self.mode = Default::default();
 70            if enabled {
 71                self.mode = Mode::normal();
 72            }
 73            self.sync_editor_options(cx);
 74        }
 75    }
 76
 77    fn sync_editor_options(&self, cx: &mut MutableAppContext) {
 78        let mode = self.mode;
 79        let cursor_shape = mode.cursor_shape();
 80        for editor in self.editors.values() {
 81            if let Some(editor) = editor.upgrade(cx) {
 82                editor.update(cx, |editor, cx| {
 83                    if self.enabled {
 84                        editor.set_cursor_shape(cursor_shape, cx);
 85                        editor.set_clip_at_line_ends(cursor_shape == CursorShape::Block, cx);
 86                        editor.set_input_enabled(mode == Mode::Insert);
 87                        let context_layer = mode.keymap_context_layer();
 88                        editor.set_keymap_context_layer::<Self>(context_layer);
 89                    } else {
 90                        editor.set_cursor_shape(CursorShape::Bar, cx);
 91                        editor.set_clip_at_line_ends(false, cx);
 92                        editor.set_input_enabled(true);
 93                        editor.remove_keymap_context_layer::<Self>();
 94                    }
 95                });
 96            }
 97        }
 98    }
 99}
100
101#[cfg(test)]
102mod test {
103    use crate::{mode::Mode, vim_test_context::VimTestContext};
104
105    #[gpui::test]
106    async fn test_initially_disabled(cx: &mut gpui::TestAppContext) {
107        let mut cx = VimTestContext::new(cx, false, "").await;
108        cx.simulate_keystrokes(&["h", "j", "k", "l"]);
109        cx.assert_editor_state("hjkl|");
110    }
111
112    #[gpui::test]
113    async fn test_toggle_through_settings(cx: &mut gpui::TestAppContext) {
114        let mut cx = VimTestContext::new(cx, true, "").await;
115
116        cx.simulate_keystroke("i");
117        assert_eq!(cx.mode(), Mode::Insert);
118
119        // Editor acts as though vim is disabled
120        cx.disable_vim();
121        cx.simulate_keystrokes(&["h", "j", "k", "l"]);
122        cx.assert_editor_state("hjkl|");
123
124        // Enabling dynamically sets vim mode again and restores normal mode
125        cx.enable_vim();
126        assert_eq!(cx.mode(), Mode::normal());
127        cx.simulate_keystrokes(&["h", "h", "h", "l"]);
128        assert_eq!(cx.editor_text(), "hjkl".to_owned());
129        cx.assert_editor_state("hj|kl");
130        cx.simulate_keystrokes(&["i", "T", "e", "s", "t"]);
131        cx.assert_editor_state("hjTest|kl");
132
133        // Disabling and enabling resets to normal mode
134        assert_eq!(cx.mode(), Mode::Insert);
135        cx.disable_vim();
136        cx.enable_vim();
137        assert_eq!(cx.mode(), Mode::normal());
138    }
139}