From ccf6f27b8f1bdfb803b9cc0da0b0cf5c9e136dd9 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Tue, 10 Sep 2024 22:59:10 -0400 Subject: [PATCH] settings: Remove auxiliary Content types where possible (#16744) Release Notes: - N/A --- crates/auto_update/src/auto_update.rs | 23 +- crates/call/src/call_settings.rs | 22 +- crates/client/src/client.rs | 50 ++- crates/collab/src/tests/editor_tests.rs | 8 +- crates/collab/src/tests/following_tests.rs | 2 +- crates/collab_ui/src/chat_panel.rs | 2 +- .../src/chat_panel/message_editor.rs | 8 +- crates/collab_ui/src/collab_panel.rs | 2 +- crates/collab_ui/src/notification_panel.rs | 2 +- crates/collab_ui/src/panel_settings.rs | 102 +++--- .../src/project_diagnostics_settings.rs | 20 +- crates/editor/src/editor.rs | 4 +- crates/editor/src/editor_settings.rs | 318 +++++++----------- crates/editor/src/editor_settings_controls.rs | 22 +- crates/editor/src/editor_tests.rs | 18 +- crates/editor/src/element.rs | 17 +- crates/extension/src/extension_settings.rs | 13 +- crates/extensions_ui/src/extensions_ui.rs | 2 +- crates/go_to_line/src/cursor_position.rs | 16 +- crates/gpui/src/geometry.rs | 2 + crates/language/src/language_settings.rs | 8 +- crates/languages/src/json.rs | 29 +- crates/outline_panel/src/outline_panel.rs | 16 +- .../src/outline_panel_settings.rs | 72 ++-- crates/performance/src/performance.rs | 184 ++++++++++ crates/project/src/project_settings.rs | 41 +-- crates/project_panel/src/project_panel.rs | 28 +- .../src/project_panel_settings.rs | 92 ++--- crates/recent_projects/src/dev_servers.rs | 3 +- crates/recent_projects/src/ssh_connections.rs | 25 +- crates/repl/src/jupyter_settings.rs | 28 +- crates/tasks_ui/src/settings.rs | 18 +- crates/vim/src/digraph.rs | 2 +- crates/vim/src/normal.rs | 6 +- crates/vim/src/normal/paste.rs | 12 +- crates/vim/src/normal/scroll.rs | 2 +- crates/vim/src/normal/search.rs | 4 +- crates/vim/src/test.rs | 2 +- crates/vim/src/test/vim_test_context.rs | 6 +- crates/vim/src/vim.rs | 32 +- crates/welcome/src/base_keymap_picker.rs | 2 +- crates/welcome/src/base_keymap_setting.rs | 6 +- crates/welcome/src/welcome.rs | 2 +- crates/workspace/src/item.rs | 70 ++-- crates/workspace/src/workspace.rs | 8 +- crates/workspace/src/workspace_settings.rs | 132 ++++---- crates/worktree/src/worktree_settings.rs | 43 ++- crates/worktree/src/worktree_tests.rs | 11 +- crates/zed/src/zed.rs | 2 +- 49 files changed, 843 insertions(+), 696 deletions(-) create mode 100644 crates/performance/src/performance.rs diff --git a/crates/auto_update/src/auto_update.rs b/crates/auto_update/src/auto_update.rs index 8063ff4c40..499df7fc29 100644 --- a/crates/auto_update/src/auto_update.rs +++ b/crates/auto_update/src/auto_update.rs @@ -116,27 +116,30 @@ impl Drop for MacOsUnmounter { } } +/// Whether or not to automatically check for updates. +#[derive(Clone, Copy, JsonSchema, Deserialize, Serialize)] +#[serde(default)] +#[serde(transparent)] struct AutoUpdateSetting(bool); -/// Whether or not to automatically check for updates. -/// -/// Default: true -#[derive(Clone, Copy, Default, JsonSchema, Deserialize, Serialize)] -#[serde(transparent)] -struct AutoUpdateSettingContent(bool); +impl Default for AutoUpdateSetting { + fn default() -> Self { + Self(true) + } +} impl Settings for AutoUpdateSetting { const KEY: Option<&'static str> = Some("auto_update"); - type FileContent = Option; + type FileContent = Self; fn load(sources: SettingsSources, _: &mut AppContext) -> Result { let auto_update = [sources.release_channel, sources.user] .into_iter() - .find_map(|value| value.copied().flatten()) - .unwrap_or(sources.default.ok_or_else(Self::missing_default)?); + .find_map(|value| value.copied()) + .unwrap_or(*sources.default); - Ok(Self(auto_update.0)) + Ok(auto_update) } } diff --git a/crates/call/src/call_settings.rs b/crates/call/src/call_settings.rs index 446178ffb9..e10b711734 100644 --- a/crates/call/src/call_settings.rs +++ b/crates/call/src/call_settings.rs @@ -4,30 +4,20 @@ use schemars::JsonSchema; use serde_derive::{Deserialize, Serialize}; use settings::{Settings, SettingsSources}; -#[derive(Deserialize, Debug)] -pub struct CallSettings { - pub mute_on_join: bool, - pub share_on_join: bool, -} - /// Configuration of voice calls in Zed. -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)] -pub struct CallSettingsContent { +#[derive(Clone, Debug, Default, Deserialize, Serialize, JsonSchema)] +#[serde(default)] +pub struct CallSettings { /// Whether the microphone should be muted when joining a channel or a call. - /// - /// Default: false - pub mute_on_join: Option, - + pub mute_on_join: bool, /// Whether your current project should be shared when joining an empty channel. - /// - /// Default: true - pub share_on_join: Option, + pub share_on_join: bool, } impl Settings for CallSettings { const KEY: Option<&'static str> = Some("calls"); - type FileContent = CallSettingsContent; + type FileContent = Self; fn load(sources: SettingsSources, _: &mut AppContext) -> Result { sources.json_merge() diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index 8787e2ed96..83eef45be8 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -99,20 +99,26 @@ pub const CONNECTION_TIMEOUT: Duration = Duration::from_secs(20); actions!(client, [SignIn, SignOut, Reconnect]); -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)] -pub struct ClientSettingsContent { - server_url: Option, +#[derive(Clone, Serialize, Deserialize, JsonSchema)] +#[serde(default)] +pub struct ClientSettings { + /// The server to connect to. If the environment variable + /// ZED_SERVER_URL is set, it will override this setting. + pub server_url: String, } -#[derive(Deserialize)] -pub struct ClientSettings { - pub server_url: String, +impl Default for ClientSettings { + fn default() -> Self { + Self { + server_url: "https://zed.dev".to_owned(), + } + } } impl Settings for ClientSettings { const KEY: Option<&'static str> = None; - type FileContent = ClientSettingsContent; + type FileContent = Self; fn load(sources: SettingsSources, _: &mut AppContext) -> Result { let mut result = sources.json_merge::()?; @@ -124,19 +130,37 @@ impl Settings for ClientSettings { } #[derive(Default, Clone, Serialize, Deserialize, JsonSchema)] -pub struct ProxySettingsContent { - proxy: Option, +#[serde(default)] +pub struct ProxySettings { + /// Set a proxy to use. The proxy protocol is specified by the URI scheme. + /// + /// Supported URI scheme: `http`, `https`, `socks4`, `socks4a`, `socks5`, + /// `socks5h`. `http` will be used when no scheme is specified. + /// + /// By default no proxy will be used, or Zed will try get proxy settings from + /// environment variables. + /// + /// Examples: + /// - "proxy": "socks5://localhost:10808" + /// - "proxy": "http://127.0.0.1:10809" + #[schemars(example = "Self::example_1")] + #[schemars(example = "Self::example_2")] + pub proxy: Option, } -#[derive(Deserialize, Default)] -pub struct ProxySettings { - pub proxy: Option, +impl ProxySettings { + fn example_1() -> String { + "http://127.0.0.1:10809".to_owned() + } + fn example_2() -> String { + "socks5://localhost:10808".to_owned() + } } impl Settings for ProxySettings { const KEY: Option<&'static str> = None; - type FileContent = ProxySettingsContent; + type FileContent = Self; fn load(sources: SettingsSources, _: &mut AppContext) -> Result { Ok(Self { diff --git a/crates/collab/src/tests/editor_tests.rs b/crates/collab/src/tests/editor_tests.rs index 3f205b7f93..a214291752 100644 --- a/crates/collab/src/tests/editor_tests.rs +++ b/crates/collab/src/tests/editor_tests.rs @@ -2261,11 +2261,11 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA cx_a.update(editor::init); cx_b.update(editor::init); // Turn inline-blame-off by default so no state is transferred without us explicitly doing so - let inline_blame_off_settings = Some(InlineBlameSettings { + let inline_blame_off_settings = InlineBlameSettings { enabled: false, - delay_ms: None, - min_column: None, - }); + delay_ms: 0, + min_column: 0, + }; cx_a.update(|cx| { SettingsStore::update_global(cx, |store, cx| { store.update_user_settings::(cx, |settings| { diff --git a/crates/collab/src/tests/following_tests.rs b/crates/collab/src/tests/following_tests.rs index e66b66a1b4..1bc3cd6917 100644 --- a/crates/collab/src/tests/following_tests.rs +++ b/crates/collab/src/tests/following_tests.rs @@ -1649,7 +1649,7 @@ async fn test_following_into_excluded_file( cx.update(|cx| { cx.update_global::(|store, cx| { store.update_user_settings::(cx, |settings| { - settings.file_scan_exclusions = Some(vec!["**/.git".to_string()]); + settings.file_scan_exclusions = vec!["**/.git".to_string()]; }); }); }); diff --git a/crates/collab_ui/src/chat_panel.rs b/crates/collab_ui/src/chat_panel.rs index 5a79f364ff..f6e6c7321f 100644 --- a/crates/collab_ui/src/chat_panel.rs +++ b/crates/collab_ui/src/chat_panel.rs @@ -1108,7 +1108,7 @@ impl Panel for ChatPanel { settings::update_settings_file::( self.fs.clone(), cx, - move |settings, _| settings.dock = Some(position), + move |settings, _| settings.dock = position, ); } diff --git a/crates/collab_ui/src/chat_panel/message_editor.rs b/crates/collab_ui/src/chat_panel/message_editor.rs index 028e148cba..0b1a2dbe69 100644 --- a/crates/collab_ui/src/chat_panel/message_editor.rs +++ b/crates/collab_ui/src/chat_panel/message_editor.rs @@ -113,9 +113,7 @@ impl MessageEditor { editor.set_show_indent_guides(false, cx); editor.set_completion_provider(Box::new(MessageEditorCompletionProvider(this))); editor.set_auto_replace_emoji_shortcode( - MessageEditorSettings::get_global(cx) - .auto_replace_emoji_shortcode - .unwrap_or_default(), + MessageEditorSettings::get_global(cx).auto_replace_emoji_shortcode, ); }); @@ -130,9 +128,7 @@ impl MessageEditor { cx.observe_global::(|view, cx| { view.editor.update(cx, |editor, cx| { editor.set_auto_replace_emoji_shortcode( - MessageEditorSettings::get_global(cx) - .auto_replace_emoji_shortcode - .unwrap_or_default(), + MessageEditorSettings::get_global(cx).auto_replace_emoji_shortcode, ) }) }) diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index 7270110181..3e6483c42d 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -2813,7 +2813,7 @@ impl Panel for CollabPanel { settings::update_settings_file::( self.fs.clone(), cx, - move |settings, _| settings.dock = Some(position), + move |settings, _| settings.dock = position, ); } diff --git a/crates/collab_ui/src/notification_panel.rs b/crates/collab_ui/src/notification_panel.rs index 33ca5a2952..326e1f0f5b 100644 --- a/crates/collab_ui/src/notification_panel.rs +++ b/crates/collab_ui/src/notification_panel.rs @@ -672,7 +672,7 @@ impl Panel for NotificationPanel { settings::update_settings_file::( self.fs.clone(), cx, - move |settings, _| settings.dock = Some(position), + move |settings, _| settings.dock = position, ); } diff --git a/crates/collab_ui/src/panel_settings.rs b/crates/collab_ui/src/panel_settings.rs index f9851d5797..a594f023bb 100644 --- a/crates/collab_ui/src/panel_settings.rs +++ b/crates/collab_ui/src/panel_settings.rs @@ -2,58 +2,84 @@ use gpui::Pixels; use schemars::JsonSchema; use serde_derive::{Deserialize, Serialize}; use settings::{Settings, SettingsSources}; +use ui::px; use workspace::dock::DockPosition; -#[derive(Deserialize, Debug)] +#[derive(Clone, Deserialize, Debug, JsonSchema, Serialize)] +#[serde(default)] pub struct CollaborationPanelSettings { - pub button: bool, - pub dock: DockPosition, - pub default_width: Pixels, -} - -#[derive(Deserialize, Debug)] -pub struct ChatPanelSettings { - pub button: bool, - pub dock: DockPosition, - pub default_width: Pixels, -} - -#[derive(Deserialize, Debug)] -pub struct NotificationPanelSettings { - pub button: bool, - pub dock: DockPosition, - pub default_width: Pixels, -} - -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)] -pub struct PanelSettingsContent { /// Whether to show the panel button in the status bar. - /// - /// Default: true - pub button: Option, + pub button: bool, /// Where to dock the panel. - /// - /// Default: left - pub dock: Option, + pub dock: DockPosition, /// Default width of the panel in pixels. - /// - /// Default: 240 - pub default_width: Option, + pub default_width: Pixels, +} + +impl Default for CollaborationPanelSettings { + fn default() -> Self { + Self { + button: true, + dock: DockPosition::Left, + default_width: px(240.), + } + } +} + +#[derive(Clone, Deserialize, Debug, JsonSchema, Serialize)] +#[serde(default)] +pub struct ChatPanelSettings { + /// Whether to show the panel button in the status bar. + pub button: bool, + /// Where to dock the panel. + pub dock: DockPosition, + /// Default width of the panel in pixels. + pub default_width: Pixels, +} + +impl Default for ChatPanelSettings { + fn default() -> Self { + Self { + button: true, + dock: DockPosition::Right, + default_width: px(240.), + } + } +} + +#[derive(Clone, Deserialize, Debug, JsonSchema, Serialize)] +#[serde(default)] +pub struct NotificationPanelSettings { + /// Whether to show the panel button in the status bar. + pub button: bool, + /// Where to dock the panel. + pub dock: DockPosition, + /// Default width of the panel in pixels. + pub default_width: Pixels, +} + +impl Default for NotificationPanelSettings { + fn default() -> Self { + Self { + button: true, + dock: DockPosition::Right, + default_width: px(380.), + } + } } #[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)] +#[serde(default)] pub struct MessageEditorSettings { /// Whether to automatically replace emoji shortcodes with emoji characters. /// For example: typing `:wave:` gets replaced with `👋`. - /// - /// Default: false - pub auto_replace_emoji_shortcode: Option, + pub auto_replace_emoji_shortcode: bool, } impl Settings for CollaborationPanelSettings { const KEY: Option<&'static str> = Some("collaboration_panel"); - type FileContent = PanelSettingsContent; + type FileContent = Self; fn load( sources: SettingsSources, @@ -66,7 +92,7 @@ impl Settings for CollaborationPanelSettings { impl Settings for ChatPanelSettings { const KEY: Option<&'static str> = Some("chat_panel"); - type FileContent = PanelSettingsContent; + type FileContent = Self; fn load( sources: SettingsSources, @@ -79,7 +105,7 @@ impl Settings for ChatPanelSettings { impl Settings for NotificationPanelSettings { const KEY: Option<&'static str> = Some("notification_panel"); - type FileContent = PanelSettingsContent; + type FileContent = Self; fn load( sources: SettingsSources, @@ -92,7 +118,7 @@ impl Settings for NotificationPanelSettings { impl Settings for MessageEditorSettings { const KEY: Option<&'static str> = Some("message_editor"); - type FileContent = MessageEditorSettings; + type FileContent = Self; fn load( sources: SettingsSources, diff --git a/crates/diagnostics/src/project_diagnostics_settings.rs b/crates/diagnostics/src/project_diagnostics_settings.rs index 55879d0c42..34739bcd17 100644 --- a/crates/diagnostics/src/project_diagnostics_settings.rs +++ b/crates/diagnostics/src/project_diagnostics_settings.rs @@ -4,23 +4,25 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsSources}; -#[derive(Deserialize, Debug)] +#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug)] +#[serde(default)] +/// Diagnostics configuration. pub struct ProjectDiagnosticsSettings { + /// Whether to show warnings or not by default. pub include_warnings: bool, } -/// Diagnostics configuration. -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)] -pub struct ProjectDiagnosticsSettingsContent { - /// Whether to show warnings or not by default. - /// - /// Default: true - include_warnings: Option, +impl Default for ProjectDiagnosticsSettings { + fn default() -> Self { + Self { + include_warnings: true, + } + } } impl Settings for ProjectDiagnosticsSettings { const KEY: Option<&'static str> = Some("diagnostics"); - type FileContent = ProjectDiagnosticsSettingsContent; + type FileContent = Self; fn load(sources: SettingsSources, _: &mut AppContext) -> Result { sources.json_merge() diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 3466888c94..f750abd95c 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -10639,7 +10639,7 @@ impl Editor { let fs = workspace.read(cx).app_state().fs.clone(); let current_show = TabBarSettings::get_global(cx).show; update_settings_file::(fs, cx, move |setting, _| { - setting.show = Some(!current_show); + setting.show = !current_show; }); } @@ -12562,7 +12562,7 @@ impl EditorSnapshot { let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| { matches!( ProjectSettings::get_global(cx).git.git_gutter, - Some(GitGutterSetting::TrackedFiles) + GitGutterSetting::TrackedFiles ) }); let gutter_settings = EditorSettings::get_global(cx).gutter; diff --git a/crates/editor/src/editor_settings.rs b/crates/editor/src/editor_settings.rs index 2614e4ea30..0532fd7bdf 100644 --- a/crates/editor/src/editor_settings.rs +++ b/crates/editor/src/editor_settings.rs @@ -3,38 +3,105 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsSources}; -#[derive(Deserialize, Clone)] +#[derive(Clone, Serialize, Deserialize, JsonSchema)] +#[serde(default)] pub struct EditorSettings { + /// Whether the cursor blinks in the editor. pub cursor_blink: bool, + /// How to highlight the current line in the editor. pub current_line_highlight: CurrentLineHighlight, + /// Whether to show the informational hover box when moving the mouse + /// over symbols in the editor. pub hover_popover_enabled: bool, + /// Whether to pop the completions menu while typing in an editor without + /// explicitly requesting it. pub show_completions_on_input: bool, + /// Whether to display inline and alongside documentation for items in the + /// completions menu. pub show_completion_documentation: bool, + /// The debounce delay before re-querying the language server for completion + /// documentation when not included in original completion list. pub completion_documentation_secondary_query_debounce: u64, + /// Whether to use additional LSP queries to format (and amend) the code after + /// every "trigger" symbol input, defined by LSP server capabilities. pub use_on_type_format: bool, + /// Toolbar related settings pub toolbar: Toolbar, + /// Scrollbar related settings pub scrollbar: Scrollbar, + /// Gutter related settings pub gutter: Gutter, + /// Whether the editor will scroll beyond the last line. pub scroll_beyond_last_line: ScrollBeyondLastLine, + /// The number of lines to keep above/below the cursor when auto-scrolling. pub vertical_scroll_margin: f32, + /// Scroll sensitivity multiplier. This multiplier is applied + /// to both the horizontal and vertical delta values while scrolling. pub scroll_sensitivity: f32, + /// Whether the line numbers on editors gutter are relative or not. pub relative_line_numbers: bool, + /// When to populate a new search's query based on the text under the cursor. pub seed_search_query_from_cursor: SeedQuerySetting, pub use_smartcase_search: bool, + /// The key to use for adding multiple cursors pub multi_cursor_modifier: MultiCursorModifier, + /// Hide the values of variables in `private` files, as defined by the + /// private_files setting. This only changes the visual representation, + /// the values are still present in the file and can be selected / copied / pasted pub redact_private_values: bool, + + /// How many lines to expand the multibuffer excerpts by default pub expand_excerpt_lines: u32, pub middle_click_paste: bool, + /// What to do when multibuffer is double clicked in some of its excerpts + /// (parts of singleton buffers). #[serde(default)] pub double_click_in_multibuffer: DoubleClickInMultibuffer, + /// Whether the editor search results will loop pub search_wrap: bool, #[serde(default)] pub search: SearchSettings, + /// Show method signatures in the editor, when inside parentheses. pub auto_signature_help: bool, + /// Whether to show the signature help after completion or a bracket pair inserted. + /// If `auto_signature_help` is enabled, this setting will be treated as enabled also. pub show_signature_help_after_edits: bool, + /// Jupyter REPL settings. pub jupyter: Jupyter, } +impl Default for EditorSettings { + fn default() -> Self { + Self { + cursor_blink: true, + current_line_highlight: CurrentLineHighlight::All, + hover_popover_enabled: true, + show_completions_on_input: true, + show_completion_documentation: true, + completion_documentation_secondary_query_debounce: 300, + use_on_type_format: true, + toolbar: Default::default(), + scrollbar: Default::default(), + gutter: Default::default(), + scroll_beyond_last_line: ScrollBeyondLastLine::OnePage, + vertical_scroll_margin: 3., + scroll_sensitivity: 1.0, + relative_line_numbers: false, + seed_search_query_from_cursor: SeedQuerySetting::Always, + multi_cursor_modifier: MultiCursorModifier::Alt, + redact_private_values: false, + expand_excerpt_lines: 3, + double_click_in_multibuffer: DoubleClickInMultibuffer::Select, + search_wrap: true, + auto_signature_help: false, + show_signature_help_after_edits: true, + jupyter: Default::default(), + use_smartcase_search: false, + middle_click_paste: true, + search: SearchSettings::default(), + } + } +} #[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum CurrentLineHighlight { @@ -72,48 +139,93 @@ pub enum DoubleClickInMultibuffer { Open, } -#[derive(Debug, Clone, Deserialize)] +#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] pub struct Jupyter { /// Whether the Jupyter feature is enabled. - /// - /// Default: true pub enabled: bool, } -#[derive(Default, Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub struct JupyterContent { - /// Whether the Jupyter feature is enabled. - /// - /// Default: true - pub enabled: Option, +impl Default for Jupyter { + fn default() -> Self { + Self { enabled: true } + } } #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[serde(default)] pub struct Toolbar { + /// Whether to display breadcrumbs in the editor toolbar. pub breadcrumbs: bool, + /// Whether to display quick action buttons in the editor toolbar. pub quick_actions: bool, + /// Whether to show the selections menu in the editor toolbar pub selections_menu: bool, } +impl Default for Toolbar { + fn default() -> Self { + Self { + breadcrumbs: true, + quick_actions: true, + selections_menu: true, + } + } +} + #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] pub struct Scrollbar { + /// When to show the scrollbar in the editor. pub show: ShowScrollbar, + /// Whether to show git diff indicators in the scrollbar. pub git_diff: bool, + /// Whether to show buffer search result indicators in the scrollbar. pub selected_symbol: bool, + /// Whether to show selected symbol occurrences in the scrollbar. pub search_results: bool, + /// Whether to show diagnostic indicators in the scrollbar. pub diagnostics: bool, + /// Whether to show cursor positions in the scrollbar. pub cursors: bool, } +impl Default for Scrollbar { + fn default() -> Self { + Self { + show: ShowScrollbar::Auto, + git_diff: true, + selected_symbol: true, + search_results: true, + diagnostics: true, + cursors: true, + } + } +} + +/// Gutter-related settings. #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[serde(default)] pub struct Gutter { + /// Whether to show line numbers in the gutter. pub line_numbers: bool, + /// Whether to show code action buttons in the gutter. pub code_actions: bool, + /// Whether to show runnable buttons in the gutter. pub runnables: bool, + /// Whether to show fold buttons in the gutter. pub folds: bool, } +impl Default for Gutter { + fn default() -> Self { + Self { + line_numbers: true, + code_actions: true, + runnables: true, + folds: true, + } + } +} + /// When to show the scrollbar in the editor. /// /// Default: auto @@ -171,188 +283,6 @@ pub struct SearchSettings { pub regex: bool, } -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)] -pub struct EditorSettingsContent { - /// Whether the cursor blinks in the editor. - /// - /// Default: true - pub cursor_blink: Option, - /// How to highlight the current line in the editor. - /// - /// Default: all - pub current_line_highlight: Option, - /// Whether to show the informational hover box when moving the mouse - /// over symbols in the editor. - /// - /// Default: true - pub hover_popover_enabled: Option, - - /// Whether to pop the completions menu while typing in an editor without - /// explicitly requesting it. - /// - /// Default: true - pub show_completions_on_input: Option, - /// Whether to display inline and alongside documentation for items in the - /// completions menu. - /// - /// Default: true - pub show_completion_documentation: Option, - /// The debounce delay before re-querying the language server for completion - /// documentation when not included in original completion list. - /// - /// Default: 300 ms - pub completion_documentation_secondary_query_debounce: Option, - /// Whether to use additional LSP queries to format (and amend) the code after - /// every "trigger" symbol input, defined by LSP server capabilities. - /// - /// Default: true - pub use_on_type_format: Option, - /// Toolbar related settings - pub toolbar: Option, - /// Scrollbar related settings - pub scrollbar: Option, - /// Gutter related settings - pub gutter: Option, - /// Whether the editor will scroll beyond the last line. - /// - /// Default: one_page - pub scroll_beyond_last_line: Option, - /// The number of lines to keep above/below the cursor when auto-scrolling. - /// - /// Default: 3. - pub vertical_scroll_margin: Option, - /// Scroll sensitivity multiplier. This multiplier is applied - /// to both the horizontal and vertical delta values while scrolling. - /// - /// Default: 1.0 - pub scroll_sensitivity: Option, - /// Whether the line numbers on editors gutter are relative or not. - /// - /// Default: false - pub relative_line_numbers: Option, - /// When to populate a new search's query based on the text under the cursor. - /// - /// Default: always - pub seed_search_query_from_cursor: Option, - pub use_smartcase_search: Option, - /// The key to use for adding multiple cursors - /// - /// Default: alt - pub multi_cursor_modifier: Option, - /// Hide the values of variables in `private` files, as defined by the - /// private_files setting. This only changes the visual representation, - /// the values are still present in the file and can be selected / copied / pasted - /// - /// Default: false - pub redact_private_values: Option, - - /// How many lines to expand the multibuffer excerpts by default - /// - /// Default: 3 - pub expand_excerpt_lines: Option, - - /// Whether to enable middle-click paste on Linux - /// - /// Default: true - pub middle_click_paste: Option, - - /// What to do when multibuffer is double clicked in some of its excerpts - /// (parts of singleton buffers). - /// - /// Default: select - pub double_click_in_multibuffer: Option, - /// Whether the editor search results will loop - /// - /// Default: true - pub search_wrap: Option, - - /// Defaults to use when opening a new buffer and project search items. - /// - /// Default: nothing is enabled - pub search: Option, - - /// Whether to automatically show a signature help pop-up or not. - /// - /// Default: false - pub auto_signature_help: Option, - - /// Whether to show the signature help pop-up after completions or bracket pairs inserted. - /// - /// Default: true - pub show_signature_help_after_edits: Option, - - /// Jupyter REPL settings. - pub jupyter: Option, -} - -// Toolbar related settings -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] -pub struct ToolbarContent { - /// Whether to display breadcrumbs in the editor toolbar. - /// - /// Default: true - pub breadcrumbs: Option, - /// Whether to display quick action buttons in the editor toolbar. - /// - /// Default: true - pub quick_actions: Option, - - /// Whether to show the selections menu in the editor toolbar - /// - /// Default: true - pub selections_menu: Option, -} - -/// Scrollbar related settings -#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)] -pub struct ScrollbarContent { - /// When to show the scrollbar in the editor. - /// - /// Default: auto - pub show: Option, - /// Whether to show git diff indicators in the scrollbar. - /// - /// Default: true - pub git_diff: Option, - /// Whether to show buffer search result indicators in the scrollbar. - /// - /// Default: true - pub search_results: Option, - /// Whether to show selected symbol occurrences in the scrollbar. - /// - /// Default: true - pub selected_symbol: Option, - /// Whether to show diagnostic indicators in the scrollbar. - /// - /// Default: true - pub diagnostics: Option, - /// Whether to show cursor positions in the scrollbar. - /// - /// Default: true - pub cursors: Option, -} - -/// Gutter related settings -#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] -pub struct GutterContent { - /// Whether to show line numbers in the gutter. - /// - /// Default: true - pub line_numbers: Option, - /// Whether to show code action buttons in the gutter. - /// - /// Default: true - pub code_actions: Option, - /// Whether to show runnable buttons in the gutter. - /// - /// Default: true - pub runnables: Option, - /// Whether to show fold buttons in the gutter. - /// - /// Default: true - pub folds: Option, -} - impl EditorSettings { pub fn jupyter_enabled(cx: &AppContext) -> bool { EditorSettings::get_global(cx).jupyter.enabled @@ -362,7 +292,7 @@ impl EditorSettings { impl Settings for EditorSettings { const KEY: Option<&'static str> = None; - type FileContent = EditorSettingsContent; + type FileContent = Self; fn load( sources: SettingsSources, diff --git a/crates/editor/src/editor_settings_controls.rs b/crates/editor/src/editor_settings_controls.rs index bbe1b00324..36d471dfa2 100644 --- a/crates/editor/src/editor_settings_controls.rs +++ b/crates/editor/src/editor_settings_controls.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use gpui::{AppContext, FontFeatures, FontWeight}; -use project::project_settings::{InlineBlameSettings, ProjectSettings}; +use project::project_settings::ProjectSettings; use settings::{EditableSettingControl, Settings}; use theme::{FontFamilyCache, ThemeSettings}; use ui::{ @@ -296,14 +296,7 @@ impl EditableSettingControl for InlineGitBlameControl { value: Self::Value, _cx: &AppContext, ) { - if let Some(inline_blame) = settings.git.inline_blame.as_mut() { - inline_blame.enabled = value; - } else { - settings.git.inline_blame = Some(InlineBlameSettings { - enabled: false, - ..Default::default() - }); - } + settings.git.inline_blame.enabled = value; } } @@ -349,14 +342,7 @@ impl EditableSettingControl for LineNumbersControl { value: Self::Value, _cx: &AppContext, ) { - if let Some(gutter) = settings.gutter.as_mut() { - gutter.line_numbers = Some(value); - } else { - settings.gutter = Some(crate::editor_settings::GutterContent { - line_numbers: Some(value), - ..Default::default() - }); - } + settings.gutter.line_numbers = value; } } @@ -402,7 +388,7 @@ impl EditableSettingControl for RelativeLineNumbersControl { value: Self::Value, _cx: &AppContext, ) { - settings.relative_line_numbers = Some(value); + settings.relative_line_numbers = value; } } diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 0b1e0385de..7d42dc7a85 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -6964,7 +6964,7 @@ async fn test_handle_input_for_show_signature_help_auto_signature_help_true( cx.update(|cx| { cx.update_global::(|settings, cx| { settings.update_user_settings::(cx, |settings| { - settings.auto_signature_help = Some(true); + settings.auto_signature_help = true; }); }); }); @@ -7105,8 +7105,8 @@ async fn test_handle_input_with_different_show_signature_settings(cx: &mut gpui: cx.update(|cx| { cx.update_global::(|settings, cx| { settings.update_user_settings::(cx, |settings| { - settings.auto_signature_help = Some(false); - settings.show_signature_help_after_edits = Some(false); + settings.auto_signature_help = false; + settings.show_signature_help_after_edits = false; }); }); }); @@ -7232,8 +7232,8 @@ async fn test_handle_input_with_different_show_signature_settings(cx: &mut gpui: cx.update(|cx| { cx.update_global::(|settings, cx| { settings.update_user_settings::(cx, |settings| { - settings.auto_signature_help = Some(false); - settings.show_signature_help_after_edits = Some(true); + settings.auto_signature_help = false; + settings.show_signature_help_after_edits = true; }); }); }); @@ -7274,8 +7274,8 @@ async fn test_handle_input_with_different_show_signature_settings(cx: &mut gpui: cx.update(|cx| { cx.update_global::(|settings, cx| { settings.update_user_settings::(cx, |settings| { - settings.auto_signature_help = Some(true); - settings.show_signature_help_after_edits = Some(false); + settings.auto_signature_help = true; + settings.show_signature_help_after_edits = false; }); }); }); @@ -7318,7 +7318,7 @@ async fn test_signature_help(cx: &mut gpui::TestAppContext) { cx.update(|cx| { cx.update_global::(|settings, cx| { settings.update_user_settings::(cx, |settings| { - settings.auto_signature_help = Some(true); + settings.auto_signature_help = true; }); }); }); @@ -7759,7 +7759,7 @@ async fn test_completion(cx: &mut gpui::TestAppContext) { cx.update(|cx| { cx.update_global::(|settings, cx| { settings.update_user_settings::(cx, |settings| { - settings.show_completions_on_input = Some(false); + settings.show_completions_on_input = false; }); }) }); diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index d4f5c565c2..1c0a325b76 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1283,10 +1283,7 @@ impl EditorElement { .row, ); - let git_gutter_setting = ProjectSettings::get_global(cx) - .git - .git_gutter - .unwrap_or_default(); + let git_gutter_setting = ProjectSettings::get_global(cx).git.git_gutter; let display_hunks = buffer_snapshot .git_diff_hunks_in_range(buffer_start_row..buffer_end_row) .map(|hunk| diff_hunk_to_display(&hunk, snapshot)) @@ -1366,12 +1363,10 @@ impl EditorElement { }; let padded_line_end = line_end + em_width * INLINE_BLAME_PADDING_EM_WIDTHS; - let min_column_in_pixels = ProjectSettings::get_global(cx) - .git - .inline_blame - .and_then(|settings| settings.min_column) - .map(|col| self.column_pixels(col as usize, cx)) - .unwrap_or(px(0.)); + let min_column_in_pixels = self.column_pixels( + ProjectSettings::get_global(cx).git.inline_blame.min_column as usize, + cx, + ); let min_start = content_origin.x - scroll_pixel_position.x + min_column_in_pixels; cmp::max(padded_line_end, min_start) @@ -3331,7 +3326,7 @@ impl EditorElement { .unwrap_or_else(|| { matches!( ProjectSettings::get_global(cx).git.git_gutter, - Some(GitGutterSetting::TrackedFiles) + GitGutterSetting::TrackedFiles ) }); if show_git_gutter { diff --git a/crates/extension/src/extension_settings.rs b/crates/extension/src/extension_settings.rs index a2ab7ac9cc..715dc3ca82 100644 --- a/crates/extension/src/extension_settings.rs +++ b/crates/extension/src/extension_settings.rs @@ -6,18 +6,25 @@ use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsSources}; use std::sync::Arc; -#[derive(Deserialize, Serialize, Debug, Default, Clone, JsonSchema)] +#[derive(Deserialize, Serialize, Debug, Clone, JsonSchema)] +#[serde(default)] pub struct ExtensionSettings { /// The extensions that should be automatically installed by Zed. /// /// This is used to make functionality provided by extensions (e.g., language support) /// available out-of-the-box. - #[serde(default)] pub auto_install_extensions: HashMap, bool>, - #[serde(default)] pub auto_update_extensions: HashMap, bool>, } +impl Default for ExtensionSettings { + fn default() -> Self { + Self { + auto_install_extensions: HashMap::from_iter([("html".into(), true)]), + auto_update_extensions: Default::default(), + } + } +} impl ExtensionSettings { /// Returns whether the given extension should be auto-installed. pub fn should_auto_install(&self, extension_id: &str) -> bool { diff --git a/crates/extensions_ui/src/extensions_ui.rs b/crates/extensions_ui/src/extensions_ui.rs index f246e3cf4f..b2d6d7f283 100644 --- a/crates/extensions_ui/src/extensions_ui.rs +++ b/crates/extensions_ui/src/extensions_ui.rs @@ -1000,7 +1000,7 @@ impl ExtensionsPage { this.update_settings::( selection, cx, - |setting, value| *setting = Some(value), + |setting, value| *setting = VimModeSetting(value), ); }), )), diff --git a/crates/go_to_line/src/cursor_position.rs b/crates/go_to_line/src/cursor_position.rs index 63e0f2b079..de3d1dc74d 100644 --- a/crates/go_to_line/src/cursor_position.rs +++ b/crates/go_to_line/src/cursor_position.rs @@ -180,18 +180,10 @@ pub(crate) enum LineIndicatorFormat { Long, } -/// Whether or not to automatically check for updates. -/// -/// Values: short, long -/// Default: short -#[derive(Clone, Copy, Default, JsonSchema, Deserialize, Serialize)] -#[serde(transparent)] -pub(crate) struct LineIndicatorFormatContent(LineIndicatorFormat); - impl Settings for LineIndicatorFormat { const KEY: Option<&'static str> = Some("line_indicator_format"); - type FileContent = Option; + type FileContent = Self; fn load( sources: SettingsSources, @@ -199,9 +191,9 @@ impl Settings for LineIndicatorFormat { ) -> anyhow::Result { let format = [sources.release_channel, sources.user] .into_iter() - .find_map(|value| value.copied().flatten()) - .unwrap_or(sources.default.ok_or_else(Self::missing_default)?); + .find_map(|value| value.copied()) + .unwrap_or(*sources.default); - Ok(format.0) + Ok(format) } } diff --git a/crates/gpui/src/geometry.rs b/crates/gpui/src/geometry.rs index 8de9e6f009..b203592360 100644 --- a/crates/gpui/src/geometry.rs +++ b/crates/gpui/src/geometry.rs @@ -5,6 +5,7 @@ use core::fmt::Debug; use derive_more::{Add, AddAssign, Div, DivAssign, Mul, Neg, Sub, SubAssign}; use refineable::Refineable; +use schemars::JsonSchema; use serde_derive::{Deserialize, Serialize}; use std::{ cmp::{self, PartialOrd}, @@ -2201,6 +2202,7 @@ impl From for Radians { PartialEq, Serialize, Deserialize, + JsonSchema, )] #[repr(transparent)] pub struct Pixels(pub f32); diff --git a/crates/language/src/language_settings.rs b/crates/language/src/language_settings.rs index e1fcaaba28..7a6b758a25 100644 --- a/crates/language/src/language_settings.rs +++ b/crates/language/src/language_settings.rs @@ -70,10 +70,10 @@ pub struct LanguageSettings { /// The column at which to soft-wrap lines, for buffers where soft-wrap /// is enabled. pub preferred_line_length: u32, - // Whether to show wrap guides (vertical rulers) in the editor. - // Setting this to true will show a guide at the 'preferred_line_length' value - // if softwrap is set to 'preferred_line_length', and will show any - // additional guides as specified by the 'wrap_guides' setting. + /// Whether to show wrap guides (vertical rulers) in the editor. + /// Setting this to true will show a guide at the 'preferred_line_length' value + /// if softwrap is set to 'preferred_line_length', and will show any + /// additional guides as specified by the 'wrap_guides' setting. pub show_wrap_guides: bool, /// Character counts at which to show wrap guides (vertical rulers) in the editor. pub wrap_guides: Vec, diff --git a/crates/languages/src/json.rs b/crates/languages/src/json.rs index 6b5f74c263..102eb1ef2f 100644 --- a/crates/languages/src/json.rs +++ b/crates/languages/src/json.rs @@ -7,10 +7,13 @@ use feature_flags::FeatureFlagAppExt; use futures::StreamExt; use gpui::{AppContext, AsyncAppContext}; use http_client::github::{latest_github_release, GitHubLspBinaryVersion}; -use language::{LanguageRegistry, LanguageServerName, LspAdapter, LspAdapterDelegate}; +use language::{ + CodeLabel, Language, LanguageRegistry, LanguageServerName, LspAdapter, LspAdapterDelegate, +}; use lsp::LanguageServerBinary; use node_runtime::NodeRuntime; use project::ContextProviderWithTasks; +use rope::Rope; use serde_json::{json, Value}; use settings::{KeymapFile, SettingsJsonSchemaParams, SettingsStore}; use smol::{ @@ -202,6 +205,30 @@ impl LspAdapter for JsonLspAdapter { }))) } + async fn label_for_completion( + &self, + item: &lsp::CompletionItem, + language: &Arc, + ) -> Option { + let text = if let Some(description) = item + .label_details + .as_ref() + .and_then(|label_details| label_details.description.as_ref()) + { + format!("{} {}", item.label, description) + } else if let Some(detail) = &item.detail { + format!("{} {}", item.label, detail) + } else { + item.label.clone() + }; + let rope = Rope::from(item.label.as_str()); + let runs = language.highlight_text(&rope, 0..item.label.len()); + Some(language::CodeLabel { + text, + runs, + filter_range: 0..item.label.len(), + }) + } async fn workspace_configuration( self: Arc, _: &Arc, diff --git a/crates/outline_panel/src/outline_panel.rs b/crates/outline_panel/src/outline_panel.rs index c5f0187c22..361607533b 100644 --- a/crates/outline_panel/src/outline_panel.rs +++ b/crates/outline_panel/src/outline_panel.rs @@ -24,12 +24,12 @@ use editor::{ use file_icons::FileIcons; use fuzzy::{match_strings, StringMatch, StringMatchCandidate}; use gpui::{ - actions, anchored, deferred, div, impl_actions, px, uniform_list, Action, AnyElement, - AppContext, AssetSource, AsyncWindowContext, ClipboardItem, DismissEvent, Div, ElementId, - EventEmitter, FocusHandle, FocusableView, HighlightStyle, InteractiveElement, IntoElement, - KeyContext, Model, MouseButton, MouseDownEvent, ParentElement, Pixels, Point, Render, - SharedString, Stateful, Styled, Subscription, Task, UniformListScrollHandle, View, ViewContext, - VisualContext, WeakView, WindowContext, + actions, anchored, deferred, div, impl_actions, uniform_list, Action, AnyElement, AppContext, + AssetSource, AsyncWindowContext, ClipboardItem, DismissEvent, Div, ElementId, EventEmitter, + FocusHandle, FocusableView, HighlightStyle, InteractiveElement, IntoElement, KeyContext, Model, + MouseButton, MouseDownEvent, ParentElement, Pixels, Point, Render, SharedString, Stateful, + Styled, Subscription, Task, UniformListScrollHandle, View, ViewContext, VisualContext, + WeakView, WindowContext, }; use itertools::Itertools; use language::{BufferId, BufferSnapshot, OffsetRangeExt, OutlineItem}; @@ -1938,7 +1938,7 @@ impl OutlinePanel { .child( ListItem::new(item_id) .indent_level(depth) - .indent_step_size(px(settings.indent_size)) + .indent_step_size(settings.indent_size) .selected(is_active) .when_some(icon_element, |list_item, icon_element| { list_item.child(h_flex().child(icon_element)) @@ -3801,7 +3801,7 @@ impl Panel for OutlinePanel { DockPosition::Left | DockPosition::Bottom => OutlinePanelDockPosition::Left, DockPosition::Right => OutlinePanelDockPosition::Right, }; - settings.dock = Some(dock); + settings.dock = dock; }, ); } diff --git a/crates/outline_panel/src/outline_panel_settings.rs b/crates/outline_panel/src/outline_panel_settings.rs index e19fc3c008..a8e51b96c5 100644 --- a/crates/outline_panel/src/outline_panel_settings.rs +++ b/crates/outline_panel/src/outline_panel_settings.rs @@ -1,4 +1,5 @@ -use gpui::Pixels; +use anyhow; +use gpui::{px, Pixels}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsSources}; @@ -10,66 +11,51 @@ pub enum OutlinePanelDockPosition { Right, } -#[derive(Deserialize, Debug, Clone, Copy, PartialEq)] +#[derive(Deserialize, Serialize, Debug, Clone, Copy, PartialEq, JsonSchema)] pub struct OutlinePanelSettings { - pub button: bool, - pub default_width: Pixels, - pub dock: OutlinePanelDockPosition, - pub file_icons: bool, - pub folder_icons: bool, - pub git_status: bool, - pub indent_size: f32, - pub auto_reveal_entries: bool, - pub auto_fold_dirs: bool, -} - -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)] -pub struct OutlinePanelSettingsContent { /// Whether to show the outline panel button in the status bar. - /// - /// Default: true - pub button: Option, + pub button: bool, /// Customize default width (in pixels) taken by outline panel - /// - /// Default: 240 - pub default_width: Option, + pub default_width: Pixels, /// The position of outline panel - /// - /// Default: left - pub dock: Option, + pub dock: OutlinePanelDockPosition, /// Whether to show file icons in the outline panel. - /// - /// Default: true - pub file_icons: Option, + pub file_icons: bool, /// Whether to show folder icons or chevrons for directories in the outline panel. - /// - /// Default: true - pub folder_icons: Option, + pub folder_icons: bool, /// Whether to show the git status in the outline panel. - /// - /// Default: true - pub git_status: Option, + pub git_status: bool, /// Amount of indentation (in pixels) for nested items. - /// - /// Default: 20 - pub indent_size: Option, + pub indent_size: Pixels, /// 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, + pub auto_reveal_entries: bool, /// Whether to fold directories automatically /// when directory has only one directory inside. - /// - /// Default: true - pub auto_fold_dirs: Option, + pub auto_fold_dirs: bool, +} + +impl Default for OutlinePanelSettings { + fn default() -> Self { + Self { + button: true, + default_width: px(240.), + dock: OutlinePanelDockPosition::Left, + file_icons: true, + folder_icons: true, + auto_fold_dirs: true, + auto_reveal_entries: true, + indent_size: px(20.), + git_status: true, + } + } } impl Settings for OutlinePanelSettings { const KEY: Option<&'static str> = Some("outline_panel"); - type FileContent = OutlinePanelSettingsContent; + type FileContent = Self; fn load( sources: SettingsSources, diff --git a/crates/performance/src/performance.rs b/crates/performance/src/performance.rs new file mode 100644 index 0000000000..db2388c59a --- /dev/null +++ b/crates/performance/src/performance.rs @@ -0,0 +1,184 @@ +use std::time::Instant; + +use anyhow::Result; +use gpui::{ + div, AppContext, InteractiveElement as _, Render, StatefulInteractiveElement as _, + Subscription, ViewContext, VisualContext, +}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use settings::{Settings, SettingsSources, SettingsStore}; +use workspace::{ + ui::{Label, LabelCommon, LabelSize, Tooltip}, + ItemHandle, StatusItemView, Workspace, +}; + +const SHOW_STARTUP_TIME_DURATION: std::time::Duration = std::time::Duration::from_secs(5); + +pub fn init(cx: &mut AppContext) { + PerformanceSettings::register(cx); + + let mut enabled = PerformanceSettings::get_global(cx).show_in_status_bar; + let start_time = Instant::now(); + let mut _observe_workspaces = toggle_status_bar_items(enabled, start_time, cx); + + cx.observe_global::(move |cx| { + let new_value = PerformanceSettings::get_global(cx).show_in_status_bar; + if new_value != enabled { + enabled = new_value; + _observe_workspaces = toggle_status_bar_items(enabled, start_time, cx); + } + }) + .detach(); +} + +fn toggle_status_bar_items( + enabled: bool, + start_time: Instant, + cx: &mut AppContext, +) -> Option { + for window in cx.windows() { + if let Some(workspace) = window.downcast::() { + workspace + .update(cx, |workspace, cx| { + toggle_status_bar_item(workspace, enabled, start_time, cx); + }) + .ok(); + } + } + + if enabled { + log::info!("performance metrics display enabled"); + Some(cx.observe_new_views::(move |workspace, cx| { + toggle_status_bar_item(workspace, true, start_time, cx); + })) + } else { + log::info!("performance metrics display disabled"); + None + } +} + +struct PerformanceStatusBarItem { + display_mode: DisplayMode, +} + +#[derive(Copy, Clone, Debug)] +enum DisplayMode { + StartupTime, + Fps, +} + +impl PerformanceStatusBarItem { + fn new(start_time: Instant, cx: &mut ViewContext) -> Self { + let now = Instant::now(); + let display_mode = if now < start_time + SHOW_STARTUP_TIME_DURATION { + DisplayMode::StartupTime + } else { + DisplayMode::Fps + }; + + let this = Self { display_mode }; + + if let DisplayMode::StartupTime = display_mode { + cx.spawn(|this, mut cx| async move { + let now = Instant::now(); + let remaining_duration = + (start_time + SHOW_STARTUP_TIME_DURATION).saturating_duration_since(now); + cx.background_executor().timer(remaining_duration).await; + this.update(&mut cx, |this, cx| { + this.display_mode = DisplayMode::Fps; + cx.notify(); + }) + .ok(); + }) + .detach(); + } + + this + } +} + +impl Render for PerformanceStatusBarItem { + fn render(&mut self, cx: &mut gpui::ViewContext) -> impl gpui::IntoElement { + let text = match self.display_mode { + DisplayMode::StartupTime => cx + .time_to_first_window_draw() + .map_or("Pending".to_string(), |duration| { + format!("{}ms", duration.as_millis()) + }), + DisplayMode::Fps => cx.fps().map_or("".to_string(), |fps| { + format!("{:3} FPS", fps.round() as u32) + }), + }; + + use gpui::ParentElement; + let display_mode = self.display_mode; + div() + .id("performance status") + .child(Label::new(text).size(LabelSize::Small)) + .tooltip(move |cx| match display_mode { + DisplayMode::StartupTime => Tooltip::text("Time to first window draw", cx), + DisplayMode::Fps => cx + .new_view(|cx| { + let tooltip = Tooltip::new("Current FPS"); + if let Some(time_to_first) = cx.time_to_first_window_draw() { + tooltip.meta(format!( + "Time to first window draw: {}ms", + time_to_first.as_millis() + )) + } else { + tooltip + } + }) + .into(), + }) + } +} + +impl StatusItemView for PerformanceStatusBarItem { + fn set_active_pane_item( + &mut self, + _active_pane_item: Option<&dyn ItemHandle>, + _cx: &mut gpui::ViewContext, + ) { + // This is not currently used. + } +} + +fn toggle_status_bar_item( + workspace: &mut Workspace, + enabled: bool, + start_time: Instant, + cx: &mut ViewContext, +) { + if enabled { + workspace.status_bar().update(cx, |bar, cx| { + bar.add_right_item( + cx.new_view(|cx| PerformanceStatusBarItem::new(start_time, cx)), + cx, + ) + }); + } else { + workspace.status_bar().update(cx, |bar, cx| { + bar.remove_items_of_type::(cx); + }); + } +} + +/// Configuration of the display of performance details. +#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)] +#[serde(default)] +pub struct PerformanceSettings { + /// Display the time to first window draw and frame rate in the status bar. + pub show_in_status_bar: bool, +} + +impl Settings for PerformanceSettings { + const KEY: Option<&'static str> = Some("performance"); + + type FileContent = Self; + + fn load(sources: SettingsSources, _: &mut AppContext) -> Result { + sources.json_merge() + } +} diff --git a/crates/project/src/project_settings.rs b/crates/project/src/project_settings.rs index 70b2eccf23..3c21b1c5e8 100644 --- a/crates/project/src/project_settings.rs +++ b/crates/project/src/project_settings.rs @@ -20,6 +20,7 @@ use worktree::{PathChange, UpdatedEntriesSet, Worktree, WorktreeId}; use crate::worktree_store::{WorktreeStore, WorktreeStoreEvent}; #[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)] +#[serde(default)] pub struct ProjectSettings { /// Configuration for language servers. /// @@ -41,7 +42,6 @@ pub struct ProjectSettings { pub load_direnv: DirenvSettings, /// Configuration for session-related features - #[serde(default)] pub session: SessionSettings, } @@ -59,36 +59,31 @@ pub enum DirenvSettings { } #[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] +#[serde(default)] pub struct GitSettings { /// Whether or not to show the git gutter. /// /// Default: tracked_files - pub git_gutter: Option, + pub git_gutter: GitGutterSetting, pub gutter_debounce: Option, /// Whether or not to show git blame data inline in /// the currently focused line. /// /// Default: on - pub inline_blame: Option, + pub inline_blame: InlineBlameSettings, } impl GitSettings { pub fn inline_blame_enabled(&self) -> bool { #[allow(unknown_lints, clippy::manual_unwrap_or_default)] - match self.inline_blame { - Some(InlineBlameSettings { enabled, .. }) => enabled, - _ => false, - } + self.inline_blame.enabled } pub fn inline_blame_delay(&self) -> Option { - match self.inline_blame { - Some(InlineBlameSettings { - delay_ms: Some(delay_ms), - .. - }) if delay_ms > 0 => Some(Duration::from_millis(delay_ms)), - _ => None, - } + self.inline_blame + .delay_ms + .gt(&0) + .then(|| Duration::from_millis(self.inline_blame.delay_ms)) } } @@ -102,28 +97,34 @@ pub enum GitGutterSetting { Hide, } -#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema)] #[serde(rename_all = "snake_case")] +#[serde(default)] pub struct InlineBlameSettings { /// Whether or not to show git blame data inline in /// the currently focused line. /// /// Default: true - #[serde(default = "true_value")] pub enabled: bool, /// Whether to only show the inline blame information /// after a delay once the cursor stops moving. /// /// Default: 0 - pub delay_ms: Option, + pub delay_ms: u64, /// The minimum column number to show the inline blame information at /// /// Default: 0 - pub min_column: Option, + pub min_column: u32, } -const fn true_value() -> bool { - true +impl Default for InlineBlameSettings { + fn default() -> Self { + Self { + enabled: true, + delay_ms: 0, + min_column: 0, + } + } } #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index c77a2170dd..6ca843875b 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -2289,7 +2289,7 @@ impl ProjectPanel { .child( ListItem::new(entry_id.to_proto() as usize) .indent_level(depth) - .indent_step_size(px(settings.indent_size)) + .indent_step_size(settings.indent_size) .selected(is_marked || is_active) .when_some(canonical_path, |this, path| { this.end_slot::( @@ -2817,7 +2817,7 @@ impl Render for DraggedProjectEntryView { this.bg(cx.theme().colors().background).w(self.width).child( ListItem::new(self.selection.entry_id.to_proto() as usize) .indent_level(self.details.depth) - .indent_step_size(px(settings.indent_size)) + .indent_step_size(settings.indent_size) .child(if let Some(icon) = &self.details.icon { div().child(Icon::from_path(icon.clone())) } else { @@ -2855,7 +2855,7 @@ impl Panel for ProjectPanel { DockPosition::Left | DockPosition::Bottom => ProjectPanelDockPosition::Left, DockPosition::Right => ProjectPanelDockPosition::Right, }; - settings.dock = Some(dock); + settings.dock = dock; }, ); } @@ -3029,7 +3029,7 @@ mod tests { cx.update_global::(|store, cx| { store.update_user_settings::(cx, |worktree_settings| { worktree_settings.file_scan_exclusions = - Some(vec!["**/.git".to_string(), "**/4/**".to_string()]); + vec!["**/.git".to_string(), "**/4/**".to_string()]; }); }); }); @@ -4818,10 +4818,10 @@ mod tests { cx.update(|cx| { cx.update_global::(|store, cx| { store.update_user_settings::(cx, |worktree_settings| { - worktree_settings.file_scan_exclusions = Some(Vec::new()); + worktree_settings.file_scan_exclusions = Vec::new(); }); store.update_user_settings::(cx, |project_panel_settings| { - project_panel_settings.auto_reveal_entries = Some(false) + project_panel_settings.auto_reveal_entries = false }); }) }); @@ -4940,7 +4940,7 @@ mod tests { cx.update(|cx| { cx.update_global::(|store, cx| { store.update_user_settings::(cx, |project_panel_settings| { - project_panel_settings.auto_reveal_entries = Some(true) + project_panel_settings.auto_reveal_entries = true }); }) }); @@ -5054,10 +5054,10 @@ mod tests { cx.update(|cx| { cx.update_global::(|store, cx| { store.update_user_settings::(cx, |worktree_settings| { - worktree_settings.file_scan_exclusions = Some(Vec::new()); + worktree_settings.file_scan_exclusions = Vec::new(); }); store.update_user_settings::(cx, |project_panel_settings| { - project_panel_settings.auto_reveal_entries = Some(false) + project_panel_settings.auto_reveal_entries = false }); }) }); @@ -5256,7 +5256,7 @@ mod tests { cx.update_global::(|store, cx| { store.update_user_settings::(cx, |project_settings| { project_settings.file_scan_exclusions = - Some(vec!["excluded_dir".to_string(), "**/.git".to_string()]); + vec!["excluded_dir".to_string(), "**/.git".to_string()]; }); }); }); @@ -5569,10 +5569,10 @@ mod tests { cx.update_global::(|store, cx| { store.update_user_settings::(cx, |project_panel_settings| { - project_panel_settings.auto_fold_dirs = Some(false); + project_panel_settings.auto_fold_dirs = false; }); store.update_user_settings::(cx, |worktree_settings| { - worktree_settings.file_scan_exclusions = Some(Vec::new()); + worktree_settings.file_scan_exclusions = Vec::new(); }); }); }); @@ -5591,10 +5591,10 @@ mod tests { cx.update_global::(|store, cx| { store.update_user_settings::(cx, |project_panel_settings| { - project_panel_settings.auto_fold_dirs = Some(false); + project_panel_settings.auto_fold_dirs = false; }); store.update_user_settings::(cx, |worktree_settings| { - worktree_settings.file_scan_exclusions = Some(Vec::new()); + worktree_settings.file_scan_exclusions = Vec::new(); }); }); }); diff --git a/crates/project_panel/src/project_panel_settings.rs b/crates/project_panel/src/project_panel_settings.rs index 4d73ae9245..6910b4627a 100644 --- a/crates/project_panel/src/project_panel_settings.rs +++ b/crates/project_panel/src/project_panel_settings.rs @@ -2,6 +2,7 @@ use gpui::Pixels; use schemars::JsonSchema; use serde_derive::{Deserialize, Serialize}; use settings::{Settings, SettingsSources}; +use ui::px; #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Copy, PartialEq)] #[serde(rename_all = "snake_case")] @@ -10,20 +11,50 @@ pub enum ProjectPanelDockPosition { Right, } -#[derive(Deserialize, Debug, Clone, Copy, PartialEq)] +#[derive(Deserialize, Serialize, Debug, Clone, Copy, PartialEq, JsonSchema)] +#[serde(default)] pub struct ProjectPanelSettings { + /// Whether to show the project panel button in the status bar. pub button: bool, + /// Customize default width (in pixels) taken by project panel pub default_width: Pixels, + /// The position of project panel pub dock: ProjectPanelDockPosition, + /// Whether to show file icons in the project panel. pub file_icons: bool, + /// Whether to show folder icons or chevrons for directories in the project panel. pub folder_icons: bool, + /// Whether to show the git status in the project panel. pub git_status: bool, - pub indent_size: f32, + /// Amount of indentation (in pixels) for nested items. + pub indent_size: Pixels, + /// Whether to reveal it in the project panel automatically, + /// when a corresponding project entry becomes active. + /// Gitignored entries are never auto revealed. pub auto_reveal_entries: bool, + /// Whether to fold directories automatically + /// when directory has only one directory inside. pub auto_fold_dirs: bool, + /// Scrollbar-related settings pub scrollbar: ScrollbarSettings, } +impl Default for ProjectPanelSettings { + fn default() -> Self { + Self { + button: true, + default_width: px(240.), + dock: ProjectPanelDockPosition::Left, + file_icons: true, + folder_icons: true, + git_status: true, + indent_size: px(20.), + auto_reveal_entries: true, + auto_fold_dirs: true, + scrollbar: Default::default(), + } + } +} /// When to show the scrollbar in the project panel. /// /// Default: always @@ -37,7 +68,7 @@ pub enum ShowScrollbar { Never, } -#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive(Copy, Clone, Default, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] pub struct ScrollbarSettings { /// When to show the scrollbar in the project panel. /// @@ -45,63 +76,10 @@ pub struct ScrollbarSettings { pub show: ShowScrollbar, } -#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] -pub struct ScrollbarSettingsContent { - /// When to show the scrollbar in the project panel. - /// - /// Default: always - pub show: Option, -} - -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)] -pub struct ProjectPanelSettingsContent { - /// Whether to show the project panel button in the status bar. - /// - /// Default: true - pub button: Option, - /// Customize default width (in pixels) taken by project panel - /// - /// Default: 240 - pub default_width: Option, - /// The position of project panel - /// - /// Default: left - pub dock: Option, - /// Whether to show file icons in the project panel. - /// - /// Default: true - pub file_icons: Option, - /// Whether to show folder icons or chevrons for directories in the project panel. - /// - /// Default: true - pub folder_icons: Option, - /// Whether to show the git status in the project panel. - /// - /// Default: true - pub git_status: Option, - /// Amount of indentation (in pixels) for nested items. - /// - /// Default: 20 - pub indent_size: Option, - /// Whether to reveal it in the project panel automatically, - /// when a corresponding project entry becomes active. - /// Gitignored entries are never auto revealed. - /// - /// Default: true - pub auto_reveal_entries: Option, - /// Whether to fold directories automatically - /// when directory has only one directory inside. - /// - /// Default: false - pub auto_fold_dirs: Option, - /// Scrollbar-related settings - pub scrollbar: Option, -} - impl Settings for ProjectPanelSettings { const KEY: Option<&'static str> = Some("project_panel"); - type FileContent = ProjectPanelSettingsContent; + type FileContent = Self; fn load( sources: SettingsSources, diff --git a/crates/recent_projects/src/dev_servers.rs b/crates/recent_projects/src/dev_servers.rs index d8b10f31f9..b7fa635945 100644 --- a/crates/recent_projects/src/dev_servers.rs +++ b/crates/recent_projects/src/dev_servers.rs @@ -48,7 +48,6 @@ use workspace::{notifications::DetachAndPromptErr, AppState, ModalView, Workspac use crate::open_dev_server_project; use crate::ssh_connections::connect_over_ssh; use crate::ssh_connections::open_ssh_project; -use crate::ssh_connections::RemoteSettingsContent; use crate::ssh_connections::SshConnection; use crate::ssh_connections::SshConnectionModal; use crate::ssh_connections::SshProject; @@ -1024,7 +1023,7 @@ impl DevServerProjects { fn update_settings_file( &mut self, cx: &mut ViewContext, - f: impl FnOnce(&mut RemoteSettingsContent) + Send + Sync + 'static, + f: impl FnOnce(&mut SshSettings) + Send + Sync + 'static, ) { let Some(fs) = self .workspace diff --git a/crates/recent_projects/src/ssh_connections.rs b/crates/recent_projects/src/ssh_connections.rs index 8da4284b7f..b54196022d 100644 --- a/crates/recent_projects/src/ssh_connections.rs +++ b/crates/recent_projects/src/ssh_connections.rs @@ -22,8 +22,24 @@ use ui::{ use util::paths::PathWithPosition; use workspace::{AppState, ModalView, Workspace}; -#[derive(Deserialize)] +#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)] +#[serde(default)] pub struct SshSettings { + /// ssh_connections is an array of ssh connections. + /// By default this setting is null, which disables the direct ssh connection support. + /// You can configure these from `project: Open Remote` in the command palette. + /// Zed's ssh support will pull configuration from your ~/.ssh too. + /// Examples: + /// [ + /// { + /// "host": "example-box", + /// "projects": [ + /// { + /// "paths": ["/home/user/code/zed"] + /// } + /// ] + /// } + /// ] pub ssh_connections: Option>, } @@ -62,15 +78,10 @@ pub struct SshProject { pub paths: Vec, } -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)] -pub struct RemoteSettingsContent { - pub ssh_connections: Option>, -} - impl Settings for SshSettings { const KEY: Option<&'static str> = None; - type FileContent = RemoteSettingsContent; + type FileContent = Self; fn load(sources: SettingsSources, _: &mut AppContext) -> Result { sources.json_merge() diff --git a/crates/repl/src/jupyter_settings.rs b/crates/repl/src/jupyter_settings.rs index aefef6cec5..f441da4790 100644 --- a/crates/repl/src/jupyter_settings.rs +++ b/crates/repl/src/jupyter_settings.rs @@ -6,8 +6,10 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsSources}; -#[derive(Debug, Default)] +#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] +#[serde(default)] pub struct JupyterSettings { + /// Default kernels to select for each language. pub kernel_selections: HashMap, } @@ -20,26 +22,10 @@ impl JupyterSettings { } } -#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug)] -pub struct JupyterSettingsContent { - /// Default kernels to select for each language. - /// - /// Default: `{}` - pub kernel_selections: Option>, -} - -impl Default for JupyterSettingsContent { - fn default() -> Self { - JupyterSettingsContent { - kernel_selections: Some(HashMap::new()), - } - } -} - impl Settings for JupyterSettings { const KEY: Option<&'static str> = Some("jupyter"); - type FileContent = JupyterSettingsContent; + type FileContent = Self; fn load( sources: SettingsSources, @@ -51,10 +37,8 @@ impl Settings for JupyterSettings { let mut settings = JupyterSettings::default(); for value in sources.defaults_and_customizations() { - if let Some(source) = &value.kernel_selections { - for (k, v) in source { - settings.kernel_selections.insert(k.clone(), v.clone()); - } + for (k, v) in &value.kernel_selections { + settings.kernel_selections.insert(k.clone(), v.clone()); } } diff --git a/crates/tasks_ui/src/settings.rs b/crates/tasks_ui/src/settings.rs index 1bcd496264..4ad6f607b7 100644 --- a/crates/tasks_ui/src/settings.rs +++ b/crates/tasks_ui/src/settings.rs @@ -2,22 +2,26 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsSources}; -#[derive(Serialize, Deserialize, PartialEq, Default)] +#[derive(Clone, Serialize, Deserialize, PartialEq, JsonSchema)] +#[serde(default)] +/// Task-related settings. pub(crate) struct TaskSettings { + /// Whether to show task status indicator in the status bar. Default: true pub(crate) show_status_indicator: bool, } -/// Task-related settings. -#[derive(Serialize, Deserialize, PartialEq, Default, Clone, JsonSchema)] -pub(crate) struct TaskSettingsContent { - /// Whether to show task status indicator in the status bar. Default: true - show_status_indicator: Option, +impl Default for TaskSettings { + fn default() -> Self { + Self { + show_status_indicator: true, + } + } } impl Settings for TaskSettings { const KEY: Option<&'static str> = Some("task"); - type FileContent = TaskSettingsContent; + type FileContent = Self; fn load( sources: SettingsSources, diff --git a/crates/vim/src/digraph.rs b/crates/vim/src/digraph.rs index 443b7ff378..282016cfda 100644 --- a/crates/vim/src/digraph.rs +++ b/crates/vim/src/digraph.rs @@ -132,7 +132,7 @@ mod test { let mut custom_digraphs = HashMap::default(); custom_digraphs.insert("|-".into(), "⊢".into()); custom_digraphs.insert(":)".into(), "👨‍💻".into()); - s.custom_digraphs = Some(custom_digraphs); + s.custom_digraphs = custom_digraphs; }); }); diff --git a/crates/vim/src/normal.rs b/crates/vim/src/normal.rs index 8198c0da53..815086d0be 100644 --- a/crates/vim/src/normal.rs +++ b/crates/vim/src/normal.rs @@ -1184,7 +1184,7 @@ mod test { let mut cx = VimTestContext::new(cx, true).await; cx.update_global(|store: &mut SettingsStore, cx| { store.update_user_settings::(cx, |s| { - s.use_multiline_find = Some(true); + s.use_multiline_find = true; }); }); @@ -1226,7 +1226,7 @@ mod test { let mut cx = VimTestContext::new(cx, true).await; cx.update_global(|store: &mut SettingsStore, cx| { store.update_user_settings::(cx, |s| { - s.use_multiline_find = Some(true); + s.use_multiline_find = true; }); }); @@ -1268,7 +1268,7 @@ mod test { let mut cx = VimTestContext::new(cx, true).await; cx.update_global(|store: &mut SettingsStore, cx| { store.update_user_settings::(cx, |s| { - s.use_smartcase_find = Some(true); + s.use_smartcase_find = true; }); }); diff --git a/crates/vim/src/normal/paste.rs b/crates/vim/src/normal/paste.rs index 05469dbf9f..6465e33e0f 100644 --- a/crates/vim/src/normal/paste.rs +++ b/crates/vim/src/normal/paste.rs @@ -291,7 +291,7 @@ mod test { cx.update_global(|store: &mut SettingsStore, cx| { store.update_user_settings::(cx, |s| { - s.use_system_clipboard = Some(UseSystemClipboard::Never) + s.use_system_clipboard = UseSystemClipboard::Never }); }); @@ -327,7 +327,7 @@ mod test { cx.update_global(|store: &mut SettingsStore, cx| { store.update_user_settings::(cx, |s| { - s.use_system_clipboard = Some(UseSystemClipboard::OnYank) + s.use_system_clipboard = UseSystemClipboard::OnYank }); }); @@ -584,7 +584,7 @@ mod test { cx.update_global(|store: &mut SettingsStore, cx| { store.update_user_settings::(cx, |s| { - s.use_system_clipboard = Some(UseSystemClipboard::Never) + s.use_system_clipboard = UseSystemClipboard::Never }); }); @@ -630,7 +630,7 @@ mod test { cx.update_global(|store: &mut SettingsStore, cx| { store.update_user_settings::(cx, |s| { - s.use_system_clipboard = Some(UseSystemClipboard::Never) + s.use_system_clipboard = UseSystemClipboard::Never }); }); @@ -659,7 +659,7 @@ mod test { cx.update_global(|store: &mut SettingsStore, cx| { store.update_user_settings::(cx, |s| { - s.use_system_clipboard = Some(UseSystemClipboard::Never) + s.use_system_clipboard = UseSystemClipboard::Never }); }); @@ -707,7 +707,7 @@ mod test { cx.update_global(|store: &mut SettingsStore, cx| { store.update_user_settings::(cx, |s| { - s.use_system_clipboard = Some(UseSystemClipboard::Never) + s.use_system_clipboard = UseSystemClipboard::Never }); }); diff --git a/crates/vim/src/normal/scroll.rs b/crates/vim/src/normal/scroll.rs index f89faa3748..6a20ea4eb3 100644 --- a/crates/vim/src/normal/scroll.rs +++ b/crates/vim/src/normal/scroll.rs @@ -294,7 +294,7 @@ mod test { cx.update_global(|store: &mut SettingsStore, cx| { store.update_user_settings::(cx, |s| { - s.scroll_beyond_last_line = Some(ScrollBeyondLastLine::Off) + s.scroll_beyond_last_line = ScrollBeyondLastLine::Off }); }); diff --git a/crates/vim/src/normal/search.rs b/crates/vim/src/normal/search.rs index 28f33d49d8..6418475ad2 100644 --- a/crates/vim/src/normal/search.rs +++ b/crates/vim/src/normal/search.rs @@ -542,7 +542,7 @@ mod test { let mut cx = VimTestContext::new(cx, true).await; cx.update_global(|store: &mut SettingsStore, cx| { - store.update_user_settings::(cx, |s| s.search_wrap = Some(false)); + store.update_user_settings::(cx, |s| s.search_wrap = false); }); cx.set_state("ˇhi\nhigh\nhi\n", Mode::Normal); @@ -655,7 +655,7 @@ mod test { // check that searching with unable search wrap cx.update_global(|store: &mut SettingsStore, cx| { - store.update_user_settings::(cx, |s| s.search_wrap = Some(false)); + store.update_user_settings::(cx, |s| s.search_wrap = false); }); cx.set_state("aa\nbˇb\ncc\ncc\ncc\n", Mode::Normal); cx.simulate_keystrokes("/ c c enter"); diff --git a/crates/vim/src/test.rs b/crates/vim/src/test.rs index 9c61e9cd93..be7db47315 100644 --- a/crates/vim/src/test.rs +++ b/crates/vim/src/test.rs @@ -1300,7 +1300,7 @@ async fn test_command_alias(cx: &mut gpui::TestAppContext) { store.update_user_settings::(cx, |s| { let mut aliases = HashMap::default(); aliases.insert("Q".to_string(), "upper".to_string()); - s.command_aliases = Some(aliases) + s.command_aliases = aliases }); }); diff --git a/crates/vim/src/test/vim_test_context.rs b/crates/vim/src/test/vim_test_context.rs index c985f68e70..b68d2ede8b 100644 --- a/crates/vim/src/test/vim_test_context.rs +++ b/crates/vim/src/test/vim_test_context.rs @@ -57,7 +57,7 @@ impl VimTestContext { pub fn new_with_lsp(mut cx: EditorLspTestContext, enabled: bool) -> VimTestContext { cx.update(|cx| { SettingsStore::update_global(cx, |store, cx| { - store.update_user_settings::(cx, |s| *s = Some(enabled)); + store.update_user_settings::(cx, |s| *s = VimModeSetting(enabled)); }); settings::KeymapFile::load_asset("keymaps/default-macos.json", cx).unwrap(); if enabled { @@ -105,7 +105,7 @@ impl VimTestContext { pub fn enable_vim(&mut self) { self.cx.update(|cx| { SettingsStore::update_global(cx, |store, cx| { - store.update_user_settings::(cx, |s| *s = Some(true)); + store.update_user_settings::(cx, |s| *s = VimModeSetting(true)); }); }) } @@ -113,7 +113,7 @@ impl VimTestContext { pub fn disable_vim(&mut self) { self.cx.update(|cx| { SettingsStore::update_global(cx, |store, cx| { - store.update_user_settings::(cx, |s| *s = Some(false)); + store.update_user_settings::(cx, |s| *s = VimModeSetting(false)); }); }) } diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index 6e03374c22..6baca17948 100644 --- a/crates/vim/src/vim.rs +++ b/crates/vim/src/vim.rs @@ -46,6 +46,8 @@ use crate::state::ReplayableAction; /// Whether or not to enable Vim mode. /// /// Default: false +#[derive(Copy, Clone, Default, Deserialize, Serialize, JsonSchema)] +#[serde(default, transparent)] pub struct VimModeSetting(pub bool); /// An Action to Switch between modes @@ -99,7 +101,7 @@ pub fn init(cx: &mut AppContext) { let fs = workspace.app_state().fs.clone(); let currently_enabled = Vim::enabled(cx); update_settings_file::(fs, cx, move |setting, _| { - *setting = Some(!currently_enabled) + *setting = VimModeSetting(!currently_enabled); }) }); @@ -1068,12 +1070,10 @@ impl Vim { impl Settings for VimModeSetting { const KEY: Option<&'static str> = Some("vim_mode"); - type FileContent = Option; + type FileContent = Self; fn load(sources: SettingsSources, _: &mut AppContext) -> Result { - Ok(Self(sources.user.copied().flatten().unwrap_or( - sources.default.ok_or_else(Self::missing_default)?, - ))) + Ok(sources.user.copied().unwrap_or(*sources.default)) } } @@ -1089,7 +1089,8 @@ pub enum UseSystemClipboard { OnYank, } -#[derive(Deserialize)] +#[derive(Clone, Serialize, Deserialize, JsonSchema)] +#[serde(default)] struct VimSettings { pub toggle_relative_line_numbers: bool, pub use_system_clipboard: UseSystemClipboard, @@ -1098,19 +1099,22 @@ struct VimSettings { pub custom_digraphs: HashMap>, } -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)] -struct VimSettingsContent { - pub toggle_relative_line_numbers: Option, - pub use_system_clipboard: Option, - pub use_multiline_find: Option, - pub use_smartcase_find: Option, - pub custom_digraphs: Option>>, +impl Default for VimSettings { + fn default() -> Self { + Self { + toggle_relative_line_numbers: false, + use_system_clipboard: UseSystemClipboard::Always, + use_multiline_find: false, + use_smartcase_find: false, + custom_digraphs: Default::default(), + } + } } impl Settings for VimSettings { const KEY: Option<&'static str> = Some("vim"); - type FileContent = VimSettingsContent; + type FileContent = Self; fn load(sources: SettingsSources, _: &mut AppContext) -> Result { sources.json_merge() diff --git a/crates/welcome/src/base_keymap_picker.rs b/crates/welcome/src/base_keymap_picker.rs index 96a9df9c3c..fd7361f9b3 100644 --- a/crates/welcome/src/base_keymap_picker.rs +++ b/crates/welcome/src/base_keymap_picker.rs @@ -177,7 +177,7 @@ impl PickerDelegate for BaseKeymapSelectorDelegate { .report_setting_event("keymap", base_keymap.to_string()); update_settings_file::(self.fs.clone(), cx, move |setting, _| { - *setting = Some(base_keymap) + *setting = base_keymap; }); } diff --git a/crates/welcome/src/base_keymap_setting.rs b/crates/welcome/src/base_keymap_setting.rs index 1b52bbc9f9..0c1724627c 100644 --- a/crates/welcome/src/base_keymap_setting.rs +++ b/crates/welcome/src/base_keymap_setting.rs @@ -87,15 +87,15 @@ impl BaseKeymap { impl Settings for BaseKeymap { const KEY: Option<&'static str> = Some("base_keymap"); - type FileContent = Option; + type FileContent = Self; fn load( sources: SettingsSources, _: &mut gpui::AppContext, ) -> anyhow::Result { - if let Some(Some(user_value)) = sources.user.copied() { + if let Some(user_value) = sources.user.copied() { return Ok(user_value); } - sources.default.ok_or_else(Self::missing_default) + Ok(*sources.default) } } diff --git a/crates/welcome/src/welcome.rs b/crates/welcome/src/welcome.rs index fc837c6867..787c2e589b 100644 --- a/crates/welcome/src/welcome.rs +++ b/crates/welcome/src/welcome.rs @@ -188,7 +188,7 @@ impl Render for WelcomePage { this.update_settings::( selection, cx, - |setting, value| *setting = Some(value), + |setting, value| *setting = VimModeSetting(value), ); }), )) diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index 935f0268b6..46b8f3bf7f 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -36,20 +36,49 @@ use util::ResultExt; pub const LEADER_UPDATE_THROTTLE: Duration = Duration::from_millis(200); -#[derive(Deserialize)] +#[derive(Clone, Serialize, Deserialize, JsonSchema)] +#[serde(default)] pub struct ItemSettings { + /// Whether to show the Git file status on a tab item. pub git_status: bool, + /// Position of the close button in a tab. pub close_position: ClosePosition, + /// Whether to show the file icon for a tab. pub file_icons: bool, } -#[derive(Deserialize)] +impl Default for ItemSettings { + fn default() -> Self { + Self { + git_status: false, + close_position: ClosePosition::Right, + file_icons: false, + } + } +} + +#[derive(Clone, Serialize, Deserialize, JsonSchema)] +#[serde(default)] pub struct PreviewTabsSettings { + /// Whether to show opened editors as preview tabs. + /// Preview tabs do not stay open, are reused until explicitly set to be kept open opened (via double-click or editing) and show file names in italic. pub enabled: bool, + /// Whether to open tabs in preview mode when selected from the file finder. pub enable_preview_from_file_finder: bool, + /// Whether a preview tab gets replaced when code navigation is used to navigate away from the tab. pub enable_preview_from_code_navigation: bool, } +impl Default for PreviewTabsSettings { + fn default() -> Self { + Self { + enabled: true, + enable_preview_from_file_finder: false, + enable_preview_from_code_navigation: false, + } + } +} + #[derive(Clone, Default, Serialize, Deserialize, JsonSchema)] #[serde(rename_all = "lowercase")] pub enum ClosePosition { @@ -67,43 +96,10 @@ impl ClosePosition { } } -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)] -pub struct ItemSettingsContent { - /// Whether to show the Git file status on a tab item. - /// - /// Default: false - git_status: Option, - /// Position of the close button in a tab. - /// - /// Default: right - close_position: Option, - /// Whether to show the file icon for a tab. - /// - /// Default: false - file_icons: Option, -} - -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)] -pub struct PreviewTabsSettingsContent { - /// Whether to show opened editors as preview tabs. - /// Preview tabs do not stay open, are reused until explicitly set to be kept open opened (via double-click or editing) and show file names in italic. - /// - /// Default: true - enabled: Option, - /// Whether to open tabs in preview mode when selected from the file finder. - /// - /// Default: false - enable_preview_from_file_finder: Option, - /// Whether a preview tab gets replaced when code navigation is used to navigate away from the tab. - /// - /// Default: false - enable_preview_from_code_navigation: Option, -} - impl Settings for ItemSettings { const KEY: Option<&'static str> = Some("tabs"); - type FileContent = ItemSettingsContent; + type FileContent = Self; fn load(sources: SettingsSources, _: &mut AppContext) -> Result { sources.json_merge() @@ -113,7 +109,7 @@ impl Settings for ItemSettings { impl Settings for PreviewTabsSettings { const KEY: Option<&'static str> = Some("preview_tabs"); - type FileContent = PreviewTabsSettingsContent; + type FileContent = Self; fn load(sources: SettingsSources, _: &mut AppContext) -> Result { sources.json_merge() diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 7371e56cee..50b92326b2 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -6418,7 +6418,7 @@ mod tests { item.update(cx, |item, cx| { SettingsStore::update_global(cx, |settings, cx| { settings.update_user_settings::(cx, |settings| { - settings.autosave = Some(AutosaveSetting::OnWindowChange); + settings.autosave = AutosaveSetting::OnWindowChange; }) }); item.is_dirty = true; @@ -6438,7 +6438,7 @@ mod tests { cx.focus_self(); SettingsStore::update_global(cx, |settings, cx| { settings.update_user_settings::(cx, |settings| { - settings.autosave = Some(AutosaveSetting::OnFocusChange); + settings.autosave = AutosaveSetting::OnFocusChange; }) }); item.is_dirty = true; @@ -6461,7 +6461,7 @@ mod tests { item.update(cx, |item, cx| { SettingsStore::update_global(cx, |settings, cx| { settings.update_user_settings::(cx, |settings| { - settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 }); + settings.autosave = AutosaveSetting::AfterDelay { milliseconds: 500 }; }) }); item.is_dirty = true; @@ -6480,7 +6480,7 @@ mod tests { item.update(cx, |item, cx| { SettingsStore::update_global(cx, |settings, cx| { settings.update_user_settings::(cx, |settings| { - settings.autosave = Some(AutosaveSetting::OnFocusChange); + settings.autosave = AutosaveSetting::OnFocusChange; }) }); item.is_dirty = true; diff --git a/crates/workspace/src/workspace_settings.rs b/crates/workspace/src/workspace_settings.rs index 52827c6941..f87840eb30 100644 --- a/crates/workspace/src/workspace_settings.rs +++ b/crates/workspace/src/workspace_settings.rs @@ -5,22 +5,58 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsSources}; -#[derive(Deserialize)] +#[derive(Clone, Serialize, Deserialize, JsonSchema)] +#[serde(default)] pub struct WorkspaceSettings { + /// Scale by which to zoom the active pane. + /// When set to 1.0, the active pane has the same size as others, + /// but when set to a larger value, the active pane takes up more space. pub active_pane_magnification: f32, + /// Direction to split horizontally. pub pane_split_direction_horizontal: PaneSplitDirectionHorizontal, + /// Direction to split vertically. pub pane_split_direction_vertical: PaneSplitDirectionVertical, + /// Centered layout related settings. pub centered_layout: CenteredLayoutSettings, + /// Whether or not to prompt the user to confirm before closing the application. pub confirm_quit: bool, + /// Whether or not to show the call status icon in the status bar. pub show_call_status_icon: bool, + /// When to automatically save edited buffers. pub autosave: AutosaveSetting, + /// Controls previous session restoration in freshly launched Zed instance. pub restore_on_startup: RestoreOnStartupBehavior, + /// The size of the workspace split drop targets on the outer edges. + /// Given as a fraction that will be multiplied by the smaller dimension of the workspace. pub drop_target_size: f32, + /// Whether to close the window when using 'close active item' on a workspace with no tabs pub when_closing_with_no_tabs: CloseWindowWhenNoItems, + /// Whether to use the system provided dialogs for Open and Save As. + /// When set to false, Zed will use the built-in keyboard-first pickers. pub use_system_path_prompts: bool, + /// Aliases for the command palette. When you type a key in this map, + /// it will be assumed to equal the value. pub command_aliases: HashMap, } +impl Default for WorkspaceSettings { + fn default() -> Self { + Self { + active_pane_magnification: 1.0, + pane_split_direction_horizontal: PaneSplitDirectionHorizontal::Up, + pane_split_direction_vertical: PaneSplitDirectionVertical::Left, + centered_layout: CenteredLayoutSettings::default(), + confirm_quit: false, + show_call_status_icon: true, + autosave: AutosaveSetting::Off, + restore_on_startup: RestoreOnStartupBehavior::default(), + drop_target_size: 0.2, + when_closing_with_no_tabs: CloseWindowWhenNoItems::default(), + use_system_path_prompts: true, + command_aliases: HashMap::default(), + } + } +} #[derive(Copy, Clone, Default, Serialize, Deserialize, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum CloseWindowWhenNoItems { @@ -55,77 +91,22 @@ pub enum RestoreOnStartupBehavior { LastSession, } -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)] -pub struct WorkspaceSettingsContent { - /// Scale by which to zoom the active pane. - /// When set to 1.0, the active pane has the same size as others, - /// but when set to a larger value, the active pane takes up more space. - /// - /// Default: `1.0` - pub active_pane_magnification: Option, - // Direction to split horizontally. - // - // Default: "up" - pub pane_split_direction_horizontal: Option, - // Direction to split vertically. - // - // Default: "left" - pub pane_split_direction_vertical: Option, - // Centered layout related settings. - pub centered_layout: Option, - /// Whether or not to prompt the user to confirm before closing the application. - /// - /// Default: false - pub confirm_quit: Option, - /// Whether or not to show the call status icon in the status bar. - /// - /// Default: true - pub show_call_status_icon: Option, - /// When to automatically save edited buffers. - /// - /// Default: off - pub autosave: Option, - /// Controls previous session restoration in freshly launched Zed instance. - /// Values: none, last_workspace, last_session - /// Default: last_session - pub restore_on_startup: Option, - /// The size of the workspace split drop targets on the outer edges. - /// Given as a fraction that will be multiplied by the smaller dimension of the workspace. - /// - /// Default: `0.2` (20% of the smaller dimension of the workspace) - pub drop_target_size: Option, - /// Whether to close the window when using 'close active item' on a workspace with no tabs - /// - /// Default: auto ("on" on macOS, "off" otherwise) - pub when_closing_with_no_tabs: Option, - /// Whether to use the system provided dialogs for Open and Save As. - /// When set to false, Zed will use the built-in keyboard-first pickers. - /// - /// Default: true - pub use_system_path_prompts: Option, - /// Aliases for the command palette. When you type a key in this map, - /// it will be assumed to equal the value. - /// - /// Default: true - pub command_aliases: Option>, -} - -#[derive(Deserialize)] +#[derive(Clone, Serialize, Deserialize, JsonSchema)] +#[serde(default)] pub struct TabBarSettings { + /// Whether or not to show the tab bar in the editor. pub show: bool, + /// Whether or not to show the navigation history buttons in the tab bar. pub show_nav_history_buttons: bool, } -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)] -pub struct TabBarSettingsContent { - /// Whether or not to show the tab bar in the editor. - /// - /// Default: true - pub show: Option, - /// Whether or not to show the navigation history buttons in the tab bar. - /// - /// Default: true - pub show_nav_history_buttons: Option, +impl Default for TabBarSettings { + fn default() -> Self { + Self { + show_nav_history_buttons: true, + show: true, + } + } } #[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] @@ -163,17 +144,26 @@ pub struct CenteredLayoutSettings { /// /// Default: 0.2 pub left_padding: Option, - // The relative width of the right padding of the central pane from the - // workspace when the centered layout is used. + /// The relative width of the right padding of the central pane from the + /// workspace when the centered layout is used. /// /// Default: 0.2 pub right_padding: Option, } +impl Default for CenteredLayoutSettings { + fn default() -> Self { + Self { + left_padding: Some(0.2), + right_padding: Some(0.2), + } + } +} + impl Settings for WorkspaceSettings { const KEY: Option<&'static str> = None; - type FileContent = WorkspaceSettingsContent; + type FileContent = Self; fn load(sources: SettingsSources, _: &mut AppContext) -> Result { sources.json_merge() @@ -183,7 +173,7 @@ impl Settings for WorkspaceSettings { impl Settings for TabBarSettings { const KEY: Option<&'static str> = Some("tab_bar"); - type FileContent = TabBarSettingsContent; + type FileContent = Self; fn load(sources: SettingsSources, _: &mut AppContext) -> Result { sources.json_merge() diff --git a/crates/worktree/src/worktree_settings.rs b/crates/worktree/src/worktree_settings.rs index 32851d963a..82be3a8028 100644 --- a/crates/worktree/src/worktree_settings.rs +++ b/crates/worktree/src/worktree_settings.rs @@ -25,7 +25,8 @@ impl WorktreeSettings { } } -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Serialize, Deserialize, JsonSchema)] +#[serde(default)] pub struct WorktreeSettingsContent { /// Completely ignore files matching globs from `file_scan_exclusions` /// @@ -39,12 +40,42 @@ pub struct WorktreeSettingsContent { /// "**/.classpath", /// "**/.settings" /// ] - #[serde(default)] - pub file_scan_exclusions: Option>, + pub file_scan_exclusions: Vec, /// Treat the files matching these globs as `.env` files. /// Default: [ "**/.env*" ] - pub private_files: Option>, + pub private_files: Vec, +} + +impl Default for WorktreeSettingsContent { + fn default() -> Self { + Self { + private_files: [ + "**/.env*", + "**/*.pem", + "**/*.key", + "**/*.cert", + "**/*.crt", + "**/secrets.yml", + ] + .into_iter() + .map(str::to_owned) + .collect(), + file_scan_exclusions: [ + "**/.git", + "**/.svn", + "**/.hg", + "**/CVS", + "**/.DS_Store", + "**/Thumbs.db", + "**/.classpath", + "**/.settings", + ] + .into_iter() + .map(str::to_owned) + .collect(), + } + } } impl Settings for WorktreeSettings { @@ -57,8 +88,8 @@ impl Settings for WorktreeSettings { _: &mut AppContext, ) -> anyhow::Result { let result: WorktreeSettingsContent = sources.json_merge()?; - let mut file_scan_exclusions = result.file_scan_exclusions.unwrap_or_default(); - let mut private_files = result.private_files.unwrap_or_default(); + let mut file_scan_exclusions = result.file_scan_exclusions; + let mut private_files = result.private_files; file_scan_exclusions.sort(); private_files.sort(); Ok(Self { diff --git a/crates/worktree/src/worktree_tests.rs b/crates/worktree/src/worktree_tests.rs index 929dc01c6d..455bc62a79 100644 --- a/crates/worktree/src/worktree_tests.rs +++ b/crates/worktree/src/worktree_tests.rs @@ -673,7 +673,7 @@ async fn test_rescan_with_gitignore(cx: &mut TestAppContext) { cx.update(|cx| { cx.update_global::(|store, cx| { store.update_user_settings::(cx, |project_settings| { - project_settings.file_scan_exclusions = Some(Vec::new()); + project_settings.file_scan_exclusions = Vec::new(); }); }); }); @@ -910,7 +910,7 @@ async fn test_file_scan_exclusions(cx: &mut TestAppContext) { cx.update_global::(|store, cx| { store.update_user_settings::(cx, |project_settings| { project_settings.file_scan_exclusions = - Some(vec!["**/foo/**".to_string(), "**/.DS_Store".to_string()]); + vec!["**/foo/**".to_string(), "**/.DS_Store".to_string()]; }); }); }); @@ -945,8 +945,7 @@ async fn test_file_scan_exclusions(cx: &mut TestAppContext) { cx.update(|cx| { cx.update_global::(|store, cx| { store.update_user_settings::(cx, |project_settings| { - project_settings.file_scan_exclusions = - Some(vec!["**/node_modules/**".to_string()]); + project_settings.file_scan_exclusions = vec!["**/node_modules/**".to_string()]; }); }); }); @@ -1009,11 +1008,11 @@ async fn test_fs_events_in_exclusions(cx: &mut TestAppContext) { cx.update(|cx| { cx.update_global::(|store, cx| { store.update_user_settings::(cx, |project_settings| { - project_settings.file_scan_exclusions = Some(vec![ + project_settings.file_scan_exclusions = vec![ "**/.git".to_string(), "node_modules/".to_string(), "build_output".to_string(), - ]); + ]; }); }); }); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 93fee57ecd..9f670efcd7 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -1996,7 +1996,7 @@ mod tests { cx.update_global::(|store, cx| { store.update_user_settings::(cx, |project_settings| { project_settings.file_scan_exclusions = - Some(vec!["excluded_dir".to_string(), "**/.git".to_string()]); + vec!["excluded_dir".to_string(), "**/.git".to_string()]; }); }); });