Define vim_mode setting in vim crate

Max Brunsfeld created

Change summary

crates/editor/src/editor.rs             | 12 +++++++--
crates/settings/src/settings.rs         |  9 +-----
crates/settings/src/settings_store.rs   | 26 +++++++++++++++++++++
crates/vim/src/test/vim_test_context.rs | 12 +++++-----
crates/vim/src/vim.rs                   | 32 +++++++++++++++++++++++---
5 files changed, 70 insertions(+), 21 deletions(-)

Detailed changes

crates/editor/src/editor.rs 🔗

@@ -70,7 +70,7 @@ use scroll::{
 };
 use selections_collection::{resolve_multiple, MutableSelectionsCollection, SelectionsCollection};
 use serde::{Deserialize, Serialize};
-use settings::Settings;
+use settings::{Settings, SettingsStore};
 use smallvec::SmallVec;
 use snippet::Snippet;
 use std::{
@@ -6868,6 +6868,12 @@ impl Editor {
                 .as_singleton()
                 .and_then(|b| b.read(cx).file()),
         ) {
+            let vim_mode = cx
+                .global::<SettingsStore>()
+                .untyped_user_settings()
+                .get("vim_mode")
+                == Some(&serde_json::Value::Bool(true));
+
             let settings = cx.global::<Settings>();
 
             let extension = Path::new(file.file_name(cx))
@@ -6880,12 +6886,12 @@ impl Editor {
                     "save" => "save editor",
                     _ => name,
                 },
-                json!({ "File Extension": extension, "Vim Mode": settings.vim_mode, "In Clickhouse": true  }),
+                json!({ "File Extension": extension, "Vim Mode": vim_mode, "In Clickhouse": true  }),
                 settings.telemetry(),
             );
             let event = ClickhouseEvent::Editor {
                 file_extension: extension.map(ToString::to_string),
-                vim_mode: settings.vim_mode,
+                vim_mode,
                 operation: name,
                 copilot_enabled: settings.features.copilot,
                 copilot_enabled_for_language: settings.show_copilot_suggestions(

crates/settings/src/settings.rs 🔗

@@ -43,7 +43,6 @@ pub struct Settings {
     pub hover_popover_enabled: bool,
     pub show_completions_on_input: bool,
     pub show_call_status_icon: bool,
-    pub vim_mode: bool,
     pub autosave: Autosave,
     pub default_dock_anchor: DockAnchor,
     pub editor_defaults: EditorSettings,
@@ -65,6 +64,8 @@ pub struct Settings {
 }
 
 impl Setting for Settings {
+    const KEY: Option<&'static str> = None;
+
     type FileContent = SettingsFileContent;
 
     fn load(
@@ -93,7 +94,6 @@ impl Setting for Settings {
             hover_popover_enabled: defaults.hover_popover_enabled.unwrap(),
             show_completions_on_input: defaults.show_completions_on_input.unwrap(),
             show_call_status_icon: defaults.show_call_status_icon.unwrap(),
-            vim_mode: defaults.vim_mode.unwrap(),
             autosave: defaults.autosave.unwrap(),
             default_dock_anchor: defaults.default_dock_anchor.unwrap(),
             editor_defaults: EditorSettings {
@@ -550,8 +550,6 @@ pub struct SettingsFileContent {
     #[serde(default)]
     pub show_call_status_icon: Option<bool>,
     #[serde(default)]
-    pub vim_mode: Option<bool>,
-    #[serde(default)]
     pub autosave: Option<Autosave>,
     #[serde(default)]
     pub default_dock_anchor: Option<DockAnchor>,
@@ -647,7 +645,6 @@ impl Settings {
             hover_popover_enabled: defaults.hover_popover_enabled.unwrap(),
             show_completions_on_input: defaults.show_completions_on_input.unwrap(),
             show_call_status_icon: defaults.show_call_status_icon.unwrap(),
-            vim_mode: defaults.vim_mode.unwrap(),
             autosave: defaults.autosave.unwrap(),
             default_dock_anchor: defaults.default_dock_anchor.unwrap(),
             editor_defaults: EditorSettings {
@@ -741,7 +738,6 @@ impl Settings {
             &mut self.show_completions_on_input,
             data.show_completions_on_input,
         );
-        merge(&mut self.vim_mode, data.vim_mode);
         merge(&mut self.autosave, data.autosave);
         merge(&mut self.default_dock_anchor, data.default_dock_anchor);
         merge(&mut self.base_keymap, data.base_keymap);
@@ -940,7 +936,6 @@ impl Settings {
             hover_popover_enabled: true,
             show_completions_on_input: true,
             show_call_status_icon: true,
-            vim_mode: false,
             autosave: Autosave::Off,
             default_dock_anchor: DockAnchor::Bottom,
             editor_defaults: EditorSettings {

crates/settings/src/settings_store.rs 🔗

@@ -23,7 +23,7 @@ pub trait Setting: 'static {
     /// The name of a key within the JSON file from which this setting should
     /// be deserialized. If this is `None`, then the setting will be deserialized
     /// from the root object.
-    const KEY: Option<&'static str> = None;
+    const KEY: Option<&'static str>;
 
     /// The type that is stored in an individual JSON file.
     type FileContent: Clone + Serialize + DeserializeOwned + JsonSchema;
@@ -165,6 +165,28 @@ impl SettingsStore {
             .expect("no default value for setting type")
     }
 
+    /// Get the user's settings as a raw JSON value.
+    ///
+    /// This is only for debugging and reporting. For user-facing functionality,
+    /// use the typed setting interface.
+    pub fn untyped_user_settings(&self) -> &serde_json::Value {
+        self.user_deserialized_settings
+            .as_ref()
+            .map_or(&serde_json::Value::Null, |s| &s.untyped)
+    }
+
+    /// Override the global value for a particular setting.
+    ///
+    /// This is only for tests. Normally, settings are only loaded from
+    /// JSON files.
+    #[cfg(any(test, feature = "test-support"))]
+    pub fn replace_value<T: Setting>(&mut self, value: T) {
+        self.setting_values
+            .get_mut(&TypeId::of::<T>())
+            .expect("unregistered setting type")
+            .set_global_value(Box::new(value))
+    }
+
     /// Update the value of a setting.
     ///
     /// Returns a list of edits to apply to the JSON file.
@@ -1164,6 +1186,8 @@ mod tests {
     }
 
     impl Setting for MultiKeySettings {
+        const KEY: Option<&'static str> = None;
+
         type FileContent = MultiKeySettingsJson;
 
         fn load(

crates/vim/src/test/vim_test_context.rs 🔗

@@ -18,8 +18,8 @@ impl<'a> VimTestContext<'a> {
     pub async fn new(cx: &'a mut gpui::TestAppContext, enabled: bool) -> VimTestContext<'a> {
         let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
         cx.update(|cx| {
-            cx.update_global(|settings: &mut Settings, _| {
-                settings.vim_mode = enabled;
+            cx.update_global(|store: &mut SettingsStore, _| {
+                store.replace_value(VimModeSetting(enabled));
             });
             search::init(cx);
             crate::init(cx);
@@ -52,16 +52,16 @@ impl<'a> VimTestContext<'a> {
 
     pub fn enable_vim(&mut self) {
         self.cx.update(|cx| {
-            cx.update_global(|settings: &mut Settings, _| {
-                settings.vim_mode = true;
+            cx.update_global(|store: &mut SettingsStore, _| {
+                store.replace_value(VimModeSetting(true))
             });
         })
     }
 
     pub fn disable_vim(&mut self) {
         self.cx.update(|cx| {
-            cx.update_global(|settings: &mut Settings, _| {
-                settings.vim_mode = false;
+            cx.update_global(|store: &mut SettingsStore, _| {
+                store.replace_value(VimModeSetting(false))
             });
         })
     }

crates/vim/src/vim.rs 🔗

@@ -22,11 +22,13 @@ use language::CursorShape;
 use motion::Motion;
 use normal::normal_replace;
 use serde::Deserialize;
-use settings::Settings;
+use settings::{Setting, SettingsStore};
 use state::{Mode, Operator, VimState};
 use visual::visual_replace;
 use workspace::{self, Workspace};
 
+struct VimModeSetting(bool);
+
 #[derive(Clone, Deserialize, PartialEq)]
 pub struct SwitchMode(pub Mode);
 
@@ -40,6 +42,8 @@ actions!(vim, [Tab, Enter]);
 impl_actions!(vim, [Number, SwitchMode, PushOperator]);
 
 pub fn init(cx: &mut AppContext) {
+    settings::register_setting::<VimModeSetting>(cx);
+
     editor_events::init(cx);
     normal::init(cx);
     visual::init(cx);
@@ -91,11 +95,11 @@ pub fn init(cx: &mut AppContext) {
         filter.filtered_namespaces.insert("vim");
     });
     cx.update_default_global(|vim: &mut Vim, cx: &mut AppContext| {
-        vim.set_enabled(cx.global::<Settings>().vim_mode, cx)
+        vim.set_enabled(settings::get_setting::<VimModeSetting>(None, cx).0, cx)
     });
-    cx.observe_global::<Settings, _>(|cx| {
+    cx.observe_global::<SettingsStore, _>(|cx| {
         cx.update_default_global(|vim: &mut Vim, cx: &mut AppContext| {
-            vim.set_enabled(cx.global::<Settings>().vim_mode, cx)
+            vim.set_enabled(settings::get_setting::<VimModeSetting>(None, cx).0, cx)
         });
     })
     .detach();
@@ -330,6 +334,26 @@ impl Vim {
     }
 }
 
+impl Setting for VimModeSetting {
+    const KEY: Option<&'static str> = Some("vim_mode");
+
+    type FileContent = Option<bool>;
+
+    fn load(
+        default_value: &Self::FileContent,
+        user_values: &[&Self::FileContent],
+        _: &AppContext,
+    ) -> Self {
+        Self(
+            user_values
+                .first()
+                .map(|e| **e)
+                .flatten()
+                .unwrap_or(default_value.unwrap()),
+        )
+    }
+}
+
 fn local_selections_changed(newest_empty: bool, cx: &mut WindowContext) {
     Vim::update(cx, |vim, cx| {
         if vim.enabled && vim.state.mode == Mode::Normal && !newest_empty {