From 6403bb86e11cdcbc2055bf704d8c891076deaed3 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 16 May 2023 20:25:18 -0700 Subject: [PATCH] Define workspace settings in workspace crate --- Cargo.lock | 1 + .../collab_ui/src/sharing_status_indicator.rs | 6 +- crates/diagnostics/src/diagnostics.rs | 1 + crates/editor/src/editor_tests.rs | 1 + crates/editor/src/element.rs | 10 +- .../src/test/editor_lsp_test_context.rs | 1 + crates/file_finder/src/file_finder.rs | 1 + crates/project_panel/src/project_panel.rs | 1 + crates/project_symbols/src/project_symbols.rs | 1 + crates/search/src/project_search.rs | 1 + crates/settings/src/settings.rs | 128 +----------------- crates/terminal_view/src/terminal_view.rs | 47 +++---- crates/workspace/Cargo.toml | 1 + crates/workspace/src/dock.rs | 18 +-- crates/workspace/src/item.rs | 17 ++- crates/workspace/src/pane.rs | 38 ++++-- crates/workspace/src/pane_group.rs | 6 +- crates/workspace/src/persistence.rs | 9 +- crates/workspace/src/persistence/model.rs | 9 +- crates/workspace/src/workspace.rs | 92 ++++++++----- crates/workspace/src/workspace_settings.rs | 103 ++++++++++++++ crates/zed/src/zed.rs | 4 +- 22 files changed, 253 insertions(+), 243 deletions(-) create mode 100644 crates/workspace/src/workspace_settings.rs diff --git a/Cargo.lock b/Cargo.lock index e0f70b15c5..216103959c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8676,6 +8676,7 @@ dependencies = [ "parking_lot 0.11.2", "postage", "project", + "schemars", "serde", "serde_derive", "serde_json", diff --git a/crates/collab_ui/src/sharing_status_indicator.rs b/crates/collab_ui/src/sharing_status_indicator.rs index 9fbe57af65..8394036f39 100644 --- a/crates/collab_ui/src/sharing_status_indicator.rs +++ b/crates/collab_ui/src/sharing_status_indicator.rs @@ -6,7 +6,7 @@ use gpui::{ platform::{Appearance, MouseButton}, AnyElement, AppContext, Element, Entity, View, ViewContext, }; -use settings::Settings; +use workspace::WorkspaceSettings; pub fn init(cx: &mut AppContext) { let active_call = ActiveCall::global(cx); @@ -15,7 +15,9 @@ pub fn init(cx: &mut AppContext) { cx.observe(&active_call, move |call, cx| { if let Some(room) = call.read(cx).room() { if room.read(cx).is_screen_sharing() { - if status_indicator.is_none() && cx.global::().show_call_status_icon { + if status_indicator.is_none() + && settings::get_setting::(None, cx).show_call_status_icon + { status_indicator = Some(cx.add_status_bar_item(|_| SharingStatusIndicator)); } } else if let Some((window_id, _)) = status_indicator.take() { diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 62e03ca54f..f7746496aa 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -1499,6 +1499,7 @@ mod tests { cx.set_global(Settings::test(cx)); cx.set_global(SettingsStore::test(cx)); language::init(cx); + workspace::init_settings(cx); }); } diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 49e878947d..cce10eb05e 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -6685,6 +6685,7 @@ pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsC language::init(cx); crate::init(cx); Project::init_settings(cx); + workspace::init_settings(cx); }); update_test_settings(cx, f); diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 8194d51b25..bc5998cc29 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -40,7 +40,7 @@ use language::{ Selection, }; use project::ProjectPath; -use settings::{GitGutter, Settings}; +use settings::Settings; use smallvec::SmallVec; use std::{ borrow::Cow, @@ -50,7 +50,7 @@ use std::{ ops::Range, sync::Arc, }; -use workspace::item::Item; +use workspace::{item::Item, GitGutterSetting, WorkspaceSettings}; enum FoldMarkers {} @@ -550,11 +550,11 @@ impl EditorElement { let scroll_top = scroll_position.y() * line_height; let show_gutter = matches!( - &cx.global::() - .git_overrides + settings::get_setting::(None, cx) + .git .git_gutter .unwrap_or_default(), - GitGutter::TrackedFiles + GitGutterSetting::TrackedFiles ); if show_gutter { diff --git a/crates/editor/src/test/editor_lsp_test_context.rs b/crates/editor/src/test/editor_lsp_test_context.rs index a5633a9e55..9f2ab4ffac 100644 --- a/crates/editor/src/test/editor_lsp_test_context.rs +++ b/crates/editor/src/test/editor_lsp_test_context.rs @@ -41,6 +41,7 @@ impl<'a> EditorLspTestContext<'a> { crate::init(cx); pane::init(cx); Project::init_settings(cx); + workspace::init_settings(cx); }); let file_name = format!( diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index 7d5aff79d0..ad865ec2f7 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -658,6 +658,7 @@ mod tests { language::init(cx); super::init(cx); editor::init(cx); + workspace::init_settings(cx); state }) } diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index d36536f334..fb82fbfdc4 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1936,6 +1936,7 @@ mod tests { cx.set_global(SettingsStore::test(cx)); language::init(cx); editor::init_settings(cx); + workspace::init_settings(cx); }); } } diff --git a/crates/project_symbols/src/project_symbols.rs b/crates/project_symbols/src/project_symbols.rs index 239a0f98f2..74edd45c21 100644 --- a/crates/project_symbols/src/project_symbols.rs +++ b/crates/project_symbols/src/project_symbols.rs @@ -375,6 +375,7 @@ mod tests { cx.set_global(SettingsStore::test(cx)); language::init(cx); Project::init_settings(cx); + workspace::init_settings(cx); }); } diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index e869efcd5c..7364431407 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -1288,6 +1288,7 @@ pub mod tests { language::init(cx); editor::init_settings(cx); + workspace::init_settings(cx); }); } } diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index b1f171d52e..cd63776f5f 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -3,7 +3,7 @@ mod keymap_file; mod settings_file; mod settings_store; -use anyhow::{bail, Result}; +use anyhow::Result; use gpui::{ font_cache::{FamilyId, FontCache}, fonts, AppContext, AssetSource, @@ -15,10 +15,6 @@ use schemars::{ }; use serde::{Deserialize, Serialize}; use serde_json::Value; -use sqlez::{ - bindable::{Bind, Column, StaticColumnCount}, - statement::Statement, -}; use std::{borrow::Cow, str, sync::Arc}; use theme::{Theme, ThemeRegistry}; use util::ResultExt as _; @@ -37,13 +33,6 @@ pub struct Settings { pub buffer_font_features: fonts::Features, pub buffer_font_family: FamilyId, pub buffer_font_size: f32, - pub active_pane_magnification: f32, - pub confirm_quit: bool, - pub show_call_status_icon: bool, - pub autosave: Autosave, - pub default_dock_anchor: DockAnchor, - pub git: GitSettings, - pub git_overrides: GitSettings, pub theme: Arc, pub base_keymap: BaseKeymap, } @@ -72,13 +61,6 @@ impl Setting for Settings { buffer_font_family_name: defaults.buffer_font_family.clone().unwrap(), buffer_font_features, buffer_font_size: defaults.buffer_font_size.unwrap(), - active_pane_magnification: defaults.active_pane_magnification.unwrap(), - confirm_quit: defaults.confirm_quit.unwrap(), - show_call_status_icon: defaults.show_call_status_icon.unwrap(), - autosave: defaults.autosave.unwrap(), - default_dock_anchor: defaults.default_dock_anchor.unwrap(), - git: defaults.git.unwrap(), - git_overrides: Default::default(), theme: themes.get(defaults.theme.as_ref().unwrap()).unwrap(), base_keymap: Default::default(), }; @@ -201,65 +183,6 @@ impl BaseKeymap { .unwrap_or_default() } } -#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] -pub struct GitSettings { - pub git_gutter: Option, - pub gutter_debounce: Option, -} - -#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum GitGutter { - #[default] - TrackedFiles, - Hide, -} - -#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum Autosave { - Off, - AfterDelay { milliseconds: u64 }, - OnFocusChange, - OnWindowChange, -} - -#[derive(PartialEq, Eq, Debug, Default, Copy, Clone, Hash, Serialize, Deserialize, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum DockAnchor { - #[default] - Bottom, - Right, - Expanded, -} - -impl StaticColumnCount for DockAnchor {} -impl Bind for DockAnchor { - fn bind(&self, statement: &Statement, start_index: i32) -> anyhow::Result { - match self { - DockAnchor::Bottom => "Bottom", - DockAnchor::Right => "Right", - DockAnchor::Expanded => "Expanded", - } - .bind(statement, start_index) - } -} - -impl Column for DockAnchor { - fn column(statement: &mut Statement, start_index: i32) -> anyhow::Result<(Self, i32)> { - String::column(statement, start_index).and_then(|(anchor_text, next_index)| { - Ok(( - match anchor_text.as_ref() { - "Bottom" => DockAnchor::Bottom, - "Right" => DockAnchor::Right, - "Expanded" => DockAnchor::Expanded, - _ => bail!("Stored dock anchor is incorrect"), - }, - next_index, - )) - }) - } -} #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] pub struct SettingsFileContent { @@ -270,24 +193,6 @@ pub struct SettingsFileContent { #[serde(default)] pub buffer_font_features: Option, #[serde(default)] - pub active_pane_magnification: Option, - #[serde(default)] - pub cursor_blink: Option, - #[serde(default)] - pub confirm_quit: Option, - #[serde(default)] - pub hover_popover_enabled: Option, - #[serde(default)] - pub show_completions_on_input: Option, - #[serde(default)] - pub show_call_status_icon: Option, - #[serde(default)] - pub autosave: Option, - #[serde(default)] - pub default_dock_anchor: Option, - #[serde(default)] - pub git: Option, - #[serde(default)] pub theme: Option, #[serde(default)] pub base_keymap: Option, @@ -323,13 +228,6 @@ impl Settings { buffer_font_family_name: defaults.buffer_font_family.unwrap(), buffer_font_features, buffer_font_size: defaults.buffer_font_size.unwrap(), - active_pane_magnification: defaults.active_pane_magnification.unwrap(), - confirm_quit: defaults.confirm_quit.unwrap(), - show_call_status_icon: defaults.show_call_status_icon.unwrap(), - autosave: defaults.autosave.unwrap(), - default_dock_anchor: defaults.default_dock_anchor.unwrap(), - git: defaults.git.unwrap(), - git_overrides: Default::default(), theme: themes.get(&defaults.theme.unwrap()).unwrap(), base_keymap: Default::default(), } @@ -367,24 +265,7 @@ impl Settings { } merge(&mut self.buffer_font_size, data.buffer_font_size); - merge( - &mut self.active_pane_magnification, - data.active_pane_magnification, - ); - merge(&mut self.confirm_quit, data.confirm_quit); - merge(&mut self.autosave, data.autosave); - merge(&mut self.default_dock_anchor, data.default_dock_anchor); merge(&mut self.base_keymap, data.base_keymap); - - self.git_overrides = data.git.unwrap_or_default(); - } - - pub fn git_gutter(&self) -> GitGutter { - self.git_overrides.git_gutter.unwrap_or_else(|| { - self.git - .git_gutter - .expect("git_gutter should be some by setting setup") - }) } #[cfg(any(test, feature = "test-support"))] @@ -397,13 +278,6 @@ impl Settings { .load_family(&["Monaco"], &Default::default()) .unwrap(), buffer_font_size: 14., - active_pane_magnification: 1., - confirm_quit: false, - show_call_status_icon: true, - autosave: Autosave::Off, - default_dock_anchor: DockAnchor::Bottom, - git: Default::default(), - git_overrides: Default::default(), theme: gpui::fonts::with_font_cache(cx.font_cache().clone(), Default::default), base_keymap: Default::default(), } diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index 1615387ca2..76bc9b4a38 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -787,22 +787,18 @@ fn get_path_from_wt(wt: &LocalWorktree) -> Option { #[cfg(test)] mod tests { - use super::*; use gpui::TestAppContext; use project::{Entry, Project, ProjectPath, Worktree}; + use std::path::Path; use workspace::AppState; - use std::path::Path; + // Working directory calculation tests - ///Working directory calculation tests - - ///No Worktrees in project -> home_dir() + // No Worktrees in project -> home_dir() #[gpui::test] async fn no_worktree(cx: &mut TestAppContext) { - //Setup variables - let (project, workspace) = blank_workspace(cx).await; - //Test + let (project, workspace) = init_test(cx).await; cx.read(|cx| { let workspace = workspace.read(cx); let active_entry = project.read(cx).active_entry(); @@ -818,14 +814,12 @@ mod tests { }); } - ///No active entry, but a worktree, worktree is a file -> home_dir() + // No active entry, but a worktree, worktree is a file -> home_dir() #[gpui::test] async fn no_active_entry_worktree_is_file(cx: &mut TestAppContext) { - //Setup variables + let (project, workspace) = init_test(cx).await; - let (project, workspace) = blank_workspace(cx).await; create_file_wt(project.clone(), "/root.txt", cx).await; - cx.read(|cx| { let workspace = workspace.read(cx); let active_entry = project.read(cx).active_entry(); @@ -841,14 +835,12 @@ mod tests { }); } - //No active entry, but a worktree, worktree is a folder -> worktree_folder + // No active entry, but a worktree, worktree is a folder -> worktree_folder #[gpui::test] async fn no_active_entry_worktree_is_dir(cx: &mut TestAppContext) { - //Setup variables - let (project, workspace) = blank_workspace(cx).await; - let (_wt, _entry) = create_folder_wt(project.clone(), "/root/", cx).await; + let (project, workspace) = init_test(cx).await; - //Test + let (_wt, _entry) = create_folder_wt(project.clone(), "/root/", cx).await; cx.update(|cx| { let workspace = workspace.read(cx); let active_entry = project.read(cx).active_entry(); @@ -863,17 +855,15 @@ mod tests { }); } - //Active entry with a work tree, worktree is a file -> home_dir() + // Active entry with a work tree, worktree is a file -> home_dir() #[gpui::test] async fn active_entry_worktree_is_file(cx: &mut TestAppContext) { - //Setup variables + let (project, workspace) = init_test(cx).await; - let (project, workspace) = blank_workspace(cx).await; let (_wt, _entry) = create_folder_wt(project.clone(), "/root1/", cx).await; let (wt2, entry2) = create_file_wt(project.clone(), "/root2.txt", cx).await; insert_active_entry_for(wt2, entry2, project.clone(), cx); - //Test cx.update(|cx| { let workspace = workspace.read(cx); let active_entry = project.read(cx).active_entry(); @@ -887,16 +877,15 @@ mod tests { }); } - //Active entry, with a worktree, worktree is a folder -> worktree_folder + // Active entry, with a worktree, worktree is a folder -> worktree_folder #[gpui::test] async fn active_entry_worktree_is_dir(cx: &mut TestAppContext) { - //Setup variables - let (project, workspace) = blank_workspace(cx).await; + let (project, workspace) = init_test(cx).await; + let (_wt, _entry) = create_folder_wt(project.clone(), "/root1/", cx).await; let (wt2, entry2) = create_folder_wt(project.clone(), "/root2/", cx).await; insert_active_entry_for(wt2, entry2, project.clone(), cx); - //Test cx.update(|cx| { let workspace = workspace.read(cx); let active_entry = project.read(cx).active_entry(); @@ -910,8 +899,8 @@ mod tests { }); } - ///Creates a worktree with 1 file: /root.txt - pub async fn blank_workspace( + /// Creates a worktree with 1 file: /root.txt + pub async fn init_test( cx: &mut TestAppContext, ) -> (ModelHandle, ViewHandle) { let params = cx.update(AppState::test); @@ -922,7 +911,7 @@ mod tests { (project, workspace) } - ///Creates a worktree with 1 folder: /root{suffix}/ + /// Creates a worktree with 1 folder: /root{suffix}/ async fn create_folder_wt( project: ModelHandle, path: impl AsRef, @@ -931,7 +920,7 @@ mod tests { create_wt(project, true, path, cx).await } - ///Creates a worktree with 1 file: /root{suffix}.txt + /// Creates a worktree with 1 file: /root{suffix}.txt async fn create_file_wt( project: ModelHandle, path: impl AsRef, diff --git a/crates/workspace/Cargo.toml b/crates/workspace/Cargo.toml index 177dc0a292..26797e8d6c 100644 --- a/crates/workspace/Cargo.toml +++ b/crates/workspace/Cargo.toml @@ -45,6 +45,7 @@ lazy_static.workspace = true log.workspace = true parking_lot.workspace = true postage.workspace = true +schemars.workspace = true serde.workspace = true serde_derive.workspace = true serde_json.workspace = true diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 7efcb7f9d3..5116391976 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -1,5 +1,9 @@ mod toggle_dock_button; +use crate::{ + sidebar::SidebarSide, BackgroundActions, DockAnchor, ItemHandle, Pane, Workspace, + WorkspaceSettings, +}; use collections::HashMap; use gpui::{ actions, @@ -8,10 +12,7 @@ use gpui::{ platform::{CursorStyle, MouseButton}, AnyElement, AppContext, Border, Element, SizeConstraint, ViewContext, ViewHandle, }; -use settings::{DockAnchor, Settings}; use theme::Theme; - -use crate::{sidebar::SidebarSide, BackgroundActions, ItemHandle, Pane, Workspace}; pub use toggle_dock_button::ToggleDockButton; actions!( @@ -171,7 +172,9 @@ impl Dock { background_actions: BackgroundActions, cx: &mut ViewContext, ) -> Self { - let position = DockPosition::Hidden(cx.global::().default_dock_anchor); + let position = DockPosition::Hidden( + settings::get_setting::(None, cx).default_dock_anchor, + ); let workspace = cx.weak_handle(); let pane = cx.add_view(|cx| Pane::new(workspace, Some(position.anchor()), background_actions, cx)); @@ -405,7 +408,6 @@ mod tests { use gpui::{AppContext, BorrowWindowContext, TestAppContext, ViewContext, WindowContext}; use project::{FakeFs, Project}; - use settings::Settings; use theme::ThemeRegistry; use super::*; @@ -417,6 +419,7 @@ mod tests { }, register_deserializable_item, sidebar::Sidebar, + tests::init_test, AppState, ItemHandle, Workspace, }; @@ -429,8 +432,7 @@ mod tests { #[gpui::test] async fn test_dock_workspace_infinite_loop(cx: &mut TestAppContext) { - cx.foreground().forbid_parking(); - Settings::test_async(cx); + init_test(cx); cx.update(|cx| { register_deserializable_item::(cx); @@ -598,7 +600,7 @@ mod tests { impl<'a> DockTestContext<'a> { pub async fn new(cx: &'a mut TestAppContext) -> DockTestContext<'a> { - Settings::test_async(cx); + init_test(cx); let fs = FakeFs::new(cx.background()); cx.update(|cx| init(cx)); diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index 43c4544611..e8c10c6cd2 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -3,6 +3,7 @@ use crate::{ FollowableItemBuilders, ItemNavHistory, Pane, ToolbarItemLocation, ViewId, Workspace, WorkspaceId, }; +use crate::{AutosaveSetting, WorkspaceSettings}; use anyhow::Result; use client::{proto, Client}; use gpui::{ @@ -10,7 +11,6 @@ use gpui::{ ViewContext, ViewHandle, WeakViewHandle, WindowContext, }; use project::{Project, ProjectEntryId, ProjectPath}; -use settings::{Autosave, Settings}; use smallvec::SmallVec; use std::{ any::{Any, TypeId}, @@ -450,8 +450,11 @@ impl ItemHandle for ViewHandle { } ItemEvent::Edit => { - if let Autosave::AfterDelay { milliseconds } = - cx.global::().autosave + let settings = settings::get_setting::(None, cx); + let debounce_delay = settings.git.gutter_debounce; + + if let AutosaveSetting::AfterDelay { milliseconds } = + settings.autosave { let delay = Duration::from_millis(milliseconds); let item = item.clone(); @@ -460,9 +463,6 @@ impl ItemHandle for ViewHandle { }); } - let settings = cx.global::(); - let debounce_delay = settings.git_overrides.gutter_debounce; - let item = item.clone(); if let Some(delay) = debounce_delay { @@ -500,7 +500,10 @@ impl ItemHandle for ViewHandle { })); cx.observe_focus(self, move |workspace, item, focused, cx| { - if !focused && cx.global::().autosave == Autosave::OnFocusChange { + if !focused + && settings::get_setting::(None, cx).autosave + == AutosaveSetting::OnFocusChange + { Pane::autosave_item(&item, workspace.project.clone(), cx) .detach_and_log_err(cx); } diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 96320a4baf..ca454d2c75 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -5,7 +5,8 @@ use crate::{ dock::{icon_for_dock_anchor, AnchorDockBottom, AnchorDockRight, Dock, ExpandDock}, item::WeakItemHandle, toolbar::Toolbar, - Item, NewFile, NewSearch, NewTerminal, Workspace, + AutosaveSetting, DockAnchor, Item, NewFile, NewSearch, NewTerminal, Workspace, + WorkspaceSettings, }; use anyhow::{anyhow, Result}; use collections::{HashMap, HashSet, VecDeque}; @@ -29,7 +30,7 @@ use gpui::{ }; use project::{Project, ProjectEntryId, ProjectPath}; use serde::Deserialize; -use settings::{Autosave, DockAnchor, Settings}; +use settings::Settings; use std::{any::Any, cell::RefCell, cmp, mem, path::Path, rc::Rc}; use theme::Theme; use util::ResultExt; @@ -1024,8 +1025,8 @@ impl Pane { } else if is_dirty && (can_save || is_singleton) { let will_autosave = cx.read(|cx| { matches!( - cx.global::().autosave, - Autosave::OnFocusChange | Autosave::OnWindowChange + settings::get_setting::(None, cx).autosave, + AutosaveSetting::OnFocusChange | AutosaveSetting::OnWindowChange ) && Self::can_autosave_item(&*item, cx) }); let should_save = if should_prompt_for_save && !will_autosave { @@ -2087,10 +2088,11 @@ mod tests { use crate::item::test::{TestItem, TestProjectItem}; use gpui::{executor::Deterministic, TestAppContext}; use project::FakeFs; + use settings::SettingsStore; #[gpui::test] async fn test_remove_active_empty(cx: &mut TestAppContext) { - Settings::test_async(cx); + init_test(cx); let fs = FakeFs::new(cx.background()); let project = Project::test(fs, None, cx).await; @@ -2104,7 +2106,7 @@ mod tests { #[gpui::test] async fn test_add_item_with_new_item(cx: &mut TestAppContext) { cx.foreground().forbid_parking(); - Settings::test_async(cx); + init_test(cx); let fs = FakeFs::new(cx.background()); let project = Project::test(fs, None, cx).await; @@ -2192,7 +2194,7 @@ mod tests { #[gpui::test] async fn test_add_item_with_existing_item(cx: &mut TestAppContext) { cx.foreground().forbid_parking(); - Settings::test_async(cx); + init_test(cx); let fs = FakeFs::new(cx.background()); let project = Project::test(fs, None, cx).await; @@ -2268,7 +2270,7 @@ mod tests { #[gpui::test] async fn test_add_item_with_same_project_entries(cx: &mut TestAppContext) { cx.foreground().forbid_parking(); - Settings::test_async(cx); + init_test(cx); let fs = FakeFs::new(cx.background()); let project = Project::test(fs, None, cx).await; @@ -2377,7 +2379,7 @@ mod tests { #[gpui::test] async fn test_remove_item_ordering(deterministic: Arc, cx: &mut TestAppContext) { - Settings::test_async(cx); + init_test(cx); let fs = FakeFs::new(cx.background()); let project = Project::test(fs, None, cx).await; @@ -2424,7 +2426,7 @@ mod tests { #[gpui::test] async fn test_close_inactive_items(deterministic: Arc, cx: &mut TestAppContext) { - Settings::test_async(cx); + init_test(cx); let fs = FakeFs::new(cx.background()); let project = Project::test(fs, None, cx).await; @@ -2443,7 +2445,7 @@ mod tests { #[gpui::test] async fn test_close_clean_items(deterministic: Arc, cx: &mut TestAppContext) { - Settings::test_async(cx); + init_test(cx); let fs = FakeFs::new(cx.background()); let project = Project::test(fs, None, cx).await; @@ -2470,7 +2472,7 @@ mod tests { deterministic: Arc, cx: &mut TestAppContext, ) { - Settings::test_async(cx); + init_test(cx); let fs = FakeFs::new(cx.background()); let project = Project::test(fs, None, cx).await; @@ -2492,7 +2494,7 @@ mod tests { deterministic: Arc, cx: &mut TestAppContext, ) { - Settings::test_async(cx); + init_test(cx); let fs = FakeFs::new(cx.background()); let project = Project::test(fs, None, cx).await; @@ -2511,7 +2513,7 @@ mod tests { #[gpui::test] async fn test_close_all_items(deterministic: Arc, cx: &mut TestAppContext) { - Settings::test_async(cx); + init_test(cx); let fs = FakeFs::new(cx.background()); let project = Project::test(fs, None, cx).await; @@ -2531,6 +2533,14 @@ mod tests { assert_item_labels(&pane, [], cx); } + fn init_test(cx: &mut TestAppContext) { + cx.update(|cx| { + cx.set_global(SettingsStore::test(cx)); + cx.set_global(settings::Settings::test(cx)); + crate::init_settings(cx); + }); + } + fn add_labeled_item( workspace: &ViewHandle, pane: &ViewHandle, diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index 55032b4bc1..edc7c617d0 100644 --- a/crates/workspace/src/pane_group.rs +++ b/crates/workspace/src/pane_group.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use crate::{AppState, FollowerStatesByLeader, Pane, Workspace}; +use crate::{AppState, FollowerStatesByLeader, Pane, Workspace, WorkspaceSettings}; use anyhow::{anyhow, Result}; use call::{ActiveCall, ParticipantLocation}; use gpui::{ @@ -11,7 +11,6 @@ use gpui::{ }; use project::Project; use serde::Deserialize; -use settings::Settings; use theme::Theme; #[derive(Clone, Debug, Eq, PartialEq)] @@ -380,7 +379,8 @@ impl PaneAxis { .with_children(self.members.iter().enumerate().map(|(ix, member)| { let mut flex = 1.0; if member.contains(active_pane) { - flex = cx.global::().active_pane_magnification; + flex = settings::get_setting::(None, cx) + .active_pane_magnification; } let mut member = member.render( diff --git a/crates/workspace/src/persistence.rs b/crates/workspace/src/persistence.rs index d5d79c3ddd..4ffae0d7e3 100644 --- a/crates/workspace/src/persistence.rs +++ b/crates/workspace/src/persistence.rs @@ -497,13 +497,10 @@ impl WorkspaceDb { #[cfg(test)] mod tests { - - use std::sync::Arc; - - use db::open_test_db; - use settings::DockAnchor; - use super::*; + use crate::DockAnchor; + use db::open_test_db; + use std::sync::Arc; #[gpui::test] async fn test_next_id_stability() { diff --git a/crates/workspace/src/persistence/model.rs b/crates/workspace/src/persistence/model.rs index a92c369e7a..ac1bcf6eed 100644 --- a/crates/workspace/src/persistence/model.rs +++ b/crates/workspace/src/persistence/model.rs @@ -1,5 +1,6 @@ use crate::{ - dock::DockPosition, ItemDeserializers, Member, Pane, PaneAxis, Workspace, WorkspaceId, + dock::DockPosition, DockAnchor, ItemDeserializers, Member, Pane, PaneAxis, Workspace, + WorkspaceId, }; use anyhow::{anyhow, Context, Result}; use async_recursion::async_recursion; @@ -11,7 +12,6 @@ use gpui::{ platform::WindowBounds, AsyncAppContext, Axis, ModelHandle, Task, ViewHandle, WeakViewHandle, }; use project::Project; -use settings::DockAnchor; use std::{ path::{Path, PathBuf}, sync::Arc, @@ -305,10 +305,9 @@ impl Column for DockPosition { #[cfg(test)] mod tests { - use db::sqlez::connection::Connection; - use settings::DockAnchor; - use super::WorkspaceLocation; + use crate::DockAnchor; + use db::sqlez::connection::Connection; #[test] fn test_workspace_round_trips() { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 768142d729..dcc4d017dd 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -13,6 +13,7 @@ pub mod shared_screen; pub mod sidebar; mod status_bar; mod toolbar; +mod workspace_settings; use anyhow::{anyhow, Context, Result}; use assets::Assets; @@ -75,7 +76,7 @@ pub use persistence::{ use postage::prelude::Stream; use project::{Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId}; use serde::Deserialize; -use settings::{Autosave, DockAnchor, Settings}; +use settings::Settings; use shared_screen::SharedScreen; use sidebar::{Sidebar, SidebarButtons, SidebarSide, ToggleSidebarItem}; use status_bar::StatusBar; @@ -83,6 +84,7 @@ pub use status_bar::StatusItemView; use theme::{Theme, ThemeRegistry}; pub use toolbar::{ToolbarItemLocation, ToolbarItemView}; use util::{paths, ResultExt}; +pub use workspace_settings::{AutosaveSetting, DockAnchor, GitGutterSetting, WorkspaceSettings}; lazy_static! { static ref ZED_WINDOW_SIZE: Option = env::var("ZED_WINDOW_SIZE") @@ -183,7 +185,12 @@ pub type WorkspaceId = i64; impl_actions!(workspace, [ActivatePane]); +pub fn init_settings(cx: &mut AppContext) { + settings::register_setting::(cx); +} + pub fn init(app_state: Arc, cx: &mut AppContext) { + init_settings(cx); pane::init(cx); dock::init(cx); notifications::init(cx); @@ -384,6 +391,7 @@ impl AppState { let themes = ThemeRegistry::new((), cx.font_cache().clone()); client::init(&client, cx); + crate::init_settings(cx); Arc::new(Self { client, @@ -672,7 +680,9 @@ impl Workspace { Self::load_from_serialized_workspace(weak_handle, serialized_workspace, cx) }); } else if project.read(cx).is_local() { - if cx.global::().default_dock_anchor != DockAnchor::Expanded { + if settings::get_setting::(None, cx).default_dock_anchor + != DockAnchor::Expanded + { Dock::show(&mut this, false, cx); } } @@ -2406,8 +2416,8 @@ impl Workspace { item.workspace_deactivated(cx); } if matches!( - cx.global::().autosave, - Autosave::OnWindowChange | Autosave::OnFocusChange + settings::get_setting::(None, cx).autosave, + AutosaveSetting::OnWindowChange | AutosaveSetting::OnFocusChange ) { for item in pane.items() { Pane::autosave_item(item.as_ref(), self.project.clone(), cx) @@ -3067,7 +3077,7 @@ pub fn join_remote_project( } pub fn restart(_: &Restart, cx: &mut AppContext) { - let should_confirm = cx.global::().confirm_quit; + let should_confirm = settings::get_setting::(None, cx).confirm_quit; cx.spawn(|mut cx| async move { let mut workspaces = cx .window_ids() @@ -3128,20 +3138,18 @@ fn parse_pixel_position_env_var(value: &str) -> Option { #[cfg(test)] mod tests { - use std::{cell::RefCell, rc::Rc}; - - use crate::item::test::{TestItem, TestItemEvent, TestProjectItem}; - use super::*; + use crate::item::test::{TestItem, TestItemEvent, TestProjectItem}; use fs::FakeFs; use gpui::{executor::Deterministic, TestAppContext}; use project::{Project, ProjectEntryId}; use serde_json::json; + use settings::SettingsStore; + use std::{cell::RefCell, rc::Rc}; #[gpui::test] async fn test_tab_disambiguation(cx: &mut TestAppContext) { - cx.foreground().forbid_parking(); - Settings::test_async(cx); + init_test(cx); let fs = FakeFs::new(cx.background()); let project = Project::test(fs, [], cx).await; @@ -3189,8 +3197,8 @@ mod tests { #[gpui::test] async fn test_tracking_active_path(cx: &mut TestAppContext) { - cx.foreground().forbid_parking(); - Settings::test_async(cx); + init_test(cx); + let fs = FakeFs::new(cx.background()); fs.insert_tree( "/root1", @@ -3293,8 +3301,8 @@ mod tests { #[gpui::test] async fn test_close_window(cx: &mut TestAppContext) { - cx.foreground().forbid_parking(); - Settings::test_async(cx); + init_test(cx); + let fs = FakeFs::new(cx.background()); fs.insert_tree("/root", json!({ "one": "" })).await; @@ -3329,8 +3337,8 @@ mod tests { #[gpui::test] async fn test_close_pane_items(cx: &mut TestAppContext) { - cx.foreground().forbid_parking(); - Settings::test_async(cx); + init_test(cx); + let fs = FakeFs::new(cx.background()); let project = Project::test(fs, None, cx).await; @@ -3436,8 +3444,8 @@ mod tests { #[gpui::test] async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) { - cx.foreground().forbid_parking(); - Settings::test_async(cx); + init_test(cx); + let fs = FakeFs::new(cx.background()); let project = Project::test(fs, [], cx).await; @@ -3542,9 +3550,8 @@ mod tests { #[gpui::test] async fn test_autosave(deterministic: Arc, cx: &mut gpui::TestAppContext) { - deterministic.forbid_parking(); + init_test(cx); - Settings::test_async(cx); let fs = FakeFs::new(cx.background()); let project = Project::test(fs, [], cx).await; @@ -3560,8 +3567,10 @@ mod tests { // Autosave on window change. item.update(cx, |item, cx| { - cx.update_global(|settings: &mut Settings, _| { - settings.autosave = Autosave::OnWindowChange; + cx.update_global(|settings: &mut SettingsStore, cx| { + settings.update_user_settings::(cx, |settings| { + settings.autosave = Some(AutosaveSetting::OnWindowChange); + }) }); item.is_dirty = true; }); @@ -3574,8 +3583,10 @@ mod tests { // Autosave on focus change. item.update(cx, |item, cx| { cx.focus_self(); - cx.update_global(|settings: &mut Settings, _| { - settings.autosave = Autosave::OnFocusChange; + cx.update_global(|settings: &mut SettingsStore, cx| { + settings.update_user_settings::(cx, |settings| { + settings.autosave = Some(AutosaveSetting::OnFocusChange); + }) }); item.is_dirty = true; }); @@ -3598,8 +3609,10 @@ mod tests { // Autosave after delay. item.update(cx, |item, cx| { - cx.update_global(|settings: &mut Settings, _| { - settings.autosave = Autosave::AfterDelay { milliseconds: 500 }; + cx.update_global(|settings: &mut SettingsStore, cx| { + settings.update_user_settings::(cx, |settings| { + settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 }); + }) }); item.is_dirty = true; cx.emit(TestItemEvent::Edit); @@ -3615,8 +3628,10 @@ mod tests { // Autosave on focus change, ensuring closing the tab counts as such. item.update(cx, |item, cx| { - cx.update_global(|settings: &mut Settings, _| { - settings.autosave = Autosave::OnFocusChange; + cx.update_global(|settings: &mut SettingsStore, cx| { + settings.update_user_settings::(cx, |settings| { + settings.autosave = Some(AutosaveSetting::OnFocusChange); + }) }); item.is_dirty = true; }); @@ -3656,12 +3671,9 @@ mod tests { } #[gpui::test] - async fn test_pane_navigation( - deterministic: Arc, - cx: &mut gpui::TestAppContext, - ) { - deterministic.forbid_parking(); - Settings::test_async(cx); + async fn test_pane_navigation(cx: &mut gpui::TestAppContext) { + init_test(cx); + let fs = FakeFs::new(cx.background()); let project = Project::test(fs, [], cx).await; @@ -3713,4 +3725,14 @@ mod tests { assert!(pane.can_navigate_forward()); }); } + + pub fn init_test(cx: &mut TestAppContext) { + cx.foreground().forbid_parking(); + cx.update(|cx| { + cx.set_global(SettingsStore::test(cx)); + cx.set_global(Settings::test(cx)); + language::init(cx); + crate::init_settings(cx); + }); + } } diff --git a/crates/workspace/src/workspace_settings.rs b/crates/workspace/src/workspace_settings.rs new file mode 100644 index 0000000000..41e4796491 --- /dev/null +++ b/crates/workspace/src/workspace_settings.rs @@ -0,0 +1,103 @@ +use anyhow::bail; +use db::sqlez::{ + bindable::{Bind, Column, StaticColumnCount}, + statement::Statement, +}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use settings::Setting; + +#[derive(Deserialize)] +pub struct WorkspaceSettings { + pub active_pane_magnification: f32, + pub confirm_quit: bool, + pub show_call_status_icon: bool, + pub autosave: AutosaveSetting, + pub default_dock_anchor: DockAnchor, + pub git: GitSettings, +} + +#[derive(Clone, Serialize, Deserialize, JsonSchema)] +pub struct WorkspaceSettingsContent { + pub active_pane_magnification: Option, + pub confirm_quit: Option, + pub show_call_status_icon: Option, + pub autosave: Option, + pub default_dock_anchor: Option, + pub git: Option, +} + +#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum AutosaveSetting { + Off, + AfterDelay { milliseconds: u64 }, + OnFocusChange, + OnWindowChange, +} + +#[derive(PartialEq, Eq, Debug, Default, Copy, Clone, Hash, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum DockAnchor { + #[default] + Bottom, + Right, + Expanded, +} + +#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] +pub struct GitSettings { + pub git_gutter: Option, + pub gutter_debounce: Option, +} + +#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum GitGutterSetting { + #[default] + TrackedFiles, + Hide, +} + +impl StaticColumnCount for DockAnchor {} + +impl Bind for DockAnchor { + fn bind(&self, statement: &Statement, start_index: i32) -> anyhow::Result { + match self { + DockAnchor::Bottom => "Bottom", + DockAnchor::Right => "Right", + DockAnchor::Expanded => "Expanded", + } + .bind(statement, start_index) + } +} + +impl Column for DockAnchor { + fn column(statement: &mut Statement, start_index: i32) -> anyhow::Result<(Self, i32)> { + String::column(statement, start_index).and_then(|(anchor_text, next_index)| { + Ok(( + match anchor_text.as_ref() { + "Bottom" => DockAnchor::Bottom, + "Right" => DockAnchor::Right, + "Expanded" => DockAnchor::Expanded, + _ => bail!("Stored dock anchor is incorrect"), + }, + next_index, + )) + }) + } +} + +impl Setting for WorkspaceSettings { + const KEY: Option<&'static str> = None; + + type FileContent = WorkspaceSettingsContent; + + fn load( + default_value: &Self::FileContent, + user_values: &[&Self::FileContent], + _: &gpui::AppContext, + ) -> anyhow::Result { + Self::load_via_json_merge(default_value, user_values) + } +} diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index b8ffab4a3b..923a3d6d9c 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -37,7 +37,7 @@ use uuid::Uuid; pub use workspace; use workspace::{ create_and_open_local_file, open_new, sidebar::SidebarSide, AppState, NewFile, NewWindow, - Workspace, + Workspace, WorkspaceSettings, }; #[derive(Deserialize, Clone, PartialEq)] @@ -367,7 +367,7 @@ pub fn build_window_options( } fn quit(_: &Quit, cx: &mut gpui::AppContext) { - let should_confirm = cx.global::().confirm_quit; + let should_confirm = settings::get_setting::(None, cx).confirm_quit; cx.spawn(|mut cx| async move { let mut workspaces = cx .window_ids()