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