wip

Ben Kunkle created

Change summary

crates/collab_ui/src/panel_settings.rs             |   1 
crates/file_finder/src/file_finder_settings.rs     |  42 +++
crates/git_ui/src/git_panel_settings.rs            |   2 
crates/go_to_line/src/cursor_position.rs           |   2 
crates/journal/src/journal.rs                      |  42 ++
crates/outline_panel/src/outline_panel_settings.rs | 158 +++++--------
crates/settings/src/settings_content.rs            | 178 +++++++++++++++
crates/vim/src/vim.rs                              | 118 ++-------
8 files changed, 345 insertions(+), 198 deletions(-)

Detailed changes

crates/collab_ui/src/panel_settings.rs 🔗

@@ -19,6 +19,7 @@ pub struct NotificationPanelSettings {
 }
 
 #[derive(Clone, Default, Debug)]
+// todo! are these settings even relevant any more?
 pub struct MessageEditorSettings {
     /// Whether to automatically replace emoji shortcodes with emoji characters.
     /// For example: typing `:wave:` gets replaced with `👋`.

crates/file_finder/src/file_finder_settings.rs 🔗

@@ -1,21 +1,41 @@
 use anyhow::Result;
 use schemars::JsonSchema;
 use serde::{Deserialize, Serialize};
-use settings::{Settings, SettingsKey, SettingsSources, SettingsUi};
+use settings::Settings;
+use util::MergeFrom;
 
 #[derive(Deserialize, Debug, Clone, Copy, PartialEq)]
 pub struct FileFinderSettings {
     pub file_icons: bool,
-    pub modal_max_width: Option<FileFinderWidth>,
+    pub modal_max_width: FileFinderWidth,
     pub skip_focus_for_active_in_search: bool,
     pub include_ignored: Option<bool>,
 }
 
 impl Settings for FileFinderSettings {
-    type FileContent = FileFinderSettingsContent;
+    fn from_defaults(content: &settings::SettingsContent, cx: &mut ui::App) -> Self {
+        let file_finder = content.file_finder.as_ref().unwrap();
 
-    fn load(sources: SettingsSources<Self::FileContent>, _: &mut gpui::App) -> Result<Self> {
-        sources.json_merge()
+        Self {
+            file_icons: file_finder.file_icons.unwrap(),
+            modal_max_width: file_finder.modal_max_width.unwrap().into(),
+            skip_focus_for_active_in_search: file_finder.skip_focus_for_active_in_search.unwrap(),
+            include_ignored: file_finder.include_ignored.unwrap(),
+        }
+    }
+
+    fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut ui::App) {
+        let Some(file_finder) = content.file_finder.as_ref() else {
+            return;
+        };
+
+        self.file_icons.merge_from(&file_finder.file_icons);
+        self.modal_max_width
+            .merge_from(&file_finder.modal_max_width.map(Into::into));
+        self.skip_focus_for_active_in_search
+            .merge_from(&file_finder.skip_focus_for_active_in_search);
+        self.include_ignored
+            .merge_from(&file_finder.include_ignored);
     }
 
     fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {}
@@ -31,3 +51,15 @@ pub enum FileFinderWidth {
     XLarge,
     Full,
 }
+
+impl From<settings::FileFinderWidthContent> for FileFinderWidth {
+    fn from(content: settings::FileFinderWidthContent) -> Self {
+        match content {
+            settings::FileFinderWidthContent::Small => FileFinderWidth::Small,
+            settings::FileFinderWidthContent::Medium => FileFinderWidth::Medium,
+            settings::FileFinderWidthContent::Large => FileFinderWidth::Large,
+            settings::FileFinderWidthContent::XLarge => FileFinderWidth::XLarge,
+            settings::FileFinderWidthContent::Full => FileFinderWidth::Full,
+        }
+    }
+}

crates/git_ui/src/git_panel_settings.rs 🔗

@@ -2,7 +2,7 @@ use editor::EditorSettings;
 use gpui::Pixels;
 use schemars::JsonSchema;
 use serde::{Deserialize, Serialize};
-use settings::{Settings, SettingsContent, SettingsKey, SettingsSources, SettingsUi, StatusStyle};
+use settings::{Settings, SettingsContent, StatusStyle};
 use ui::{
     px,
     scrollbars::{ScrollbarVisibility, ShowScrollbar},

crates/go_to_line/src/cursor_position.rs 🔗

@@ -2,7 +2,7 @@ use editor::{Editor, EditorSettings, MultiBufferSnapshot};
 use gpui::{App, Entity, FocusHandle, Focusable, Subscription, Task, WeakEntity};
 use schemars::JsonSchema;
 use serde::{Deserialize, Serialize};
-use settings::{Settings, SettingsKey, SettingsSources, SettingsUi};
+use settings::Settings;
 use std::{fmt::Write, num::NonZeroU32, time::Duration};
 use text::{Point, Selection};
 use ui::{

crates/journal/src/journal.rs 🔗

@@ -22,17 +22,16 @@ actions!(
 );
 
 /// Settings specific to journaling
-#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey)]
-#[settings_key(key = "journal")]
+#[derive(Clone, Debug)]
 pub struct JournalSettings {
     /// The path of the directory where journal entries are stored.
     ///
     /// Default: `~`
-    pub path: Option<String>,
+    pub path: String,
     /// What format to display the hours in.
     ///
     /// Default: hour12
-    pub hour_format: Option<HourFormat>,
+    pub hour_format: HourFormat,
 }
 
 impl Default for JournalSettings {
@@ -44,19 +43,44 @@ impl Default for JournalSettings {
     }
 }
 
-#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
-#[serde(rename_all = "snake_case")]
+#[derive(Clone, Debug, Default)]
 pub enum HourFormat {
     #[default]
     Hour12,
     Hour24,
 }
 
+impl From<settings::HourFormatContent> for HourFormat {
+    fn from(content: settings::HourFormatContent) -> Self {
+        match content {
+            settings::HourFormatContent::Hour12 => HourFormat::Hour12,
+            settings::HourFormatContent::Hour24 => HourFormat::Hour24,
+        }
+    }
+}
+
 impl settings::Settings for JournalSettings {
-    type FileContent = Self;
+    fn from_defaults(content: &settings::SettingsContent, cx: &mut App) -> Self {
+        let journal = content.journal.as_ref().unwrap();
 
-    fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
-        sources.json_merge()
+        Self {
+            path: journal.path.unwrap(),
+            hour_format: journal.hour_format.unwrap().into(),
+        }
+    }
+
+    fn refine(&mut self, content: &settings::SettingsContent, cx: &mut App) {
+        let Some(journal) = content.journal.as_ref() else {
+            return;
+        };
+
+        if let Some(path) = journal.path {
+            self.path = path;
+        }
+
+        if let Some(hour_format) = journal.hour_format {
+            self.hour_format = hour_format.into();
+        }
     }
 
     fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {}

crates/outline_panel/src/outline_panel_settings.rs 🔗

@@ -1,25 +1,10 @@
 use editor::EditorSettings;
 use gpui::{App, Pixels};
-use schemars::JsonSchema;
-use serde::{Deserialize, Serialize};
-use settings::{Settings, SettingsKey, SettingsSources, SettingsUi};
+pub use settings::{OutlinePanelDockPosition, Settings, ShowIndentGuides};
 use ui::scrollbars::{ScrollbarVisibility, ShowScrollbar};
+use util::MergeFrom;
 
-#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Copy, PartialEq)]
-#[serde(rename_all = "snake_case")]
-pub enum OutlinePanelDockPosition {
-    Left,
-    Right,
-}
-
-#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
-#[serde(rename_all = "snake_case")]
-pub enum ShowIndentGuides {
-    Always,
-    Never,
-}
-
-#[derive(Deserialize, Debug, Clone, Copy, PartialEq)]
+#[derive(Debug, Clone, Copy, PartialEq)]
 pub struct OutlinePanelSettings {
     pub button: bool,
     pub default_width: Pixels,
@@ -35,7 +20,7 @@ pub struct OutlinePanelSettings {
     pub expand_outlines_with_depth: usize,
 }
 
-#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
 pub struct ScrollbarSettings {
     /// When to show the scrollbar in the project panel.
     ///
@@ -43,80 +28,17 @@ pub struct ScrollbarSettings {
     pub show: Option<ShowScrollbar>,
 }
 
-#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
-pub struct ScrollbarSettingsContent {
-    /// When to show the scrollbar in the project panel.
-    ///
-    /// Default: inherits editor scrollbar settings
-    pub show: Option<Option<ShowScrollbar>>,
-}
-
-#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
 pub struct IndentGuidesSettings {
     pub show: ShowIndentGuides,
 }
 
-#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
 pub struct IndentGuidesSettingsContent {
     /// When to show the scrollbar in the outline panel.
     pub show: Option<ShowIndentGuides>,
 }
 
-#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, SettingsUi, SettingsKey)]
-#[settings_key(key = "outline_panel")]
-pub struct OutlinePanelSettingsContent {
-    /// Whether to show the outline panel button in the status bar.
-    ///
-    /// Default: true
-    pub button: Option<bool>,
-    /// Customize default width (in pixels) taken by outline panel
-    ///
-    /// Default: 240
-    pub default_width: Option<f32>,
-    /// The position of outline panel
-    ///
-    /// Default: left
-    pub dock: Option<OutlinePanelDockPosition>,
-    /// Whether to show file icons in the outline panel.
-    ///
-    /// Default: true
-    pub file_icons: Option<bool>,
-    /// Whether to show folder icons or chevrons for directories in the outline panel.
-    ///
-    /// Default: true
-    pub folder_icons: Option<bool>,
-    /// Whether to show the git status in the outline panel.
-    ///
-    /// Default: true
-    pub git_status: Option<bool>,
-    /// Amount of indentation (in pixels) for nested items.
-    ///
-    /// Default: 20
-    pub indent_size: Option<f32>,
-    /// Whether to reveal it in the outline panel automatically,
-    /// when a corresponding project entry becomes active.
-    /// Gitignored entries are never auto revealed.
-    ///
-    /// Default: true
-    pub auto_reveal_entries: Option<bool>,
-    /// Whether to fold directories automatically
-    /// when directory has only one directory inside.
-    ///
-    /// Default: true
-    pub auto_fold_dirs: Option<bool>,
-    /// Settings related to indent guides in the outline panel.
-    pub indent_guides: Option<IndentGuidesSettingsContent>,
-    /// Scrollbar-related settings
-    pub scrollbar: Option<ScrollbarSettingsContent>,
-    /// Default depth to expand outline items in the current file.
-    /// The default depth to which outline entries are expanded on reveal.
-    /// - Set to 0 to collapse all items that have children
-    /// - Set to 1 or higher to collapse items at that depth or deeper
-    ///
-    /// Default: 100
-    pub expand_outlines_with_depth: Option<usize>,
-}
-
 impl ScrollbarVisibility for OutlinePanelSettings {
     fn visibility(&self, cx: &App) -> ShowScrollbar {
         self.scrollbar
@@ -126,21 +48,67 @@ impl ScrollbarVisibility for OutlinePanelSettings {
 }
 
 impl Settings for OutlinePanelSettings {
-    type FileContent = OutlinePanelSettingsContent;
-
-    fn load(
-        sources: SettingsSources<Self::FileContent>,
-        _: &mut gpui::App,
-    ) -> anyhow::Result<Self> {
-        sources.json_merge()
+    fn from_defaults(content: &settings::SettingsContent, cx: &mut App) -> Self {
+        let panel = content.outline_panel.as_ref().unwrap();
+        Self {
+            button: panel.button.unwrap(),
+            default_width: panel.default_width.map(gpui::px).unwrap(),
+            dock: panel.dock.unwrap(),
+            file_icons: panel.file_icons.unwrap(),
+            folder_icons: panel.folder_icons.unwrap(),
+            git_status: panel.git_status.unwrap(),
+            indent_size: panel.indent_size.unwrap(),
+            indent_guides: IndentGuidesSettings {
+                show: panel.indent_guides.unwrap().show.unwrap(),
+            },
+            auto_reveal_entries: panel.auto_reveal_entries.unwrap(),
+            auto_fold_dirs: panel.auto_fold_dirs.unwrap(),
+            scrollbar: ScrollbarSettings {
+                show: panel.scrollbar.unwrap().show.unwrap(),
+            },
+            expand_outlines_with_depth: panel.expand_outlines_with_depth.unwrap(),
+        }
     }
 
-    fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) {
+    fn refine(&mut self, content: &settings::SettingsContent, cx: &mut App) {
+        let Some(panel) = content.outline_panel.as_ref() else {
+            return;
+        };
+
+        self.button.merge_from(&panel.button);
+        self.default_width
+            .merge_from(&panel.default_width.map(Pixels::from));
+        self.dock.merge_from(&panel.dock.map(Into::into));
+        self.file_icons.merge_from(&panel.file_icons);
+        self.folder_icons.merge_from(&panel.folder_icons);
+        self.git_status.merge_from(&panel.git_status);
+        self.indent_size.merge_from(&panel.indent_size);
+
+        if let Some(indent_guides) = panel.indent_guides.as_ref() {
+            self.indent_guides.show.merge_from(&indent_guides.show);
+        }
+
+        self.auto_reveal_entries
+            .merge_from(&panel.auto_reveal_entries);
+        self.auto_fold_dirs.merge_from(&panel.auto_fold_dirs);
+
+        if let Some(scrollbar) = panel.scrollbar.as_ref() {
+            self.scrollbar.show.merge_from(&scrollbar.show);
+        }
+    }
+    fn import_from_vscode(
+        vscode: &settings::VsCodeSettings,
+        current: &mut settings::SettingsContent,
+    ) {
         if let Some(b) = vscode.read_bool("outline.icons") {
-            current.file_icons = Some(b);
-            current.folder_icons = Some(b);
+            let outline_panel = current.outline_panel.get_or_insert_default();
+            outline_panel.file_icons = Some(b);
+            outline_panel.folder_icons = Some(b);
         }
 
-        vscode.bool_setting("git.decorations.enabled", &mut current.git_status);
+        if let Some(b) = vscode.read_bool("git.decorations.enabled") {
+            let outline_panel = current.outline_panel.get_or_insert_default();
+            outline_panel.git_status = Some(b);
+        }
     }
 }

crates/settings/src/settings_content.rs 🔗

@@ -41,6 +41,9 @@ pub struct SettingsContent {
     #[serde(flatten)]
     pub editor: EditorSettingsContent,
 
+    /// Settings related to the file finder.
+    pub file_finder: Option<FileFinderSettingsContent>,
+
     pub git_panel: Option<GitPanelSettingsContent>,
 
     pub tabs: Option<ItemSettingsContent>,
@@ -81,12 +84,16 @@ pub struct SettingsContent {
     /// Default: false
     pub helix_mode: Option<bool>,
 
+    pub journal: Option<JournalSettingsContent>,
+
     /// A map of log scopes to the desired log level.
     /// Useful for filtering out noisy logs or enabling more verbose logging.
     ///
     /// Example: {"log": {"client": "warn"}}
     pub log: Option<HashMap<String, String>>,
 
+    pub outline_panel: Option<OutlinePanelSettingsContent>,
+
     /// Configuration for the Message Editor
     pub message_editor: Option<MessageEditorSettings>,
 
@@ -123,6 +130,9 @@ pub struct SettingsContent {
     ///
     /// Default: false
     pub disable_ai: Option<bool>,
+
+    /// Settings related to Vim mode in Zed.
+    pub vim: Option<VimSettingsContent>,
 }
 
 impl SettingsContent {
@@ -472,7 +482,7 @@ pub struct MessageEditorSettings {
     pub auto_replace_emoji_shortcode: Option<bool>,
 }
 
-#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
+#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, PartialEq)]
 pub struct FileFinderSettingsContent {
     /// Whether to show file icons in the file finder.
     ///
@@ -481,7 +491,7 @@ pub struct FileFinderSettingsContent {
     /// Determines how much space the file finder can take up in relation to the available window width.
     ///
     /// Default: small
-    pub modal_max_width: Option<FileFinderWidth>,
+    pub modal_max_width: Option<FileFinderWidthContent>,
     /// Determines whether the file finder should skip focus for the active file in search results.
     ///
     /// Default: true
@@ -499,5 +509,169 @@ pub struct FileFinderSettingsContent {
     /// * `None`: Be smart and search for ignored when called from a gitignored worktree
     ///
     /// Default: None
+    /// todo!() -> Change this type to an enum
     pub include_ignored: Option<Option<bool>>,
 }
+
+#[derive(Debug, PartialEq, Eq, Clone, Copy, Default, Serialize, Deserialize, JsonSchema)]
+#[serde(rename_all = "lowercase")]
+pub enum FileFinderWidthContent {
+    #[default]
+    Small,
+    Medium,
+    Large,
+    XLarge,
+    Full,
+}
+
+#[derive(Clone, Default, Serialize, Deserialize, PartialEq, Debug, JsonSchema)]
+pub struct VimSettingsContent {
+    pub default_mode: Option<ModeContent>,
+    pub toggle_relative_line_numbers: Option<bool>,
+    pub use_system_clipboard: Option<UseSystemClipboard>,
+    pub use_smartcase_find: Option<bool>,
+    pub custom_digraphs: Option<HashMap<String, Arc<str>>>,
+    pub highlight_on_yank_duration: Option<u64>,
+    pub cursor_shape: Option<CursorShapeSettings>,
+}
+
+#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, PartialEq, Debug)]
+#[serde(rename_all = "snake_case")]
+pub enum ModeContent {
+    #[default]
+    Normal,
+    Insert,
+    Replace,
+    Visual,
+    VisualLine,
+    VisualBlock,
+    HelixNormal,
+}
+
+/// Controls when to use system clipboard.
+#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
+#[serde(rename_all = "snake_case")]
+pub enum UseSystemClipboard {
+    /// Don't use system clipboard.
+    Never,
+    /// Use system clipboard.
+    Always,
+    /// Use system clipboard for yank operations.
+    OnYank,
+}
+
+/// The settings for cursor shape.
+#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
+pub struct CursorShapeSettings {
+    /// Cursor shape for the normal mode.
+    ///
+    /// Default: block
+    pub normal: Option<CursorShape>,
+    /// Cursor shape for the replace mode.
+    ///
+    /// Default: underline
+    pub replace: Option<CursorShape>,
+    /// Cursor shape for the visual mode.
+    ///
+    /// Default: block
+    pub visual: Option<CursorShape>,
+    /// Cursor shape for the insert mode.
+    ///
+    /// The default value follows the primary cursor_shape.
+    pub insert: Option<CursorShape>,
+}
+
+/// Settings specific to journaling
+#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
+pub struct JournalSettingsContent {
+    /// The path of the directory where journal entries are stored.
+    ///
+    /// Default: `~`
+    pub path: Option<String>,
+    /// What format to display the hours in.
+    ///
+    /// Default: hour12
+    pub hour_format: Option<HourFormatContent>,
+}
+
+#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq)]
+#[serde(rename_all = "snake_case")]
+pub enum HourFormatContent {
+    #[default]
+    Hour12,
+    Hour24,
+}
+
+#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, PartialEq)]
+pub struct OutlinePanelSettingsContent {
+    /// Whether to show the outline panel button in the status bar.
+    ///
+    /// Default: true
+    pub button: Option<bool>,
+    /// Customize default width (in pixels) taken by outline panel
+    ///
+    /// Default: 240
+    pub default_width: Option<f32>,
+    /// The position of outline panel
+    ///
+    /// Default: left
+    pub dock: Option<OutlinePanelDockPosition>,
+    /// Whether to show file icons in the outline panel.
+    ///
+    /// Default: true
+    pub file_icons: Option<bool>,
+    /// Whether to show folder icons or chevrons for directories in the outline panel.
+    ///
+    /// Default: true
+    pub folder_icons: Option<bool>,
+    /// Whether to show the git status in the outline panel.
+    ///
+    /// Default: true
+    pub git_status: Option<bool>,
+    /// Amount of indentation (in pixels) for nested items.
+    ///
+    /// Default: 20
+    pub indent_size: Option<f32>,
+    /// Whether to reveal it in the outline panel automatically,
+    /// when a corresponding project entry becomes active.
+    /// Gitignored entries are never auto revealed.
+    ///
+    /// Default: true
+    pub auto_reveal_entries: Option<bool>,
+    /// Whether to fold directories automatically
+    /// when directory has only one directory inside.
+    ///
+    /// Default: true
+    pub auto_fold_dirs: Option<bool>,
+    /// Settings related to indent guides in the outline panel.
+    pub indent_guides: Option<IndentGuidesSettingsContent>,
+    /// Scrollbar-related settings
+    pub scrollbar: Option<ScrollbarSettingsContent>,
+    /// Default depth to expand outline items in the current file.
+    /// The default depth to which outline entries are expanded on reveal.
+    /// - Set to 0 to collapse all items that have children
+    /// - Set to 1 or higher to collapse items at that depth or deeper
+    ///
+    /// Default: 100
+    pub expand_outlines_with_depth: Option<usize>,
+}
+
+#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Copy, PartialEq)]
+#[serde(rename_all = "snake_case")]
+pub enum OutlinePanelDockPosition {
+    Left,
+    Right,
+}
+
+#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize, Serialize, JsonSchema)]
+#[serde(rename_all = "snake_case")]
+pub enum ShowIndentGuides {
+    Always,
+    Never,
+}
+
+#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
+pub struct IndentGuidesSettingsContent {
+    /// When to show the scrollbar in the outline panel.
+    pub show: Option<ShowIndentGuides>,
+}

crates/vim/src/vim.rs 🔗

@@ -1793,39 +1793,6 @@ impl Vim {
     }
 }
 
-/// Controls when to use system clipboard.
-#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
-#[serde(rename_all = "snake_case")]
-pub enum UseSystemClipboard {
-    /// Don't use system clipboard.
-    Never,
-    /// Use system clipboard.
-    Always,
-    /// Use system clipboard for yank operations.
-    OnYank,
-}
-
-/// The settings for cursor shape.
-#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
-struct CursorShapeSettings {
-    /// Cursor shape for the normal mode.
-    ///
-    /// Default: block
-    pub normal: Option<CursorShape>,
-    /// Cursor shape for the replace mode.
-    ///
-    /// Default: underline
-    pub replace: Option<CursorShape>,
-    /// Cursor shape for the visual mode.
-    ///
-    /// Default: block
-    pub visual: Option<CursorShape>,
-    /// Cursor shape for the insert mode.
-    ///
-    /// The default value follows the primary cursor_shape.
-    pub insert: Option<CursorShape>,
-}
-
 #[derive(Deserialize)]
 struct VimSettings {
     pub default_mode: Mode,
@@ -1837,32 +1804,7 @@ struct VimSettings {
     pub cursor_shape: CursorShapeSettings,
 }
 
-#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey)]
-#[settings_key(key = "vim")]
-struct VimSettingsContent {
-    pub default_mode: Option<ModeContent>,
-    pub toggle_relative_line_numbers: Option<bool>,
-    pub use_system_clipboard: Option<UseSystemClipboard>,
-    pub use_smartcase_find: Option<bool>,
-    pub custom_digraphs: Option<HashMap<String, Arc<str>>>,
-    pub highlight_on_yank_duration: Option<u64>,
-    pub cursor_shape: Option<CursorShapeSettings>,
-}
-
-#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
-#[serde(rename_all = "snake_case")]
-pub enum ModeContent {
-    #[default]
-    Normal,
-    Insert,
-    Replace,
-    Visual,
-    VisualLine,
-    VisualBlock,
-    HelixNormal,
-}
-
-impl From<ModeContent> for Mode {
+impl From<settings::ModeContent> for Mode {
     fn from(mode: ModeContent) -> Self {
         match mode {
             ModeContent::Normal => Self::Normal,
@@ -1877,34 +1819,40 @@ impl From<ModeContent> for Mode {
 }
 
 impl Settings for VimSettings {
-    type FileContent = VimSettingsContent;
-
-    fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
-        let settings: VimSettingsContent = sources.json_merge()?;
-
-        Ok(Self {
-            default_mode: settings
-                .default_mode
-                .ok_or_else(Self::missing_default)?
-                .into(),
-            toggle_relative_line_numbers: settings
-                .toggle_relative_line_numbers
-                .ok_or_else(Self::missing_default)?,
-            use_system_clipboard: settings
-                .use_system_clipboard
-                .ok_or_else(Self::missing_default)?,
-            use_smartcase_find: settings
-                .use_smartcase_find
-                .ok_or_else(Self::missing_default)?,
-            custom_digraphs: settings.custom_digraphs.ok_or_else(Self::missing_default)?,
-            highlight_on_yank_duration: settings
-                .highlight_on_yank_duration
-                .ok_or_else(Self::missing_default)?,
-            cursor_shape: settings.cursor_shape.ok_or_else(Self::missing_default)?,
-        })
+    fn from_defaults(content: &settings::SettingsContent, cx: &mut App) -> Self {
+        let vim = content.vim.as_ref().unwrap();
+        Self {
+            default_mode: vim.default_mode.unwrap().into(),
+            toggle_relative_line_numbers: vim.toggle_relative_line_numbers.unwrap(),
+            use_system_clipboard: vim.use_system_clipboard.unwrap(),
+            use_smartcase_find: vim.use_smartcase_find.unwrap(),
+            custom_digraphs: vim.custom_digraphs.unwrap(),
+            highlight_on_yank_duration: vim.highlight_on_yank_duration.unwrap(),
+            cursor_shape: vim.cursor_shape.unwrap(),
+        }
     }
 
-    fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {
+    fn refine(&mut self, content: &settings::SettingsContent, cx: &mut App) {
+        let Some(vim) = content.vim.as_ref() else {
+            return;
+        };
+        self.default_mode
+            .merge_from(&vim.default_mode.map(Into::into));
+        self.toggle_relative_line_numbers
+            .merge_from(&vim.toggle_relative_line_numbers);
+        self.use_system_clipboard
+            .merge_from(&vim.use_system_clipboard);
+        self.use_smartcase_find.merge_from(&vim.use_smartcase_find);
+        self.custom_digraphs.merge_from(&vim.custom_digraphs);
+        self.highlight_on_yank_duration
+            .merge_from(&vim.highlight_on_yank_duration);
+        self.cursor_shape.merge_from(&vim.cursor_shape);
+    }
+
+    fn import_from_vscode(
+        _vscode: &settings::VsCodeSettings,
+        _current: &mut settings::SettingsContent,
+    ) {
         // TODO: translate vim extension settings
     }
 }