Skip to content

Commit

Permalink
Merge pull request #686 from zed-industries/disable-vim-on-start
Browse files Browse the repository at this point in the history
Fully disable vim mode on start unless it's enabled
  • Loading branch information
nathansobo authored Mar 27, 2022
2 parents 2837125 + c6ad667 commit 3ae5fc7
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 88 deletions.
26 changes: 17 additions & 9 deletions crates/gpui/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -782,7 +782,7 @@ type GlobalActionCallback = dyn FnMut(&dyn AnyAction, &mut MutableAppContext);
type SubscriptionCallback = Box<dyn FnMut(&dyn Any, &mut MutableAppContext) -> bool>;
type GlobalSubscriptionCallback = Box<dyn FnMut(&dyn Any, &mut MutableAppContext)>;
type ObservationCallback = Box<dyn FnMut(&mut MutableAppContext) -> bool>;
type GlobalObservationCallback = Box<dyn FnMut(&mut MutableAppContext)>;
type GlobalObservationCallback = Box<dyn FnMut(&dyn Any, &mut MutableAppContext)>;
type ReleaseObservationCallback = Box<dyn FnMut(&dyn Any, &mut MutableAppContext)>;

pub struct MutableAppContext {
Expand Down Expand Up @@ -1222,10 +1222,10 @@ impl MutableAppContext {
}
}

pub fn observe_global<G, F>(&mut self, observe: F) -> Subscription
pub fn observe_global<G, F>(&mut self, mut observe: F) -> Subscription
where
G: Any,
F: 'static + FnMut(&mut MutableAppContext),
F: 'static + FnMut(&G, &mut MutableAppContext),
{
let type_id = TypeId::of::<G>();
let id = post_inc(&mut self.next_subscription_id);
Expand All @@ -1234,7 +1234,14 @@ impl MutableAppContext {
.lock()
.entry(type_id)
.or_default()
.insert(id, Some(Box::new(observe)));
.insert(
id,
Some(
Box::new(move |global: &dyn Any, cx: &mut MutableAppContext| {
observe(global.downcast_ref().unwrap(), cx)
}) as GlobalObservationCallback,
),
);

Subscription::GlobalObservation {
id,
Expand Down Expand Up @@ -2075,10 +2082,10 @@ impl MutableAppContext {
fn notify_global_observers(&mut self, observed_type_id: TypeId) {
let callbacks = self.global_observations.lock().remove(&observed_type_id);
if let Some(callbacks) = callbacks {
if self.cx.globals.contains_key(&observed_type_id) {
if let Some(global) = self.cx.globals.remove(&observed_type_id) {
for (id, callback) in callbacks {
if let Some(mut callback) = callback {
callback(self);
callback(global.as_ref(), self);
match self
.global_observations
.lock()
Expand All @@ -2095,6 +2102,7 @@ impl MutableAppContext {
}
}
}
self.cx.globals.insert(observed_type_id, global);
}
}
}
Expand Down Expand Up @@ -5232,7 +5240,7 @@ mod tests {
let observation_count = Rc::new(RefCell::new(0));
let subscription = cx.observe_global::<Global, _>({
let observation_count = observation_count.clone();
move |_| {
move |_, _| {
*observation_count.borrow_mut() += 1;
}
});
Expand Down Expand Up @@ -5262,7 +5270,7 @@ mod tests {
let observation_count = Rc::new(RefCell::new(0));
cx.observe_global::<OtherGlobal, _>({
let observation_count = observation_count.clone();
move |_| {
move |_, _| {
*observation_count.borrow_mut() += 1;
}
})
Expand Down Expand Up @@ -5636,7 +5644,7 @@ mod tests {
*subscription.borrow_mut() = Some(cx.observe_global::<(), _>({
let observation_count = observation_count.clone();
let subscription = subscription.clone();
move |_| {
move |_, _| {
subscription.borrow_mut().take();
*observation_count.borrow_mut() += 1;
}
Expand Down
18 changes: 9 additions & 9 deletions crates/vim/src/editor_events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ pub fn init(cx: &mut MutableAppContext) {
fn editor_created(EditorCreated(editor): &EditorCreated, cx: &mut MutableAppContext) {
cx.update_default_global(|vim_state: &mut VimState, cx| {
vim_state.editors.insert(editor.id(), editor.downgrade());
VimState::sync_editor_options(cx);
vim_state.sync_editor_options(cx);
})
}

Expand All @@ -24,21 +24,21 @@ fn editor_focused(EditorFocused(editor): &EditorFocused, cx: &mut MutableAppCont
Mode::Normal
};

cx.update_default_global(|vim_state: &mut VimState, _| {
vim_state.active_editor = Some(editor.downgrade());
VimState::update_global(cx, |state, cx| {
state.active_editor = Some(editor.downgrade());
state.switch_mode(&SwitchMode(mode), cx);
});
VimState::switch_mode(&SwitchMode(mode), cx);
}

fn editor_blurred(EditorBlurred(editor): &EditorBlurred, cx: &mut MutableAppContext) {
cx.update_default_global(|vim_state: &mut VimState, _| {
if let Some(previous_editor) = vim_state.active_editor.clone() {
VimState::update_global(cx, |state, cx| {
if let Some(previous_editor) = state.active_editor.clone() {
if previous_editor == editor.clone() {
vim_state.active_editor = None;
state.active_editor = None;
}
}
});
VimState::sync_editor_options(cx);
state.sync_editor_options(cx);
})
}

fn editor_released(EditorReleased(editor): &EditorReleased, cx: &mut MutableAppContext) {
Expand Down
14 changes: 8 additions & 6 deletions crates/vim/src/insert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@ pub fn init(cx: &mut MutableAppContext) {
}

fn normal_before(_: &mut Workspace, _: &NormalBefore, cx: &mut ViewContext<Workspace>) {
VimState::update_active_editor(cx, |editor, cx| {
editor.move_cursors(cx, |map, mut cursor, _| {
*cursor.column_mut() = cursor.column().saturating_sub(1);
(map.clip_point(cursor, Bias::Left), SelectionGoal::None)
VimState::update_global(cx, |state, cx| {
state.update_active_editor(cx, |editor, cx| {
editor.move_cursors(cx, |map, mut cursor, _| {
*cursor.column_mut() = cursor.column().saturating_sub(1);
(map.clip_point(cursor, Bias::Left), SelectionGoal::None)
});
});
});
VimState::switch_mode(&SwitchMode(Mode::Normal), cx);
state.switch_mode(&SwitchMode(Mode::Normal), cx);
})
}
34 changes: 21 additions & 13 deletions crates/vim/src/normal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,31 +28,39 @@ pub fn init(cx: &mut MutableAppContext) {
}

fn move_left(_: &mut Workspace, _: &MoveLeft, cx: &mut ViewContext<Workspace>) {
VimState::update_active_editor(cx, |editor, cx| {
editor.move_cursors(cx, |map, mut cursor, _| {
*cursor.column_mut() = cursor.column().saturating_sub(1);
(map.clip_point(cursor, Bias::Left), SelectionGoal::None)
VimState::update_global(cx, |state, cx| {
state.update_active_editor(cx, |editor, cx| {
editor.move_cursors(cx, |map, mut cursor, _| {
*cursor.column_mut() = cursor.column().saturating_sub(1);
(map.clip_point(cursor, Bias::Left), SelectionGoal::None)
});
});
});
})
}

fn move_down(_: &mut Workspace, _: &MoveDown, cx: &mut ViewContext<Workspace>) {
VimState::update_active_editor(cx, |editor, cx| {
editor.move_cursors(cx, movement::down);
VimState::update_global(cx, |state, cx| {
state.update_active_editor(cx, |editor, cx| {
editor.move_cursors(cx, movement::down);
});
});
}

fn move_up(_: &mut Workspace, _: &MoveUp, cx: &mut ViewContext<Workspace>) {
VimState::update_active_editor(cx, |editor, cx| {
editor.move_cursors(cx, movement::up);
VimState::update_global(cx, |state, cx| {
state.update_active_editor(cx, |editor, cx| {
editor.move_cursors(cx, movement::up);
});
});
}

fn move_right(_: &mut Workspace, _: &MoveRight, cx: &mut ViewContext<Workspace>) {
VimState::update_active_editor(cx, |editor, cx| {
editor.move_cursors(cx, |map, mut cursor, _| {
*cursor.column_mut() += 1;
(map.clip_point(cursor, Bias::Right), SelectionGoal::None)
VimState::update_global(cx, |state, cx| {
state.update_active_editor(cx, |editor, cx| {
editor.move_cursors(cx, |map, mut cursor, _| {
*cursor.column_mut() += 1;
(map.clip_point(cursor, Bias::Right), SelectionGoal::None)
});
});
});
}
91 changes: 46 additions & 45 deletions crates/vim/src/vim.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,14 @@ pub fn init(cx: &mut MutableAppContext) {
insert::init(cx);
normal::init(cx);

cx.add_action(|_: &mut Workspace, action: &SwitchMode, cx| VimState::switch_mode(action, cx));
cx.add_action(|_: &mut Workspace, action: &SwitchMode, cx| {
VimState::update_global(cx, |state, cx| state.switch_mode(action, cx))
});

cx.observe_global::<Settings, _>(VimState::settings_changed)
.detach();
cx.observe_global::<Settings, _>(|settings, cx| {
VimState::update_global(cx, |state, cx| state.set_enabled(settings.vim_mode, cx))
})
.detach();
}

#[derive(Default)]
Expand All @@ -35,62 +39,59 @@ pub struct VimState {
}

impl VimState {
fn update_global<F, S>(cx: &mut MutableAppContext, update: F) -> S
where
F: FnOnce(&mut Self, &mut MutableAppContext) -> S,
{
cx.update_default_global(update)
}

fn update_active_editor<S>(
&self,
cx: &mut MutableAppContext,
update: impl FnOnce(&mut Editor, &mut ViewContext<Editor>) -> S,
) -> Option<S> {
cx.global::<Self>()
.active_editor
self.active_editor
.clone()
.and_then(|ae| ae.upgrade(cx))
.map(|ae| ae.update(cx, update))
}

fn switch_mode(SwitchMode(mode): &SwitchMode, cx: &mut MutableAppContext) {
cx.update_default_global(|this: &mut Self, _| {
this.mode = *mode;
});

VimState::sync_editor_options(cx);
fn switch_mode(&mut self, SwitchMode(mode): &SwitchMode, cx: &mut MutableAppContext) {
self.mode = *mode;
self.sync_editor_options(cx);
}

fn settings_changed(cx: &mut MutableAppContext) {
cx.update_default_global(|this: &mut Self, cx| {
let settings = cx.global::<Settings>();
if this.enabled != settings.vim_mode {
this.enabled = settings.vim_mode;
this.mode = if settings.vim_mode {
Mode::Normal
} else {
Mode::Insert
};
Self::sync_editor_options(cx);
fn set_enabled(&mut self, enabled: bool, cx: &mut MutableAppContext) {
if self.enabled != enabled {
self.enabled = enabled;
if enabled {
self.mode = Mode::Normal;
}
});
self.sync_editor_options(cx);
}
}

fn sync_editor_options(cx: &mut MutableAppContext) {
cx.defer(move |cx| {
cx.update_default_global(|this: &mut VimState, cx| {
let mode = this.mode;
let cursor_shape = mode.cursor_shape();
let keymap_layer_active = this.enabled;
for editor in this.editors.values() {
if let Some(editor) = editor.upgrade(cx) {
editor.update(cx, |editor, cx| {
editor.set_cursor_shape(cursor_shape, cx);
editor.set_clip_at_line_ends(cursor_shape == CursorShape::Block, cx);
editor.set_input_enabled(mode == Mode::Insert);
if keymap_layer_active {
let context_layer = mode.keymap_context_layer();
editor.set_keymap_context_layer::<Self>(context_layer);
} else {
editor.remove_keymap_context_layer::<Self>();
}
});
fn sync_editor_options(&self, cx: &mut MutableAppContext) {
let mode = self.mode;
let cursor_shape = mode.cursor_shape();
for editor in self.editors.values() {
if let Some(editor) = editor.upgrade(cx) {
editor.update(cx, |editor, cx| {
if self.enabled {
editor.set_cursor_shape(cursor_shape, cx);
editor.set_clip_at_line_ends(cursor_shape == CursorShape::Block, cx);
editor.set_input_enabled(mode == Mode::Insert);
let context_layer = mode.keymap_context_layer();
editor.set_keymap_context_layer::<Self>(context_layer);
} else {
editor.set_cursor_shape(CursorShape::Bar, cx);
editor.set_clip_at_line_ends(false, cx);
editor.set_input_enabled(true);
editor.remove_keymap_context_layer::<Self>();
}
}
});
});
});
}
}
}
}
22 changes: 16 additions & 6 deletions crates/vim/src/vim_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crate::*;

#[gpui::test]
async fn test_insert_mode(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestAppContext::new(cx, "").await;
let mut cx = VimTestAppContext::new(cx, true, "").await;
cx.simulate_keystroke("i");
assert_eq!(cx.mode(), Mode::Insert);
cx.simulate_keystrokes(&["T", "e", "s", "t"]);
Expand All @@ -23,7 +23,7 @@ async fn test_insert_mode(cx: &mut gpui::TestAppContext) {

#[gpui::test]
async fn test_normal_hjkl(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestAppContext::new(cx, "Test\nTestTest\nTest").await;
let mut cx = VimTestAppContext::new(cx, true, "Test\nTestTest\nTest").await;
cx.simulate_keystroke("l");
cx.assert_newest_selection_head(indoc! {"
T|est
Expand Down Expand Up @@ -81,15 +81,17 @@ async fn test_normal_hjkl(cx: &mut gpui::TestAppContext) {

#[gpui::test]
async fn test_toggle_through_settings(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestAppContext::new(cx, "").await;
let mut cx = VimTestAppContext::new(cx, true, "").await;

cx.simulate_keystroke("i");
assert_eq!(cx.mode(), Mode::Insert);

// Editor acts as though vim is disabled
cx.disable_vim();
assert_eq!(cx.mode(), Mode::Insert);
cx.simulate_keystrokes(&["h", "j", "k", "l"]);
cx.assert_newest_selection_head("hjkl|");

// Enabling dynamically sets vim mode again
// Enabling dynamically sets vim mode again and restores normal mode
cx.enable_vim();
assert_eq!(cx.mode(), Mode::Normal);
cx.simulate_keystrokes(&["h", "h", "h", "l"]);
Expand All @@ -106,6 +108,13 @@ async fn test_toggle_through_settings(cx: &mut gpui::TestAppContext) {
assert_eq!(cx.mode(), Mode::Normal);
}

#[gpui::test]
async fn test_initially_disabled(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestAppContext::new(cx, false, "").await;
cx.simulate_keystrokes(&["h", "j", "k", "l"]);
cx.assert_newest_selection_head("hjkl|");
}

struct VimTestAppContext<'a> {
cx: &'a mut gpui::TestAppContext,
window_id: usize,
Expand All @@ -115,6 +124,7 @@ struct VimTestAppContext<'a> {
impl<'a> VimTestAppContext<'a> {
async fn new(
cx: &'a mut gpui::TestAppContext,
enabled: bool,
initial_editor_text: &str,
) -> VimTestAppContext<'a> {
cx.update(|cx| {
Expand All @@ -125,7 +135,7 @@ impl<'a> VimTestAppContext<'a> {

cx.update(|cx| {
cx.update_global(|settings: &mut Settings, _| {
settings.vim_mode = true;
settings.vim_mode = enabled;
});
});

Expand Down

0 comments on commit 3ae5fc7

Please sign in to comment.