diff --git a/Cargo.lock b/Cargo.lock index ed17474f832d77f4fa5dd3471ecf1b5b098d4c77..2310b2aa3dcc7c0ef08176793103afc6c51d0447 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4618,6 +4618,7 @@ dependencies = [ "anyhow", "gpui", "indoc", + "inventory", "log", "paths", "release_channel", @@ -4626,6 +4627,7 @@ dependencies = [ "sqlez_macros", "tempfile", "util", + "uuid", "zed_env_vars", ] diff --git a/crates/agent_ui/src/agent_panel.rs b/crates/agent_ui/src/agent_panel.rs index 41f2092f251c95ab2964a24557ffd24753ddfdb5..f1ad3a21c7510776e444c3c28616e7a89abfa56c 100644 --- a/crates/agent_ui/src/agent_panel.rs +++ b/crates/agent_ui/src/agent_panel.rs @@ -14,7 +14,7 @@ use agent::{ContextServerRegistry, SharedThread, ThreadStore}; use agent_client_protocol as acp; use agent_servers::AgentServer; use collections::HashSet; -use db::kvp::{Dismissable, KEY_VALUE_STORE}; +use db::kvp::{Dismissable, KeyValueStore}; use itertools::Itertools; use project::AgentId; use serde::{Deserialize, Serialize}; @@ -95,8 +95,11 @@ const AGENT_PANEL_KEY: &str = "agent_panel"; const RECENTLY_UPDATED_MENU_LIMIT: usize = 6; const DEFAULT_THREAD_TITLE: &str = "New Thread"; -fn read_serialized_panel(workspace_id: workspace::WorkspaceId) -> Option { - let scope = KEY_VALUE_STORE.scoped(AGENT_PANEL_KEY); +fn read_serialized_panel( + workspace_id: workspace::WorkspaceId, + kvp: &KeyValueStore, +) -> Option { + let scope = kvp.scoped(AGENT_PANEL_KEY); let key = i64::from(workspace_id).to_string(); scope .read(&key) @@ -108,8 +111,9 @@ fn read_serialized_panel(workspace_id: workspace::WorkspaceId) -> Option Result<()> { - let scope = KEY_VALUE_STORE.scoped(AGENT_PANEL_KEY); + let scope = kvp.scoped(AGENT_PANEL_KEY); let key = i64::from(workspace_id).to_string(); scope.write(key, serde_json::to_string(&panel)?).await?; Ok(()) @@ -117,9 +121,8 @@ async fn save_serialized_panel( /// Migration: reads the original single-panel format stored under the /// `"agent_panel"` KVP key before per-workspace keying was introduced. -fn read_legacy_serialized_panel() -> Option { - KEY_VALUE_STORE - .read_kvp(AGENT_PANEL_KEY) +fn read_legacy_serialized_panel(kvp: &KeyValueStore) -> Option { + kvp.read_kvp(AGENT_PANEL_KEY) .log_err() .flatten() .and_then(|json| serde_json::from_str::(&json).log_err()) @@ -782,6 +785,7 @@ impl AgentPanel { } }); + let kvp = KeyValueStore::global(cx); self.pending_serialization = Some(cx.background_spawn(async move { save_serialized_panel( workspace_id, @@ -791,6 +795,7 @@ impl AgentPanel { last_active_thread, start_thread_in, }, + kvp, ) .await?; anyhow::Ok(()) @@ -803,6 +808,7 @@ impl AgentPanel { mut cx: AsyncWindowContext, ) -> Task>> { let prompt_store = cx.update(|_window, cx| PromptStore::global(cx)); + let kvp = cx.update(|_window, cx| KeyValueStore::global(cx)).ok(); cx.spawn(async move |cx| { let prompt_store = match prompt_store { Ok(prompt_store) => prompt_store.await.ok(), @@ -815,9 +821,11 @@ impl AgentPanel { let serialized_panel = cx .background_spawn(async move { - workspace_id - .and_then(read_serialized_panel) - .or_else(read_legacy_serialized_panel) + kvp.and_then(|kvp| { + workspace_id + .and_then(|id| read_serialized_panel(id, &kvp)) + .or_else(|| read_legacy_serialized_panel(&kvp)) + }) }) .await; @@ -1089,7 +1097,7 @@ impl AgentPanel { _worktree_creation_task: None, show_trust_workspace_message: false, last_configuration_error_telemetry: None, - on_boarding_upsell_dismissed: AtomicBool::new(OnboardingUpsell::dismissed()), + on_boarding_upsell_dismissed: AtomicBool::new(OnboardingUpsell::dismissed(cx)), _active_view_observation: None, }; @@ -1308,16 +1316,17 @@ impl AgentPanel { } let thread_store = self.thread_store.clone(); + let kvp = KeyValueStore::global(cx); if let Some(agent) = agent_choice { cx.background_spawn({ let agent = agent.clone(); + let kvp = kvp; async move { if let Some(serialized) = serde_json::to_string(&LastUsedExternalAgent { agent }).log_err() { - KEY_VALUE_STORE - .write_kvp(LAST_USED_EXTERNAL_AGENT_KEY.to_string(), serialized) + kvp.write_kvp(LAST_USED_EXTERNAL_AGENT_KEY.to_string(), serialized) .await .log_err(); } @@ -1344,17 +1353,15 @@ impl AgentPanel { let ext_agent = if is_via_collab { Agent::NativeAgent } else { - cx.background_spawn(async move { - KEY_VALUE_STORE.read_kvp(LAST_USED_EXTERNAL_AGENT_KEY) - }) - .await - .log_err() - .flatten() - .and_then(|value| { - serde_json::from_str::(&value).log_err() - }) - .map(|agent| agent.agent) - .unwrap_or(Agent::NativeAgent) + cx.background_spawn(async move { kvp.read_kvp(LAST_USED_EXTERNAL_AGENT_KEY) }) + .await + .log_err() + .flatten() + .and_then(|value| { + serde_json::from_str::(&value).log_err() + }) + .map(|agent| agent.agent) + .unwrap_or(Agent::NativeAgent) }; let server = ext_agent.server(fs, thread_store); @@ -4139,7 +4146,7 @@ impl AgentPanel { } fn should_render_trial_end_upsell(&self, cx: &mut Context) -> bool { - if TrialEndUpsell::dismissed() { + if TrialEndUpsell::dismissed(cx) { return false; } diff --git a/crates/agent_ui/src/thread_metadata_store.rs b/crates/agent_ui/src/thread_metadata_store.rs index 7a4d8b4bd193acca937e09fae5003019de50a682..d25a4e147e737e32012a17b1c419cf348307b4b2 100644 --- a/crates/agent_ui/src/thread_metadata_store.rs +++ b/crates/agent_ui/src/thread_metadata_store.rs @@ -95,7 +95,7 @@ impl ThreadMetadataStore { return; } - let db = THREAD_METADATA_DB.clone(); + let db = ThreadMetadataDb::global(cx); let thread_store = cx.new(|cx| Self::new(db, cx)); cx.set_global(GlobalThreadMetadataStore(thread_store)); } @@ -251,7 +251,6 @@ impl ThreadMetadataStore { impl Global for ThreadMetadataStore {} -#[derive(Clone)] struct ThreadMetadataDb(ThreadSafeConnection); impl Domain for ThreadMetadataDb { @@ -270,7 +269,7 @@ impl Domain for ThreadMetadataDb { )]; } -db::static_connection!(THREAD_METADATA_DB, ThreadMetadataDb, []); +db::static_connection!(ThreadMetadataDb, []); impl ThreadMetadataDb { /// List all sidebar thread metadata, ordered by updated_at descending. diff --git a/crates/auto_update/src/auto_update.rs b/crates/auto_update/src/auto_update.rs index a464ced821b263a1e48d19fe90f85519ae98b408..b5b01807a8a0cd75ea46881b379414002bc26c04 100644 --- a/crates/auto_update/src/auto_update.rs +++ b/crates/auto_update/src/auto_update.rs @@ -1,6 +1,6 @@ use anyhow::{Context as _, Result}; use client::Client; -use db::kvp::KEY_VALUE_STORE; +use db::kvp::KeyValueStore; use futures_lite::StreamExt; use gpui::{ App, AppContext as _, AsyncApp, BackgroundExecutor, Context, Entity, Global, Task, Window, @@ -770,17 +770,16 @@ impl AutoUpdater { should_show: bool, cx: &App, ) -> Task> { + let kvp = KeyValueStore::global(cx); cx.background_spawn(async move { if should_show { - KEY_VALUE_STORE - .write_kvp( - SHOULD_SHOW_UPDATE_NOTIFICATION_KEY.to_string(), - "".to_string(), - ) - .await?; + kvp.write_kvp( + SHOULD_SHOW_UPDATE_NOTIFICATION_KEY.to_string(), + "".to_string(), + ) + .await?; } else { - KEY_VALUE_STORE - .delete_kvp(SHOULD_SHOW_UPDATE_NOTIFICATION_KEY.to_string()) + kvp.delete_kvp(SHOULD_SHOW_UPDATE_NOTIFICATION_KEY.to_string()) .await?; } Ok(()) @@ -788,10 +787,9 @@ impl AutoUpdater { } pub fn should_show_update_notification(&self, cx: &App) -> Task> { + let kvp = KeyValueStore::global(cx); cx.background_spawn(async move { - Ok(KEY_VALUE_STORE - .read_kvp(SHOULD_SHOW_UPDATE_NOTIFICATION_KEY)? - .is_some()) + Ok(kvp.read_kvp(SHOULD_SHOW_UPDATE_NOTIFICATION_KEY)?.is_some()) }) } } diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index 9aeeeeb4233a7e5486ef49da8b0aeaaddd846d17..7541e5871bf0699f93e842dbfed610ee59d1d13b 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -9,7 +9,7 @@ use channel::{Channel, ChannelEvent, ChannelStore}; use client::{ChannelId, Client, Contact, User, UserStore}; use collections::{HashMap, HashSet}; use contact_finder::ContactFinder; -use db::kvp::KEY_VALUE_STORE; +use db::kvp::KeyValueStore; use editor::{Editor, EditorElement, EditorStyle}; use fuzzy::{StringMatch, StringMatchCandidate, match_strings}; use gpui::{ @@ -429,16 +429,17 @@ impl CollabPanel { .ok() .flatten() { - Some(serialization_key) => cx - .background_spawn(async move { KEY_VALUE_STORE.read_kvp(&serialization_key) }) - .await - .context("reading collaboration panel from key value store") - .log_err() - .flatten() - .map(|panel| serde_json::from_str::(&panel)) - .transpose() - .log_err() - .flatten(), + Some(serialization_key) => { + let kvp = cx.update(|_, cx| KeyValueStore::global(cx))?; + kvp.read_kvp(&serialization_key) + .context("reading collaboration panel from key value store") + .log_err() + .flatten() + .map(|panel| serde_json::from_str::(&panel)) + .transpose() + .log_err() + .flatten() + } None => None, }; @@ -479,19 +480,19 @@ impl CollabPanel { }; let width = self.width; let collapsed_channels = self.collapsed_channels.clone(); + let kvp = KeyValueStore::global(cx); self.pending_serialization = cx.background_spawn( async move { - KEY_VALUE_STORE - .write_kvp( - serialization_key, - serde_json::to_string(&SerializedCollabPanel { - width, - collapsed_channels: Some( - collapsed_channels.iter().map(|cid| cid.0).collect(), - ), - })?, - ) - .await?; + kvp.write_kvp( + serialization_key, + serde_json::to_string(&SerializedCollabPanel { + width, + collapsed_channels: Some( + collapsed_channels.iter().map(|cid| cid.0).collect(), + ), + })?, + ) + .await?; anyhow::Ok(()) } .log_err(), diff --git a/crates/collab_ui/src/notification_panel.rs b/crates/collab_ui/src/notification_panel.rs index 308d521832d5f2964a46f32e88329bd15d5358ee..4374349b15f1c8e6404c61648fed720550e31a3e 100644 --- a/crates/collab_ui/src/notification_panel.rs +++ b/crates/collab_ui/src/notification_panel.rs @@ -3,7 +3,7 @@ use anyhow::Result; use channel::ChannelStore; use client::{ChannelId, Client, Notification, User, UserStore}; use collections::HashMap; -use db::kvp::KEY_VALUE_STORE; +use db::kvp::KeyValueStore; use futures::StreamExt; use gpui::{ AnyElement, App, AsyncWindowContext, ClickEvent, Context, DismissEvent, Element, Entity, @@ -186,16 +186,13 @@ impl NotificationPanel { cx: AsyncWindowContext, ) -> Task>> { cx.spawn(async move |cx| { - let serialized_panel = if let Some(panel) = cx - .background_spawn(async move { KEY_VALUE_STORE.read_kvp(NOTIFICATION_PANEL_KEY) }) - .await - .log_err() - .flatten() - { - Some(serde_json::from_str::(&panel)?) - } else { - None - }; + let kvp = cx.update(|_, cx| KeyValueStore::global(cx))?; + let serialized_panel = + if let Some(panel) = kvp.read_kvp(NOTIFICATION_PANEL_KEY).log_err().flatten() { + Some(serde_json::from_str::(&panel)?) + } else { + None + }; workspace.update_in(cx, |workspace, window, cx| { let panel = Self::new(workspace, window, cx); @@ -212,14 +209,14 @@ impl NotificationPanel { fn serialize(&mut self, cx: &mut Context) { let width = self.width; + let kvp = KeyValueStore::global(cx); self.pending_serialization = cx.background_spawn( async move { - KEY_VALUE_STORE - .write_kvp( - NOTIFICATION_PANEL_KEY.into(), - serde_json::to_string(&SerializedNotificationPanel { width })?, - ) - .await?; + kvp.write_kvp( + NOTIFICATION_PANEL_KEY.into(), + serde_json::to_string(&SerializedNotificationPanel { width })?, + ) + .await?; anyhow::Ok(()) } .log_err(), diff --git a/crates/command_palette/src/command_palette.rs b/crates/command_palette/src/command_palette.rs index d13360a7c5403d997cfb2363f33cfe3b257dcef1..a1a7c33f19549d15075cb9d8f243703d6b1e4cb1 100644 --- a/crates/command_palette/src/command_palette.rs +++ b/crates/command_palette/src/command_palette.rs @@ -18,7 +18,7 @@ use gpui::{ Action, App, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, ParentElement, Render, Styled, Task, WeakEntity, Window, }; -use persistence::COMMAND_PALETTE_HISTORY; +use persistence::CommandPaletteDB; use picker::Direction; use picker::{Picker, PickerDelegate}; use postage::{sink::Sink, stream::Stream}; @@ -180,9 +180,9 @@ struct QueryHistory { } impl QueryHistory { - fn history(&mut self) -> &mut VecDeque { + fn history(&mut self, cx: &App) -> &mut VecDeque { self.history.get_or_insert_with(|| { - COMMAND_PALETTE_HISTORY + CommandPaletteDB::global(cx) .list_recent_queries() .unwrap_or_default() .into_iter() @@ -190,18 +190,18 @@ impl QueryHistory { }) } - fn add(&mut self, query: String) { - if let Some(pos) = self.history().iter().position(|h| h == &query) { - self.history().remove(pos); + fn add(&mut self, query: String, cx: &App) { + if let Some(pos) = self.history(cx).iter().position(|h| h == &query) { + self.history(cx).remove(pos); } - self.history().push_back(query); + self.history(cx).push_back(query); self.cursor = None; self.prefix = None; } - fn validate_cursor(&mut self, current_query: &str) -> Option { + fn validate_cursor(&mut self, current_query: &str, cx: &App) -> Option { if let Some(pos) = self.cursor { - if self.history().get(pos).map(|s| s.as_str()) != Some(current_query) { + if self.history(cx).get(pos).map(|s| s.as_str()) != Some(current_query) { self.cursor = None; self.prefix = None; } @@ -209,39 +209,39 @@ impl QueryHistory { self.cursor } - fn previous(&mut self, current_query: &str) -> Option<&str> { - if self.validate_cursor(current_query).is_none() { + fn previous(&mut self, current_query: &str, cx: &App) -> Option<&str> { + if self.validate_cursor(current_query, cx).is_none() { self.prefix = Some(current_query.to_string()); } let prefix = self.prefix.clone().unwrap_or_default(); - let start_index = self.cursor.unwrap_or(self.history().len()); + let start_index = self.cursor.unwrap_or(self.history(cx).len()); for i in (0..start_index).rev() { if self - .history() + .history(cx) .get(i) .is_some_and(|e| e.starts_with(&prefix)) { self.cursor = Some(i); - return self.history().get(i).map(|s| s.as_str()); + return self.history(cx).get(i).map(|s| s.as_str()); } } None } - fn next(&mut self, current_query: &str) -> Option<&str> { - let selected = self.validate_cursor(current_query)?; + fn next(&mut self, current_query: &str, cx: &App) -> Option<&str> { + let selected = self.validate_cursor(current_query, cx)?; let prefix = self.prefix.clone().unwrap_or_default(); - for i in (selected + 1)..self.history().len() { + for i in (selected + 1)..self.history(cx).len() { if self - .history() + .history(cx) .get(i) .is_some_and(|e| e.starts_with(&prefix)) { self.cursor = Some(i); - return self.history().get(i).map(|s| s.as_str()); + return self.history(cx).get(i).map(|s| s.as_str()); } } None @@ -338,8 +338,8 @@ impl CommandPaletteDelegate { /// Hit count for each command in the palette. /// We only account for commands triggered directly via command palette and not by e.g. keystrokes because /// if a user already knows a keystroke for a command, they are unlikely to use a command palette to look for it. - fn hit_counts(&self) -> HashMap { - if let Ok(commands) = COMMAND_PALETTE_HISTORY.list_commands_used() { + fn hit_counts(&self, cx: &App) -> HashMap { + if let Ok(commands) = CommandPaletteDB::global(cx).list_commands_used() { commands .into_iter() .map(|command| (command.command_name, command.invocations)) @@ -378,21 +378,25 @@ impl PickerDelegate for CommandPaletteDelegate { direction: Direction, query: &str, _window: &mut Window, - _cx: &mut App, + cx: &mut App, ) -> Option { match direction { Direction::Up => { let should_use_history = self.selected_ix == 0 || self.query_history.is_navigating(); if should_use_history { - if let Some(query) = self.query_history.previous(query).map(|s| s.to_string()) { + if let Some(query) = self + .query_history + .previous(query, cx) + .map(|s| s.to_string()) + { return Some(query); } } } Direction::Down => { if self.query_history.is_navigating() { - if let Some(query) = self.query_history.next(query).map(|s| s.to_string()) { + if let Some(query) = self.query_history.next(query, cx).map(|s| s.to_string()) { return Some(query); } else { let prefix = self.query_history.prefix.take().unwrap_or_default(); @@ -444,7 +448,7 @@ impl PickerDelegate for CommandPaletteDelegate { let task = cx.background_spawn({ let mut commands = self.all_commands.clone(); - let hit_counts = self.hit_counts(); + let hit_counts = self.hit_counts(cx); let executor = cx.background_executor().clone(); let query = normalize_action_query(query_str); let query_for_link = query_str.to_string(); @@ -566,7 +570,7 @@ impl PickerDelegate for CommandPaletteDelegate { } if !self.latest_query.is_empty() { - self.query_history.add(self.latest_query.clone()); + self.query_history.add(self.latest_query.clone(), cx); self.query_history.reset_cursor(); } @@ -581,9 +585,9 @@ impl PickerDelegate for CommandPaletteDelegate { self.commands.clear(); let command_name = command.name.clone(); let latest_query = self.latest_query.clone(); + let db = CommandPaletteDB::global(cx); cx.background_spawn(async move { - COMMAND_PALETTE_HISTORY - .write_command_invocation(command_name, latest_query) + db.write_command_invocation(command_name, latest_query) .await }) .detach_and_log_err(cx); @@ -771,11 +775,9 @@ mod tests { #[gpui::test] async fn test_command_palette(cx: &mut TestAppContext) { - persistence::COMMAND_PALETTE_HISTORY - .clear_all() - .await - .unwrap(); let app_state = init_test(cx); + let db = cx.update(|cx| persistence::CommandPaletteDB::global(cx)); + db.clear_all().await.unwrap(); let project = Project::test(app_state.fs.clone(), [], cx).await; let (multi_workspace, cx) = cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx)); diff --git a/crates/command_palette/src/persistence.rs b/crates/command_palette/src/persistence.rs index 9a69f32f85fca4ae8b59b17144f7bb5c85993c2c..4e284e0e809fa4371ab3552aff1263b2c7823a46 100644 --- a/crates/command_palette/src/persistence.rs +++ b/crates/command_palette/src/persistence.rs @@ -69,7 +69,7 @@ impl Domain for CommandPaletteDB { )]; } -db::static_connection!(COMMAND_PALETTE_HISTORY, CommandPaletteDB, []); +db::static_connection!(CommandPaletteDB, []); impl CommandPaletteDB { pub async fn write_command_invocation( diff --git a/crates/component_preview/examples/component_preview.rs b/crates/component_preview/examples/component_preview.rs index 3859ff6803c79a176bc733af156e1304bbc5f61e..99222a9ffd47222eb11375b2277bd7ee4e6c7a94 100644 --- a/crates/component_preview/examples/component_preview.rs +++ b/crates/component_preview/examples/component_preview.rs @@ -48,7 +48,10 @@ fn main() { let user_store = cx.new(|cx| UserStore::new(client.clone(), cx)); let workspace_store = cx.new(|cx| WorkspaceStore::new(client.clone(), cx)); let session_id = uuid::Uuid::new_v4().to_string(); - let session = cx.foreground_executor().block_on(Session::new(session_id)); + let kvp = db::kvp::KeyValueStore::global(cx); + let session = cx + .foreground_executor() + .block_on(Session::new(session_id, kvp)); let session = cx.new(|cx| AppSession::new(session, cx)); let node_runtime = NodeRuntime::unavailable(); diff --git a/crates/component_preview/src/component_preview.rs b/crates/component_preview/src/component_preview.rs index 640b243b73e4cf6042bb7c81a8869b0ffb64230b..17a598aa78a410b43600afa9d1e09ed06e167647 100644 --- a/crates/component_preview/src/component_preview.rs +++ b/crates/component_preview/src/component_preview.rs @@ -9,7 +9,7 @@ use gpui::{ use gpui::{ListState, ScrollHandle, ScrollStrategy, UniformListScrollHandle}; use language::LanguageRegistry; use notifications::status_toast::{StatusToast, ToastIcon}; -use persistence::COMPONENT_PREVIEW_DB; +use persistence::ComponentPreviewDb; use project::Project; use std::{iter::Iterator, ops::Range, sync::Arc}; use ui::{ButtonLike, Divider, HighlightedLabel, ListItem, ListSubHeader, Tooltip, prelude::*}; @@ -784,7 +784,7 @@ impl SerializableItem for ComponentPreview { cx: &mut App, ) -> Task>> { let deserialized_active_page = - match COMPONENT_PREVIEW_DB.get_active_page(item_id, workspace_id) { + match ComponentPreviewDb::global(cx).get_active_page(item_id, workspace_id) { Ok(page) => { if let Some(page) = page { ActivePageId(page) @@ -845,7 +845,7 @@ impl SerializableItem for ComponentPreview { alive_items, workspace_id, "component_previews", - &COMPONENT_PREVIEW_DB, + &ComponentPreviewDb::global(cx), cx, ) } @@ -860,9 +860,9 @@ impl SerializableItem for ComponentPreview { ) -> Option>> { let active_page = self.active_page_id(cx); let workspace_id = self.workspace_id?; + let db = ComponentPreviewDb::global(cx); Some(cx.background_spawn(async move { - COMPONENT_PREVIEW_DB - .save_active_page(item_id, workspace_id, active_page.0) + db.save_active_page(item_id, workspace_id, active_page.0) .await })) } diff --git a/crates/component_preview/src/persistence.rs b/crates/component_preview/src/persistence.rs index c37a4cc3899fd2da4834070f0a987650079ad515..13b042e63663a9b0e6ce0d4fc722455af8659c4b 100644 --- a/crates/component_preview/src/persistence.rs +++ b/crates/component_preview/src/persistence.rs @@ -23,7 +23,7 @@ impl Domain for ComponentPreviewDb { )]; } -db::static_connection!(COMPONENT_PREVIEW_DB, ComponentPreviewDb, [WorkspaceDb]); +db::static_connection!(ComponentPreviewDb, [WorkspaceDb]); impl ComponentPreviewDb { pub async fn save_active_page( diff --git a/crates/db/Cargo.toml b/crates/db/Cargo.toml index 3bcfefec0315ad2d94f44946c754501f43999264..2fc790181a86392ef545818ce04ca0efcf87713c 100644 --- a/crates/db/Cargo.toml +++ b/crates/db/Cargo.toml @@ -19,6 +19,7 @@ test-support = [] anyhow.workspace = true gpui.workspace = true indoc.workspace = true +inventory.workspace = true log.workspace = true paths.workspace = true release_channel.workspace = true @@ -26,6 +27,7 @@ smol.workspace = true sqlez.workspace = true sqlez_macros.workspace = true util.workspace = true +uuid.workspace = true zed_env_vars.workspace = true [dev-dependencies] diff --git a/crates/db/src/db.rs b/crates/db/src/db.rs index 36f0365af97ed05859d0c1116065adb004dec2d9..1ed6aa080757cf99dd90a685489bdf3dd6e94e0b 100644 --- a/crates/db/src/db.rs +++ b/crates/db/src/db.rs @@ -4,12 +4,15 @@ pub mod query; // Re-export pub use anyhow; use anyhow::Context as _; -use gpui::{App, AppContext}; +pub use gpui; +use gpui::{App, AppContext, Global}; pub use indoc::indoc; +pub use inventory; pub use paths::database_dir; pub use smol; pub use sqlez; pub use sqlez_macros; +pub use uuid; pub use release_channel::RELEASE_CHANNEL; use sqlez::domain::Migrator; @@ -22,6 +25,103 @@ use std::sync::{LazyLock, atomic::Ordering}; use util::{ResultExt, maybe}; use zed_env_vars::ZED_STATELESS; +/// A migration registered via `static_connection!` and collected at link time. +pub struct DomainMigration { + pub name: &'static str, + pub migrations: &'static [&'static str], + pub dependencies: &'static [&'static str], + pub should_allow_migration_change: fn(usize, &str, &str) -> bool, +} + +inventory::collect!(DomainMigration); + +/// The shared database connection backing all domain-specific DB wrappers. +/// Set as a GPUI global per-App. Falls back to a shared LazyLock if not set. +pub struct AppDatabase(pub ThreadSafeConnection); + +impl Global for AppDatabase {} + +/// Migrator that runs all inventory-registered domain migrations. +pub struct AppMigrator; + +impl Migrator for AppMigrator { + fn migrate(connection: &sqlez::connection::Connection) -> anyhow::Result<()> { + let registrations: Vec<&DomainMigration> = inventory::iter::().collect(); + let sorted = topological_sort(®istrations); + for reg in &sorted { + let mut should_allow = reg.should_allow_migration_change; + connection.migrate(reg.name, reg.migrations, &mut should_allow)?; + } + Ok(()) + } +} + +impl AppDatabase { + /// Opens the production database and runs all inventory-registered + /// migrations in dependency order. + pub fn new() -> Self { + let db_dir = database_dir(); + let scope = RELEASE_CHANNEL.dev_name(); + let connection = smol::block_on(open_db::(db_dir, scope)); + Self(connection) + } + + /// Creates a new in-memory database with a unique name and runs all + /// inventory-registered migrations in dependency order. + #[cfg(any(test, feature = "test-support"))] + pub fn test_new() -> Self { + let name = format!("test-db-{}", uuid::Uuid::new_v4()); + let connection = smol::block_on(open_test_db::(&name)); + Self(connection) + } + + /// Returns the per-App connection if set, otherwise falls back to + /// the shared LazyLock. + pub fn global(cx: &App) -> &ThreadSafeConnection { + #[allow(unreachable_code)] + if let Some(db) = cx.try_global::() { + return &db.0; + } else { + #[cfg(any(feature = "test-support", test))] + return &TEST_APP_DATABASE.0; + + panic!("database not initialized") + } + } +} + +fn topological_sort<'a>(registrations: &[&'a DomainMigration]) -> Vec<&'a DomainMigration> { + let mut sorted: Vec<&DomainMigration> = Vec::new(); + let mut visited: std::collections::HashSet<&str> = std::collections::HashSet::new(); + + fn visit<'a>( + name: &str, + registrations: &[&'a DomainMigration], + sorted: &mut Vec<&'a DomainMigration>, + visited: &mut std::collections::HashSet<&'a str>, + ) { + if visited.contains(name) { + return; + } + if let Some(reg) = registrations.iter().find(|r| r.name == name) { + for dep in reg.dependencies { + visit(dep, registrations, sorted, visited); + } + visited.insert(reg.name); + sorted.push(reg); + } + } + + for reg in registrations { + visit(reg.name, registrations, &mut sorted, &mut visited); + } + sorted +} + +/// Shared fallback `AppDatabase` used when no per-App global is set. +#[cfg(any(test, feature = "test-support"))] +static TEST_APP_DATABASE: LazyLock = LazyLock::new(AppDatabase::test_new); + const CONNECTION_INITIALIZE_QUERY: &str = sql!( PRAGMA foreign_keys=TRUE; ); @@ -110,12 +210,11 @@ pub async fn open_test_db(db_name: &str) -> ThreadSafeConnection { /// Implements a basic DB wrapper for a given domain /// /// Arguments: -/// - static variable name for connection /// - type of connection wrapper /// - dependencies, whose migrations should be run prior to this domain's migrations #[macro_export] macro_rules! static_connection { - ($id:ident, $t:ident, [ $($d:ty),* ] $(, $global:ident)?) => { + ($t:ident, [ $($d:ty),* ]) => { impl ::std::ops::Deref for $t { type Target = $crate::sqlez::thread_safe_connection::ThreadSafeConnection; @@ -124,30 +223,33 @@ macro_rules! static_connection { } } + impl ::std::clone::Clone for $t { + fn clone(&self) -> Self { + $t(self.0.clone()) + } + } + impl $t { + /// Returns an instance backed by the per-App database if set, + /// or the shared fallback connection otherwise. + pub fn global(cx: &$crate::gpui::App) -> Self { + $t($crate::AppDatabase::global(cx).clone()) + } + #[cfg(any(test, feature = "test-support"))] pub async fn open_test_db(name: &'static str) -> Self { $t($crate::open_test_db::<$t>(name).await) } } - #[cfg(any(test, feature = "test-support"))] - pub static $id: std::sync::LazyLock<$t> = std::sync::LazyLock::new(|| { - #[allow(unused_parens)] - $t($crate::smol::block_on($crate::open_test_db::<($($d,)* $t)>(stringify!($id)))) - }); - - #[cfg(not(any(test, feature = "test-support")))] - pub static $id: std::sync::LazyLock<$t> = std::sync::LazyLock::new(|| { - let db_dir = $crate::database_dir(); - let scope = if false $(|| stringify!($global) == "global")? { - "global" - } else { - $crate::RELEASE_CHANNEL.dev_name() - }; - #[allow(unused_parens)] - $t($crate::smol::block_on($crate::open_db::<($($d,)* $t)>(db_dir, scope))) - }); + $crate::inventory::submit! { + $crate::DomainMigration { + name: <$t as $crate::sqlez::domain::Domain>::NAME, + migrations: <$t as $crate::sqlez::domain::Domain>::MIGRATIONS, + dependencies: &[$(<$d as $crate::sqlez::domain::Domain>::NAME),*], + should_allow_migration_change: <$t as $crate::sqlez::domain::Domain>::should_allow_migration_change, + } + } } } diff --git a/crates/db/src/kvp.rs b/crates/db/src/kvp.rs index 438adcdf44921aa1d2590694608c139e9174d788..8d86ac7fc4b7b62de05583b28784da251b0efe74 100644 --- a/crates/db/src/kvp.rs +++ b/crates/db/src/kvp.rs @@ -11,6 +11,12 @@ use crate::{ pub struct KeyValueStore(crate::sqlez::thread_safe_connection::ThreadSafeConnection); +impl KeyValueStore { + pub fn from_app_db(db: &crate::AppDatabase) -> Self { + Self(db.0.clone()) + } +} + impl Domain for KeyValueStore { const NAME: &str = stringify!(KeyValueStore); @@ -32,26 +38,25 @@ impl Domain for KeyValueStore { ]; } -crate::static_connection!(KEY_VALUE_STORE, KeyValueStore, []); +crate::static_connection!(KeyValueStore, []); pub trait Dismissable { const KEY: &'static str; - fn dismissed() -> bool { - KEY_VALUE_STORE + fn dismissed(cx: &App) -> bool { + KeyValueStore::global(cx) .read_kvp(Self::KEY) .log_err() .is_some_and(|s| s.is_some()) } fn set_dismissed(is_dismissed: bool, cx: &mut App) { + let db = KeyValueStore::global(cx); write_and_log(cx, move || async move { if is_dismissed { - KEY_VALUE_STORE - .write_kvp(Self::KEY.into(), "1".into()) - .await + db.write_kvp(Self::KEY.into(), "1".into()).await } else { - KEY_VALUE_STORE.delete_kvp(Self::KEY.into()).await + db.delete_kvp(Self::KEY.into()).await } }) } @@ -228,9 +233,26 @@ impl Domain for GlobalKeyValueStore { )]; } -crate::static_connection!(GLOBAL_KEY_VALUE_STORE, GlobalKeyValueStore, [], global); +impl std::ops::Deref for GlobalKeyValueStore { + type Target = ThreadSafeConnection; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +static GLOBAL_KEY_VALUE_STORE: std::sync::LazyLock = + std::sync::LazyLock::new(|| { + let db_dir = crate::database_dir(); + GlobalKeyValueStore(smol::block_on(crate::open_db::( + db_dir, "global", + ))) + }); impl GlobalKeyValueStore { + pub fn global() -> &'static Self { + &GLOBAL_KEY_VALUE_STORE + } + query! { pub fn read_kvp(key: &str) -> Result> { SELECT value FROM kv_store WHERE key = (?) diff --git a/crates/debugger_ui/src/debugger_panel.rs b/crates/debugger_ui/src/debugger_panel.rs index 7e11fe4e19f9acafdb9e2d0be30069f3d5457e5c..39c0dc9d7a79afae19ae27cba244253e37460117 100644 --- a/crates/debugger_ui/src/debugger_panel.rs +++ b/crates/debugger_ui/src/debugger_panel.rs @@ -1461,7 +1461,12 @@ async fn register_session_inner( .detach(); }) .ok(); - let serialized_layout = persistence::get_serialized_layout(adapter_name).await; + let serialized_layout = this + .update(cx, |_, cx| { + persistence::get_serialized_layout(&adapter_name, &db::kvp::KeyValueStore::global(cx)) + }) + .ok() + .flatten(); let debug_session = this.update_in(cx, |this, window, cx| { let parent_session = this .sessions_with_children diff --git a/crates/debugger_ui/src/persistence.rs b/crates/debugger_ui/src/persistence.rs index 7b0fba39e70012cdeb19408d22ce21e3b6c9621f..7282e5160d709a2af813e465da7686e45ee8bb66 100644 --- a/crates/debugger_ui/src/persistence.rs +++ b/crates/debugger_ui/src/persistence.rs @@ -1,7 +1,7 @@ use anyhow::Context as _; use collections::HashMap; use dap::{Capabilities, adapters::DebugAdapterName}; -use db::kvp::KEY_VALUE_STORE; +use db::kvp::KeyValueStore; use gpui::{Axis, Context, Entity, EntityId, Focusable, Subscription, WeakEntity, Window}; use project::Project; use serde::{Deserialize, Serialize}; @@ -125,15 +125,15 @@ const DEBUGGER_PANEL_PREFIX: &str = "debugger_panel_"; pub(crate) async fn serialize_pane_layout( adapter_name: DebugAdapterName, pane_group: SerializedLayout, + kvp: KeyValueStore, ) -> anyhow::Result<()> { let serialized_pane_group = serde_json::to_string(&pane_group) .context("Serializing pane group with serde_json as a string")?; - KEY_VALUE_STORE - .write_kvp( - format!("{DEBUGGER_PANEL_PREFIX}-{adapter_name}"), - serialized_pane_group, - ) - .await + kvp.write_kvp( + format!("{DEBUGGER_PANEL_PREFIX}-{adapter_name}"), + serialized_pane_group, + ) + .await } pub(crate) fn build_serialized_layout( @@ -187,13 +187,13 @@ fn serialize_pane(pane: &Entity, cx: &App) -> SerializedPane { } } -pub(crate) async fn get_serialized_layout( +pub(crate) fn get_serialized_layout( adapter_name: impl AsRef, + kvp: &KeyValueStore, ) -> Option { let key = format!("{DEBUGGER_PANEL_PREFIX}-{}", adapter_name.as_ref()); - KEY_VALUE_STORE - .read_kvp(&key) + kvp.read_kvp(&key) .log_err() .flatten() .and_then(|value| serde_json::from_str::(&value).ok()) diff --git a/crates/debugger_ui/src/session/running.rs b/crates/debugger_ui/src/session/running.rs index 1df442ef88fada109b6b7ad6e3bb5cf63f0ea453..ef92135c879ea22a09569df0f1b2fc9c5ae12473 100644 --- a/crates/debugger_ui/src/session/running.rs +++ b/crates/debugger_ui/src/session/running.rs @@ -1501,9 +1501,14 @@ impl RunningState { return; }; - persistence::serialize_pane_layout(adapter_name, pane_layout) - .await - .log_err(); + let kvp = this + .read_with(cx, |_, cx| db::kvp::KeyValueStore::global(cx)) + .ok(); + if let Some(kvp) = kvp { + persistence::serialize_pane_layout(adapter_name, pane_layout, kvp) + .await + .log_err(); + } this.update(cx, |this, _| { this._schedule_serialize.take(); diff --git a/crates/debugger_ui/src/session/running/breakpoint_list.rs b/crates/debugger_ui/src/session/running/breakpoint_list.rs index 352acbd530d937d8300528000693ab76099ca991..1af4e7a05e4f5ae9db40f8d682b8d09f9ae03ae2 100644 --- a/crates/debugger_ui/src/session/running/breakpoint_list.rs +++ b/crates/debugger_ui/src/session/running/breakpoint_list.rs @@ -6,7 +6,7 @@ use std::{ }; use dap::{Capabilities, ExceptionBreakpointsFilter, adapters::DebugAdapterName}; -use db::kvp::KEY_VALUE_STORE; +use db::kvp::KeyValueStore; use editor::Editor; use gpui::{ Action, AppContext, ClickEvent, Entity, FocusHandle, Focusable, MouseButton, ScrollStrategy, @@ -520,8 +520,9 @@ impl BreakpointList { }); let value = serde_json::to_string(&settings); + let kvp = KeyValueStore::global(cx); cx.background_executor() - .spawn(async move { KEY_VALUE_STORE.write_kvp(key, value?).await }) + .spawn(async move { kvp.write_kvp(key, value?).await }) } else { Task::ready(Result::Ok(())) } @@ -532,7 +533,7 @@ impl BreakpointList { adapter_name: DebugAdapterName, cx: &mut Context, ) -> anyhow::Result<()> { - let Some(val) = KEY_VALUE_STORE.read_kvp(&Self::kvp_key(&adapter_name))? else { + let Some(val) = KeyValueStore::global(cx).read_kvp(&Self::kvp_key(&adapter_name))? else { return Ok(()); }; let value: PersistedAdapterOptions = serde_json::from_str(&val)?; diff --git a/crates/debugger_ui/src/session/running/stack_frame_list.rs b/crates/debugger_ui/src/session/running/stack_frame_list.rs index 3e8a28a40bfc194413e0bf19d371a86609ba58c7..7175b8556a45f0f499bd0604a0112a085b8730ea 100644 --- a/crates/debugger_ui/src/session/running/stack_frame_list.rs +++ b/crates/debugger_ui/src/session/running/stack_frame_list.rs @@ -5,7 +5,7 @@ use std::time::Duration; use anyhow::{Context as _, Result, anyhow}; use dap::StackFrameId; use dap::adapters::DebugAdapterName; -use db::kvp::KEY_VALUE_STORE; +use db::kvp::KeyValueStore; use gpui::{ Action, AnyElement, Entity, EventEmitter, FocusHandle, Focusable, FontWeight, ListState, Subscription, Task, WeakEntity, list, @@ -122,7 +122,7 @@ impl StackFrameList { .flatten() .and_then(|database_id| { let key = stack_frame_filter_key(&session.read(cx).adapter(), database_id); - KEY_VALUE_STORE + KeyValueStore::global(cx) .read_kvp(&key) .ok() .flatten() @@ -852,8 +852,10 @@ impl StackFrameList { .flatten() { let key = stack_frame_filter_key(&self.session.read(cx).adapter(), database_id); - let save_task = KEY_VALUE_STORE.write_kvp(key, self.list_filter.into()); - cx.background_spawn(save_task).detach(); + let kvp = KeyValueStore::global(cx); + let filter: String = self.list_filter.into(); + cx.background_spawn(async move { kvp.write_kvp(key, filter).await }) + .detach(); } if let Some(ThreadStatus::Stopped) = thread_status { diff --git a/crates/debugger_ui/src/tests/stack_frame_list.rs b/crates/debugger_ui/src/tests/stack_frame_list.rs index 1f5ac5dea4a19af338feceaa2ee51fd9322fa9a5..7e1763f6650127be12803f4d64bc16f0ab3c9989 100644 --- a/crates/debugger_ui/src/tests/stack_frame_list.rs +++ b/crates/debugger_ui/src/tests/stack_frame_list.rs @@ -9,7 +9,7 @@ use dap::{ StackFrame, requests::{Scopes, StackTrace, Threads}, }; -use db::kvp::KEY_VALUE_STORE; +use db::kvp::KeyValueStore; use editor::{Editor, ToPoint as _}; use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext}; use project::{FakeFs, Project}; @@ -1217,7 +1217,10 @@ async fn test_stack_frame_filter_persistence( .expect("workspace id has to be some for this test to work properly"); let key = stack_frame_filter_key(&adapter_name, workspace_id); - let stored_value = KEY_VALUE_STORE.read_kvp(&key).unwrap(); + let stored_value = cx + .update(|_, cx| KeyValueStore::global(cx)) + .read_kvp(&key) + .unwrap(); assert_eq!( stored_value, Some(StackFrameFilter::OnlyUserFrames.into()), diff --git a/crates/edit_prediction/src/edit_prediction.rs b/crates/edit_prediction/src/edit_prediction.rs index cfc5c7efe348b7238813853bbf3e5fd70047340d..b8a631955ec4dc8f5494379eca3d9cffb4c2d05b 100644 --- a/crates/edit_prediction/src/edit_prediction.rs +++ b/crates/edit_prediction/src/edit_prediction.rs @@ -12,7 +12,7 @@ use cloud_llm_client::{ }; use collections::{HashMap, HashSet}; use copilot::{Copilot, Reinstall, SignIn, SignOut}; -use db::kvp::{Dismissable, KEY_VALUE_STORE}; +use db::kvp::{Dismissable, KeyValueStore}; use edit_prediction_context::{RelatedExcerptStore, RelatedExcerptStoreEvent, RelatedFile}; use feature_flags::{FeatureFlag, FeatureFlagAppExt as _}; use futures::{ @@ -770,7 +770,7 @@ impl EditPredictionStore { } pub fn new(client: Arc, user_store: Entity, cx: &mut Context) -> Self { - let data_collection_choice = Self::load_data_collection_choice(); + let data_collection_choice = Self::load_data_collection_choice(cx); let llm_token = LlmApiToken::global(cx); @@ -2745,8 +2745,8 @@ impl EditPredictionStore { self.data_collection_choice.is_enabled(cx) } - fn load_data_collection_choice() -> DataCollectionChoice { - let choice = KEY_VALUE_STORE + fn load_data_collection_choice(cx: &App) -> DataCollectionChoice { + let choice = KeyValueStore::global(cx) .read_kvp(ZED_PREDICT_DATA_COLLECTION_CHOICE) .log_err() .flatten(); @@ -2766,11 +2766,13 @@ impl EditPredictionStore { self.data_collection_choice = self.data_collection_choice.toggle(); let new_choice = self.data_collection_choice; let is_enabled = new_choice.is_enabled(cx); - db::write_and_log(cx, move || { - KEY_VALUE_STORE.write_kvp( + let kvp = KeyValueStore::global(cx); + db::write_and_log(cx, move || async move { + kvp.write_kvp( ZED_PREDICT_DATA_COLLECTION_CHOICE.into(), is_enabled.to_string(), ) + .await }); } @@ -3006,12 +3008,13 @@ struct ZedPredictUpsell; impl Dismissable for ZedPredictUpsell { const KEY: &'static str = "dismissed-edit-predict-upsell"; - fn dismissed() -> bool { + fn dismissed(cx: &App) -> bool { // To make this backwards compatible with older versions of Zed, we // check if the user has seen the previous Edit Prediction Onboarding // before, by checking the data collection choice which was written to // the database once the user clicked on "Accept and Enable" - if KEY_VALUE_STORE + let kvp = KeyValueStore::global(cx); + if kvp .read_kvp(ZED_PREDICT_DATA_COLLECTION_CHOICE) .log_err() .is_some_and(|s| s.is_some()) @@ -3019,15 +3022,14 @@ impl Dismissable for ZedPredictUpsell { return true; } - KEY_VALUE_STORE - .read_kvp(Self::KEY) + kvp.read_kvp(Self::KEY) .log_err() .is_some_and(|s| s.is_some()) } } -pub fn should_show_upsell_modal() -> bool { - !ZedPredictUpsell::dismissed() +pub fn should_show_upsell_modal(cx: &App) -> bool { + !ZedPredictUpsell::dismissed(cx) } pub fn init(cx: &mut App) { diff --git a/crates/edit_prediction_ui/src/edit_prediction_button.rs b/crates/edit_prediction_ui/src/edit_prediction_button.rs index 1a5e60ca8b27f31d26c6389bbd39a516164f3bf6..2d50e7fa2321750634500925b0b6ec2b6989163d 100644 --- a/crates/edit_prediction_ui/src/edit_prediction_button.rs +++ b/crates/edit_prediction_ui/src/edit_prediction_button.rs @@ -379,7 +379,7 @@ impl Render for EditPredictionButton { } }; - if edit_prediction::should_show_upsell_modal() { + if edit_prediction::should_show_upsell_modal(cx) { let tooltip_meta = if self.user_store.read(cx).current_user().is_some() { "Choose a Plan" } else { diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 69081c07fd97ff49204893cdca6d4fbc081f2750..99814b3cbce9914e1c2634bc1456267912c4d744 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -153,7 +153,7 @@ use multi_buffer::{ ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow, }; use parking_lot::Mutex; -use persistence::DB; +use persistence::EditorDb; use project::{ BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent, CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint, InlayId, @@ -3757,6 +3757,7 @@ impl Editor { let selections = selections.clone(); let background_executor = cx.background_executor().clone(); let editor_id = cx.entity().entity_id().as_u64() as ItemId; + let db = EditorDb::global(cx); self.serialize_selections = cx.background_spawn(async move { background_executor.timer(SERIALIZATION_THROTTLE_TIME).await; let db_selections = selections @@ -3769,7 +3770,7 @@ impl Editor { }) .collect(); - DB.save_editor_selections(editor_id, workspace_id, db_selections) + db.save_editor_selections(editor_id, workspace_id, db_selections) .await .with_context(|| { format!( @@ -3854,16 +3855,17 @@ impl Editor { (start, end, start_fp, end_fp) }) .collect::>(); + let db = EditorDb::global(cx); self.serialize_folds = cx.background_spawn(async move { background_executor.timer(SERIALIZATION_THROTTLE_TIME).await; if db_folds.is_empty() { // No folds - delete any persisted folds for this file - DB.delete_file_folds(workspace_id, file_path) + db.delete_file_folds(workspace_id, file_path) .await .with_context(|| format!("deleting file folds for workspace {workspace_id:?}")) .log_err(); } else { - DB.save_file_folds(workspace_id, file_path, db_folds) + db.save_file_folds(workspace_id, file_path, db_folds) .await .with_context(|| { format!("persisting file folds for workspace {workspace_id:?}") @@ -25357,12 +25359,13 @@ impl Editor { }); // Try file_folds (path-based) first, fallback to editor_folds (migration) + let db = EditorDb::global(cx); let (folds, needs_migration) = if let Some(ref path) = file_path { - if let Some(folds) = DB.get_file_folds(workspace_id, path).log_err() + if let Some(folds) = db.get_file_folds(workspace_id, path).log_err() && !folds.is_empty() { (Some(folds), false) - } else if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() + } else if let Some(folds) = db.get_editor_folds(item_id, workspace_id).log_err() && !folds.is_empty() { // Found old editor_folds data, will migrate to file_folds @@ -25372,7 +25375,7 @@ impl Editor { } } else { // No file path, try editor_folds as fallback - let folds = DB.get_editor_folds(item_id, workspace_id).log_err(); + let folds = db.get_editor_folds(item_id, workspace_id).log_err(); (folds.filter(|f| !f.is_empty()), false) }; @@ -25471,8 +25474,9 @@ impl Editor { if needs_migration { if let Some(ref path) = file_path { let path = path.clone(); + let db = EditorDb::global(cx); cx.spawn(async move |_, _| { - DB.save_file_folds(workspace_id, path, db_folds_for_migration) + db.save_file_folds(workspace_id, path, db_folds_for_migration) .await .log_err(); }) @@ -25482,7 +25486,7 @@ impl Editor { } } - if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() + if let Some(selections) = db.get_editor_selections(item_id, workspace_id).log_err() && !selections.is_empty() { let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx)); @@ -25517,7 +25521,10 @@ impl Editor { return; } - let Some(folds) = DB.get_file_folds(workspace_id, &file_path).log_err() else { + let Some(folds) = EditorDb::global(cx) + .get_file_folds(workspace_id, &file_path) + .log_err() + else { return; }; if folds.is_empty() { diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index ac07545a455a7fe8de90470b19573cec0c1743f5..d2d44fc8ada7360a0b8a8608ce916649b280abae 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -4,7 +4,7 @@ use crate::{ NavigationData, ReportEditorEvent, SelectionEffects, ToPoint as _, display_map::HighlightKey, editor_settings::SeedQuerySetting, - persistence::{DB, SerializedEditor}, + persistence::{EditorDb, SerializedEditor}, scroll::{ScrollAnchor, ScrollOffset}, }; use anyhow::{Context as _, Result, anyhow}; @@ -1135,7 +1135,13 @@ impl SerializableItem for Editor { _window: &mut Window, cx: &mut App, ) -> Task> { - workspace::delete_unloaded_items(alive_items, workspace_id, "editors", &DB, cx) + workspace::delete_unloaded_items( + alive_items, + workspace_id, + "editors", + &EditorDb::global(cx), + cx, + ) } fn deserialize( @@ -1146,7 +1152,7 @@ impl SerializableItem for Editor { window: &mut Window, cx: &mut App, ) -> Task>> { - let serialized_editor = match DB + let serialized_editor = match EditorDb::global(cx) .get_serialized_editor(item_id, workspace_id) .context("Failed to query editor state") { @@ -1361,6 +1367,7 @@ impl SerializableItem for Editor { let snapshot = buffer.read(cx).snapshot(); + let db = EditorDb::global(cx); Some(cx.spawn_in(window, async move |_this, cx| { cx.background_spawn(async move { let (contents, language) = if serialize_dirty_buffers && is_dirty { @@ -1378,7 +1385,7 @@ impl SerializableItem for Editor { mtime, }; log::debug!("Serializing editor {item_id:?} in workspace {workspace_id:?}"); - DB.save_serialized_editor(item_id, workspace_id, editor) + db.save_serialized_editor(item_id, workspace_id, editor) .await .context("failed to save serialized editor") }) @@ -2110,7 +2117,9 @@ mod tests { MultiWorkspace::test_new(project.clone(), window, cx) }); let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone()); - let workspace_id = workspace::WORKSPACE_DB.next_id().await.unwrap(); + let db = cx.update(|_, cx| workspace::WorkspaceDb::global(cx)); + let workspace_id = db.next_id().await.unwrap(); + let editor_db = cx.update(|_, cx| EditorDb::global(cx)); let item_id = 1234 as ItemId; let mtime = fs .metadata(Path::new(path!("/file.rs"))) @@ -2126,7 +2135,8 @@ mod tests { mtime: Some(mtime), }; - DB.save_serialized_editor(item_id, workspace_id, serialized_editor.clone()) + editor_db + .save_serialized_editor(item_id, workspace_id, serialized_editor.clone()) .await .unwrap(); @@ -2149,8 +2159,10 @@ mod tests { MultiWorkspace::test_new(project.clone(), window, cx) }); let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone()); + let db = cx.update(|_, cx| workspace::WorkspaceDb::global(cx)); + let editor_db = cx.update(|_, cx| EditorDb::global(cx)); - let workspace_id = workspace::WORKSPACE_DB.next_id().await.unwrap(); + let workspace_id = db.next_id().await.unwrap(); let item_id = 5678 as ItemId; let serialized_editor = SerializedEditor { @@ -2160,7 +2172,8 @@ mod tests { mtime: None, }; - DB.save_serialized_editor(item_id, workspace_id, serialized_editor) + editor_db + .save_serialized_editor(item_id, workspace_id, serialized_editor) .await .unwrap(); @@ -2189,8 +2202,10 @@ mod tests { MultiWorkspace::test_new(project.clone(), window, cx) }); let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone()); + let db = cx.update(|_, cx| workspace::WorkspaceDb::global(cx)); + let editor_db = cx.update(|_, cx| EditorDb::global(cx)); - let workspace_id = workspace::WORKSPACE_DB.next_id().await.unwrap(); + let workspace_id = db.next_id().await.unwrap(); let item_id = 9012 as ItemId; let serialized_editor = SerializedEditor { @@ -2200,7 +2215,8 @@ mod tests { mtime: None, }; - DB.save_serialized_editor(item_id, workspace_id, serialized_editor) + editor_db + .save_serialized_editor(item_id, workspace_id, serialized_editor) .await .unwrap(); @@ -2227,8 +2243,10 @@ mod tests { MultiWorkspace::test_new(project.clone(), window, cx) }); let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone()); + let db = cx.update(|_, cx| workspace::WorkspaceDb::global(cx)); + let editor_db = cx.update(|_, cx| EditorDb::global(cx)); - let workspace_id = workspace::WORKSPACE_DB.next_id().await.unwrap(); + let workspace_id = db.next_id().await.unwrap(); let item_id = 9345 as ItemId; let old_mtime = MTime::from_seconds_and_nanos(0, 50); @@ -2239,7 +2257,8 @@ mod tests { mtime: Some(old_mtime), }; - DB.save_serialized_editor(item_id, workspace_id, serialized_editor) + editor_db + .save_serialized_editor(item_id, workspace_id, serialized_editor) .await .unwrap(); @@ -2259,8 +2278,10 @@ mod tests { MultiWorkspace::test_new(project.clone(), window, cx) }); let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone()); + let db = cx.update(|_, cx| workspace::WorkspaceDb::global(cx)); + let editor_db = cx.update(|_, cx| EditorDb::global(cx)); - let workspace_id = workspace::WORKSPACE_DB.next_id().await.unwrap(); + let workspace_id = db.next_id().await.unwrap(); let item_id = 10000 as ItemId; let serialized_editor = SerializedEditor { @@ -2270,7 +2291,8 @@ mod tests { mtime: None, }; - DB.save_serialized_editor(item_id, workspace_id, serialized_editor) + editor_db + .save_serialized_editor(item_id, workspace_id, serialized_editor) .await .unwrap(); @@ -2301,8 +2323,10 @@ mod tests { MultiWorkspace::test_new(project.clone(), window, cx) }); let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone()); + let db = cx.update(|_, cx| workspace::WorkspaceDb::global(cx)); + let editor_db = cx.update(|_, cx| EditorDb::global(cx)); - let workspace_id = workspace::WORKSPACE_DB.next_id().await.unwrap(); + let workspace_id = db.next_id().await.unwrap(); let item_id = 11000 as ItemId; let mtime = fs @@ -2320,7 +2344,8 @@ mod tests { mtime: Some(mtime), }; - DB.save_serialized_editor(item_id, workspace_id, serialized_editor) + editor_db + .save_serialized_editor(item_id, workspace_id, serialized_editor) .await .unwrap(); @@ -2357,8 +2382,10 @@ mod tests { let (multi_workspace, cx) = cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx)); let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone()); + let db = cx.update(|_, cx| workspace::WorkspaceDb::global(cx)); + let editor_db = cx.update(|_, cx| EditorDb::global(cx)); - let workspace_id = workspace::WORKSPACE_DB.next_id().await.unwrap(); + let workspace_id = db.next_id().await.unwrap(); let item_id = 99999 as ItemId; let serialized_editor = SerializedEditor { @@ -2368,7 +2395,8 @@ mod tests { mtime: None, }; - DB.save_serialized_editor(item_id, workspace_id, serialized_editor) + editor_db + .save_serialized_editor(item_id, workspace_id, serialized_editor) .await .unwrap(); diff --git a/crates/editor/src/persistence.rs b/crates/editor/src/persistence.rs index fc24277cda1c834471559144304c0d14d5ab52df..f1f12239cf190f1bb5ff26aff26f43fe3b52336b 100644 --- a/crates/editor/src/persistence.rs +++ b/crates/editor/src/persistence.rs @@ -226,7 +226,7 @@ impl Domain for EditorDb { ]; } -db::static_connection!(DB, EditorDb, [WorkspaceDb]); +db::static_connection!(EditorDb, [WorkspaceDb]); // https://www.sqlite.org/limits.html // > <..> the maximum value of a host parameter number is SQLITE_MAX_VARIABLE_NUMBER, @@ -415,8 +415,10 @@ mod tests { use super::*; #[gpui::test] - async fn test_save_and_get_serialized_editor() { - let workspace_id = workspace::WORKSPACE_DB.next_id().await.unwrap(); + async fn test_save_and_get_serialized_editor(cx: &mut gpui::TestAppContext) { + let db = cx.update(|cx| workspace::WorkspaceDb::global(cx)); + let workspace_id = db.next_id().await.unwrap(); + let editor_db = cx.update(|cx| EditorDb::global(cx)); let serialized_editor = SerializedEditor { abs_path: Some(PathBuf::from("testing.txt")), @@ -425,11 +427,12 @@ mod tests { mtime: None, }; - DB.save_serialized_editor(1234, workspace_id, serialized_editor.clone()) + editor_db + .save_serialized_editor(1234, workspace_id, serialized_editor.clone()) .await .unwrap(); - let have = DB + let have = editor_db .get_serialized_editor(1234, workspace_id) .unwrap() .unwrap(); @@ -443,11 +446,12 @@ mod tests { mtime: None, }; - DB.save_serialized_editor(1234, workspace_id, serialized_editor.clone()) + editor_db + .save_serialized_editor(1234, workspace_id, serialized_editor.clone()) .await .unwrap(); - let have = DB + let have = editor_db .get_serialized_editor(1234, workspace_id) .unwrap() .unwrap(); @@ -461,11 +465,12 @@ mod tests { mtime: None, }; - DB.save_serialized_editor(1234, workspace_id, serialized_editor.clone()) + editor_db + .save_serialized_editor(1234, workspace_id, serialized_editor.clone()) .await .unwrap(); - let have = DB + let have = editor_db .get_serialized_editor(1234, workspace_id) .unwrap() .unwrap(); @@ -479,11 +484,12 @@ mod tests { mtime: Some(MTime::from_seconds_and_nanos(100, 42)), }; - DB.save_serialized_editor(1234, workspace_id, serialized_editor.clone()) + editor_db + .save_serialized_editor(1234, workspace_id, serialized_editor.clone()) .await .unwrap(); - let have = DB + let have = editor_db .get_serialized_editor(1234, workspace_id) .unwrap() .unwrap(); @@ -499,8 +505,10 @@ mod tests { // The search uses contains_str_at() to find fingerprints in the buffer. #[gpui::test] - async fn test_save_and_get_file_folds() { - let workspace_id = workspace::WORKSPACE_DB.next_id().await.unwrap(); + async fn test_save_and_get_file_folds(cx: &mut gpui::TestAppContext) { + let db = cx.update(|cx| workspace::WorkspaceDb::global(cx)); + let workspace_id = db.next_id().await.unwrap(); + let editor_db = cx.update(|cx| EditorDb::global(cx)); // file_folds table uses path as key (no FK to editors table) let file_path: Arc = Arc::from(Path::new("/tmp/test_file_folds.rs")); @@ -520,12 +528,13 @@ mod tests { "} // end Foo".to_string(), ), ]; - DB.save_file_folds(workspace_id, file_path.clone(), folds.clone()) + editor_db + .save_file_folds(workspace_id, file_path.clone(), folds.clone()) .await .unwrap(); // Retrieve and verify fingerprints are preserved - let retrieved = DB.get_file_folds(workspace_id, &file_path).unwrap(); + let retrieved = editor_db.get_file_folds(workspace_id, &file_path).unwrap(); assert_eq!(retrieved.len(), 2); assert_eq!( retrieved[0], @@ -553,11 +562,12 @@ mod tests { "impl Bar {".to_string(), "} // end impl".to_string(), )]; - DB.save_file_folds(workspace_id, file_path.clone(), new_folds) + editor_db + .save_file_folds(workspace_id, file_path.clone(), new_folds) .await .unwrap(); - let retrieved = DB.get_file_folds(workspace_id, &file_path).unwrap(); + let retrieved = editor_db.get_file_folds(workspace_id, &file_path).unwrap(); assert_eq!(retrieved.len(), 1); assert_eq!( retrieved[0], @@ -570,10 +580,11 @@ mod tests { ); // Test delete - DB.delete_file_folds(workspace_id, file_path.clone()) + editor_db + .delete_file_folds(workspace_id, file_path.clone()) .await .unwrap(); - let retrieved = DB.get_file_folds(workspace_id, &file_path).unwrap(); + let retrieved = editor_db.get_file_folds(workspace_id, &file_path).unwrap(); assert!(retrieved.is_empty()); // Test multiple files don't interfere @@ -582,15 +593,21 @@ mod tests { let folds_a = vec![(10, 20, "a_start".to_string(), "a_end".to_string())]; let folds_b = vec![(30, 40, "b_start".to_string(), "b_end".to_string())]; - DB.save_file_folds(workspace_id, file_path_a.clone(), folds_a) + editor_db + .save_file_folds(workspace_id, file_path_a.clone(), folds_a) .await .unwrap(); - DB.save_file_folds(workspace_id, file_path_b.clone(), folds_b) + editor_db + .save_file_folds(workspace_id, file_path_b.clone(), folds_b) .await .unwrap(); - let retrieved_a = DB.get_file_folds(workspace_id, &file_path_a).unwrap(); - let retrieved_b = DB.get_file_folds(workspace_id, &file_path_b).unwrap(); + let retrieved_a = editor_db + .get_file_folds(workspace_id, &file_path_a) + .unwrap(); + let retrieved_b = editor_db + .get_file_folds(workspace_id, &file_path_b) + .unwrap(); assert_eq!(retrieved_a.len(), 1); assert_eq!(retrieved_b.len(), 1); diff --git a/crates/editor/src/scroll.rs b/crates/editor/src/scroll.rs index 3341764383b594e8ee3fcb84486f71f8c94c5cb1..b10f7650a051c3ad3c31c1426eb98aeee4f9da07 100644 --- a/crates/editor/src/scroll.rs +++ b/crates/editor/src/scroll.rs @@ -8,7 +8,7 @@ use crate::{ InlayHintRefreshReason, MultiBufferSnapshot, RowExt, ToPoint, display_map::{DisplaySnapshot, ToDisplayPoint}, hover_popover::hide_hover, - persistence::DB, + persistence::EditorDb, }; pub use autoscroll::{Autoscroll, AutoscrollStrategy}; use core::fmt::Debug; @@ -467,12 +467,13 @@ impl ScrollManager { let item_id = cx.entity().entity_id().as_u64() as ItemId; let executor = cx.background_executor().clone(); + let db = EditorDb::global(cx); self._save_scroll_position_task = cx.background_executor().spawn(async move { executor.timer(Duration::from_millis(10)).await; log::debug!( "Saving scroll position for item {item_id:?} in workspace {workspace_id:?}" ); - DB.save_scroll_position( + db.save_scroll_position( item_id, workspace_id, top_row, @@ -937,7 +938,7 @@ impl Editor { window: &mut Window, cx: &mut Context, ) { - let scroll_position = DB.get_scroll_position(item_id, workspace_id); + let scroll_position = EditorDb::global(cx).get_scroll_position(item_id, workspace_id); if let Ok(Some((top_row, x, y))) = scroll_position { let top_anchor = self .buffer() diff --git a/crates/extensions_ui/src/extension_suggest.rs b/crates/extensions_ui/src/extension_suggest.rs index 7ad4c1540a419f0cdeedb2aeff7661aafac5ef4c..47d1092eacabb8f49593cb266ece7c8401cf3f3e 100644 --- a/crates/extensions_ui/src/extension_suggest.rs +++ b/crates/extensions_ui/src/extension_suggest.rs @@ -1,12 +1,13 @@ use std::collections::HashMap; use std::sync::{Arc, OnceLock}; -use db::kvp::KEY_VALUE_STORE; +use db::kvp::KeyValueStore; use editor::Editor; use extension_host::ExtensionStore; use gpui::{AppContext as _, Context, Entity, SharedString, Window}; use language::Buffer; use ui::prelude::*; +use util::ResultExt; use util::rel_path::RelPath; use workspace::notifications::simple_message_notification::MessageNotification; use workspace::{Workspace, notifications::NotificationId}; @@ -147,7 +148,8 @@ pub(crate) fn suggest(buffer: Entity, window: &mut Window, cx: &mut Cont }; let key = language_extension_key(&extension_id); - let Ok(None) = KEY_VALUE_STORE.read_kvp(&key) else { + let kvp = KeyValueStore::global(cx); + let Ok(None) = kvp.read_kvp(&key) else { return; }; @@ -193,9 +195,11 @@ pub(crate) fn suggest(buffer: Entity, window: &mut Window, cx: &mut Cont .secondary_icon_color(Color::Error) .secondary_on_click(move |_window, cx| { let key = language_extension_key(&extension_id); - db::write_and_log(cx, move || { - KEY_VALUE_STORE.write_kvp(key, "dismissed".to_string()) - }); + let kvp = KeyValueStore::global(cx); + cx.background_spawn(async move { + kvp.write_kvp(key, "dismissed".to_string()).await.log_err() + }) + .detach(); }) }) }); diff --git a/crates/git_graph/src/git_graph.rs b/crates/git_graph/src/git_graph.rs index b0a4701cd25021e2725ff28b7cc45d1b4f203c8d..aa53cd83e45b07cf94a6fc1b862b71053b92c81d 100644 --- a/crates/git_graph/src/git_graph.rs +++ b/crates/git_graph/src/git_graph.rs @@ -2358,7 +2358,7 @@ impl SerializableItem for GitGraph { alive_items, workspace_id, "git_graphs", - &persistence::GIT_GRAPHS, + &persistence::GitGraphsDb::global(cx), cx, ) } @@ -2371,7 +2371,8 @@ impl SerializableItem for GitGraph { window: &mut Window, cx: &mut App, ) -> Task>> { - if persistence::GIT_GRAPHS + let db = persistence::GitGraphsDb::global(cx); + if db .get_git_graph(item_id, workspace_id) .ok() .is_some_and(|is_open| is_open) @@ -2392,11 +2393,12 @@ impl SerializableItem for GitGraph { cx: &mut Context, ) -> Option>> { let workspace_id = workspace.database_id()?; - Some(cx.background_spawn(async move { - persistence::GIT_GRAPHS - .save_git_graph(item_id, workspace_id, true) - .await - })) + let db = persistence::GitGraphsDb::global(cx); + Some( + cx.background_spawn( + async move { db.save_git_graph(item_id, workspace_id, true).await }, + ), + ) } fn should_serialize(&self, event: &Self::Event) -> bool { @@ -2430,7 +2432,7 @@ mod persistence { )]); } - db::static_connection!(GIT_GRAPHS, GitGraphsDb, [WorkspaceDb]); + db::static_connection!(GitGraphsDb, [WorkspaceDb]); impl GitGraphsDb { query! { diff --git a/crates/git_ui/src/git_panel.rs b/crates/git_ui/src/git_panel.rs index 5a63a0662272b5d627bbb8e03ebaa18127d190cd..3615a447d231ce741e38b677366da19ea1a93e84 100644 --- a/crates/git_ui/src/git_panel.rs +++ b/crates/git_ui/src/git_panel.rs @@ -14,7 +14,7 @@ use anyhow::Context as _; use askpass::AskPassDelegate; use cloud_llm_client::CompletionIntent; use collections::{BTreeMap, HashMap, HashSet}; -use db::kvp::KEY_VALUE_STORE; +use db::kvp::KeyValueStore; use editor::{ Direction, Editor, EditorElement, EditorMode, MultiBuffer, MultiBufferOffset, actions::ExpandAllDiffHunks, @@ -928,6 +928,7 @@ impl GitPanel { let width = self.width; let amend_pending = self.amend_pending; let signoff_enabled = self.signoff_enabled; + let kvp = KeyValueStore::global(cx); self.pending_serialization = cx.spawn(async move |git_panel, cx| { cx.background_executor() @@ -948,16 +949,15 @@ impl GitPanel { }; cx.background_spawn( async move { - KEY_VALUE_STORE - .write_kvp( - serialization_key, - serde_json::to_string(&SerializedGitPanel { - width, - amend_pending, - signoff_enabled, - })?, - ) - .await?; + kvp.write_kvp( + serialization_key, + serde_json::to_string(&SerializedGitPanel { + width, + amend_pending, + signoff_enabled, + })?, + ) + .await?; anyhow::Ok(()) } .log_err(), @@ -5542,12 +5542,14 @@ impl GitPanel { mut cx: AsyncWindowContext, ) -> anyhow::Result> { let serialized_panel = match workspace - .read_with(&cx, |workspace, _| Self::serialization_key(workspace)) + .read_with(&cx, |workspace, cx| { + Self::serialization_key(workspace).map(|key| (key, KeyValueStore::global(cx))) + }) .ok() .flatten() { - Some(serialization_key) => cx - .background_spawn(async move { KEY_VALUE_STORE.read_kvp(&serialization_key) }) + Some((serialization_key, kvp)) => cx + .background_spawn(async move { kvp.read_kvp(&serialization_key) }) .await .context("loading git panel") .log_err() diff --git a/crates/git_ui/src/project_diff.rs b/crates/git_ui/src/project_diff.rs index 41eff7b23a95ca2d4112d4b95aef67ff7d4a765f..6d8b91cc54cc4baeb4fdda594404e04181fe6cf4 100644 --- a/crates/git_ui/src/project_diff.rs +++ b/crates/git_ui/src/project_diff.rs @@ -1219,8 +1219,9 @@ impl SerializableItem for ProjectDiff { window: &mut Window, cx: &mut App, ) -> Task>> { + let db = persistence::ProjectDiffDb::global(cx); window.spawn(cx, async move |cx| { - let diff_base = persistence::PROJECT_DIFF_DB.get_diff_base(item_id, workspace_id)?; + let diff_base = db.get_diff_base(item_id, workspace_id)?; let diff = cx.update(|window, cx| { let branch_diff = cx @@ -1246,10 +1247,10 @@ impl SerializableItem for ProjectDiff { let workspace_id = workspace.database_id()?; let diff_base = self.diff_base(cx).clone(); + let db = persistence::ProjectDiffDb::global(cx); Some(cx.background_spawn({ async move { - persistence::PROJECT_DIFF_DB - .save_diff_base(item_id, workspace_id, diff_base.clone()) + db.save_diff_base(item_id, workspace_id, diff_base.clone()) .await } })) @@ -1289,7 +1290,7 @@ mod persistence { )]; } - db::static_connection!(PROJECT_DIFF_DB, ProjectDiffDb, [WorkspaceDb]); + db::static_connection!(ProjectDiffDb, [WorkspaceDb]); impl ProjectDiffDb { pub async fn save_diff_base( diff --git a/crates/image_viewer/src/image_viewer.rs b/crates/image_viewer/src/image_viewer.rs index 729a2d9ce31cbe2165f0f66c15921e566d6878b4..8d619c82dfdac660a10210e375a8edf9bb97eee9 100644 --- a/crates/image_viewer/src/image_viewer.rs +++ b/crates/image_viewer/src/image_viewer.rs @@ -16,7 +16,7 @@ use gpui::{ WeakEntity, Window, actions, checkerboard, div, img, point, px, size, }; use language::File as _; -use persistence::IMAGE_VIEWER; +use persistence::ImageViewerDb; use project::{ImageItem, Project, ProjectPath, image_store::ImageItemEvent}; use settings::Settings; use theme::ThemeSettings; @@ -600,8 +600,9 @@ impl SerializableItem for ImageView { window: &mut Window, cx: &mut App, ) -> Task>> { + let db = ImageViewerDb::global(cx); window.spawn(cx, async move |cx| { - let image_path = IMAGE_VIEWER + let image_path = db .get_image_path(item_id, workspace_id)? .context("No image path found")?; @@ -634,13 +635,8 @@ impl SerializableItem for ImageView { _window: &mut Window, cx: &mut App, ) -> Task> { - delete_unloaded_items( - alive_items, - workspace_id, - "image_viewers", - &IMAGE_VIEWER, - cx, - ) + let db = ImageViewerDb::global(cx); + delete_unloaded_items(alive_items, workspace_id, "image_viewers", &db, cx) } fn serialize( @@ -654,12 +650,11 @@ impl SerializableItem for ImageView { let workspace_id = workspace.database_id()?; let image_path = self.image_item.read(cx).abs_path(cx)?; + let db = ImageViewerDb::global(cx); Some(cx.background_spawn({ async move { log::debug!("Saving image at path {image_path:?}"); - IMAGE_VIEWER - .save_image_path(item_id, workspace_id, image_path) - .await + db.save_image_path(item_id, workspace_id, image_path).await } })) } @@ -910,7 +905,7 @@ mod persistence { )]; } - db::static_connection!(IMAGE_VIEWER, ImageViewerDb, [WorkspaceDb]); + db::static_connection!(ImageViewerDb, [WorkspaceDb]); impl ImageViewerDb { query! { diff --git a/crates/keymap_editor/src/keymap_editor.rs b/crates/keymap_editor/src/keymap_editor.rs index e63c07d9975950afbb57b243114950f77c7240cb..2a66f9e9182c99a8e0f077617e75a863c1af9208 100644 --- a/crates/keymap_editor/src/keymap_editor.rs +++ b/crates/keymap_editor/src/keymap_editor.rs @@ -47,7 +47,7 @@ use zed_actions::{ChangeKeybinding, OpenKeymap}; use crate::{ action_completion_provider::ActionCompletionProvider, - persistence::KEYBINDING_EDITORS, + persistence::KeybindingEditorDb, ui_components::keystroke_input::{ ClearKeystrokes, KeystrokeInput, StartRecording, StopRecording, }, @@ -3818,13 +3818,8 @@ impl SerializableItem for KeymapEditor { _window: &mut Window, cx: &mut App, ) -> gpui::Task> { - workspace::delete_unloaded_items( - alive_items, - workspace_id, - "keybinding_editors", - &KEYBINDING_EDITORS, - cx, - ) + let db = KeybindingEditorDb::global(cx); + workspace::delete_unloaded_items(alive_items, workspace_id, "keybinding_editors", &db, cx) } fn deserialize( @@ -3835,11 +3830,9 @@ impl SerializableItem for KeymapEditor { window: &mut Window, cx: &mut App, ) -> gpui::Task>> { + let db = KeybindingEditorDb::global(cx); window.spawn(cx, async move |cx| { - if KEYBINDING_EDITORS - .get_keybinding_editor(item_id, workspace_id)? - .is_some() - { + if db.get_keybinding_editor(item_id, workspace_id)?.is_some() { cx.update(|window, cx| cx.new(|cx| KeymapEditor::new(workspace, window, cx))) } else { Err(anyhow!("No keybinding editor to deserialize")) @@ -3856,11 +3849,10 @@ impl SerializableItem for KeymapEditor { cx: &mut ui::Context, ) -> Option>> { let workspace_id = workspace.database_id()?; - Some(cx.background_spawn(async move { - KEYBINDING_EDITORS - .save_keybinding_editor(item_id, workspace_id) - .await - })) + let db = KeybindingEditorDb::global(cx); + Some(cx.background_spawn( + async move { db.save_keybinding_editor(item_id, workspace_id).await }, + )) } fn should_serialize(&self, _event: &Self::Event) -> bool { @@ -3889,7 +3881,7 @@ mod persistence { )]; } - db::static_connection!(KEYBINDING_EDITORS, KeybindingEditorDb, [WorkspaceDb]); + db::static_connection!(KeybindingEditorDb, [WorkspaceDb]); impl KeybindingEditorDb { query! { diff --git a/crates/language_onboarding/src/python.rs b/crates/language_onboarding/src/python.rs index 751980fd57af5d2bd28ca17f38b88aa09741e482..64b6502327f71e7a68f40b5a7690f308ecbf8c40 100644 --- a/crates/language_onboarding/src/python.rs +++ b/crates/language_onboarding/src/python.rs @@ -23,7 +23,7 @@ impl BasedPyrightBanner { this.have_basedpyright = true; } }); - let dismissed = Self::dismissed(); + let dismissed = Self::dismissed(cx); Self { dismissed, have_basedpyright: false, diff --git a/crates/onboarding/src/multibuffer_hint.rs b/crates/onboarding/src/multibuffer_hint.rs index 1f710318a64760faeecb31c8a6a368a0e11537a4..56092863c8b5ae1a18694a23419fc2127c5bdc81 100644 --- a/crates/onboarding/src/multibuffer_hint.rs +++ b/crates/onboarding/src/multibuffer_hint.rs @@ -2,7 +2,7 @@ use std::collections::HashSet; use std::sync::OnceLock; use std::sync::atomic::{AtomicUsize, Ordering}; -use db::kvp::KEY_VALUE_STORE; +use db::kvp::KeyValueStore; use gpui::{App, EntityId, EventEmitter, Subscription}; use ui::{IconButtonShape, Tooltip, prelude::*}; use workspace::item::{ItemBufferKind, ItemEvent, ItemHandle}; @@ -35,10 +35,10 @@ impl MultibufferHint { } impl MultibufferHint { - fn counter() -> &'static AtomicUsize { + fn counter(cx: &App) -> &'static AtomicUsize { static SHOWN_COUNT: OnceLock = OnceLock::new(); SHOWN_COUNT.get_or_init(|| { - let value: usize = KEY_VALUE_STORE + let value: usize = KeyValueStore::global(cx) .read_kvp(SHOWN_COUNT_KEY) .ok() .flatten() @@ -49,19 +49,21 @@ impl MultibufferHint { }) } - fn shown_count() -> usize { - Self::counter().load(Ordering::Relaxed) + fn shown_count(cx: &App) -> usize { + Self::counter(cx).load(Ordering::Relaxed) } fn increment_count(cx: &mut App) { - Self::set_count(Self::shown_count() + 1, cx) + Self::set_count(Self::shown_count(cx) + 1, cx) } pub(crate) fn set_count(count: usize, cx: &mut App) { - Self::counter().store(count, Ordering::Relaxed); + Self::counter(cx).store(count, Ordering::Relaxed); - db::write_and_log(cx, move || { - KEY_VALUE_STORE.write_kvp(SHOWN_COUNT_KEY.to_string(), format!("{}", count)) + let kvp = KeyValueStore::global(cx); + db::write_and_log(cx, move || async move { + kvp.write_kvp(SHOWN_COUNT_KEY.to_string(), format!("{}", count)) + .await }); } @@ -71,7 +73,7 @@ impl MultibufferHint { /// Determines the toolbar location for this [`MultibufferHint`]. fn determine_toolbar_location(&mut self, cx: &mut Context) -> ToolbarItemLocation { - if Self::shown_count() >= NUMBER_OF_HINTS { + if Self::shown_count(cx) >= NUMBER_OF_HINTS { return ToolbarItemLocation::Hidden; } diff --git a/crates/onboarding/src/onboarding.rs b/crates/onboarding/src/onboarding.rs index 68748afbd62a54fb33060b2812d8977ee94ee46d..808cba456406f915bdd9f593a6647ea3e90c696d 100644 --- a/crates/onboarding/src/onboarding.rs +++ b/crates/onboarding/src/onboarding.rs @@ -1,6 +1,6 @@ use crate::multibuffer_hint::MultibufferHint; use client::{Client, UserStore, zed_urls}; -use db::kvp::KEY_VALUE_STORE; +use db::kvp::KeyValueStore; use fs::Fs; use gpui::{ Action, AnyElement, App, AppContext, AsyncWindowContext, Context, Entity, EventEmitter, @@ -194,8 +194,10 @@ pub fn show_onboarding_view(app_state: Arc, cx: &mut App) -> Task gpui::Task>> { + let db = persistence::OnboardingPagesDb::global(cx); window.spawn(cx, async move |cx| { - if let Some(_) = - persistence::ONBOARDING_PAGES.get_onboarding_page(item_id, workspace_id)? - { + if let Some(_) = db.get_onboarding_page(item_id, workspace_id)? { workspace.update(cx, |workspace, cx| Onboarding::new(workspace, cx)) } else { Err(anyhow::anyhow!("No onboarding page to deserialize")) @@ -593,11 +594,12 @@ impl workspace::SerializableItem for Onboarding { ) -> Option>> { let workspace_id = workspace.database_id()?; - Some(cx.background_spawn(async move { - persistence::ONBOARDING_PAGES - .save_onboarding_page(item_id, workspace_id) - .await - })) + let db = persistence::OnboardingPagesDb::global(cx); + Some( + cx.background_spawn( + async move { db.save_onboarding_page(item_id, workspace_id).await }, + ), + ) } fn should_serialize(&self, event: &Self::Event) -> bool { @@ -646,7 +648,7 @@ mod persistence { ]; } - db::static_connection!(ONBOARDING_PAGES, OnboardingPagesDb, [WorkspaceDb]); + db::static_connection!(OnboardingPagesDb, [WorkspaceDb]); impl OnboardingPagesDb { query! { diff --git a/crates/outline_panel/src/outline_panel.rs b/crates/outline_panel/src/outline_panel.rs index ec85fc14a2eefe280afd0d44ed92b4b8502f460c..e35301ee16b3631290e232ac0838dc781e88aebf 100644 --- a/crates/outline_panel/src/outline_panel.rs +++ b/crates/outline_panel/src/outline_panel.rs @@ -2,7 +2,7 @@ mod outline_panel_settings; use anyhow::Context as _; use collections::{BTreeSet, HashMap, HashSet, hash_map}; -use db::kvp::KEY_VALUE_STORE; +use db::kvp::KeyValueStore; use editor::{ AnchorRangeExt, Bias, DisplayPoint, Editor, EditorEvent, ExcerptId, ExcerptRange, MultiBufferSnapshot, RangeToAnchorExt, SelectionEffects, @@ -693,16 +693,18 @@ impl OutlinePanel { .ok() .flatten() { - Some(serialization_key) => cx - .background_spawn(async move { KEY_VALUE_STORE.read_kvp(&serialization_key) }) - .await - .context("loading outline panel") - .log_err() - .flatten() - .map(|panel| serde_json::from_str::(&panel)) - .transpose() - .log_err() - .flatten(), + Some(serialization_key) => { + let kvp = cx.update(|_, cx| KeyValueStore::global(cx))?; + cx.background_spawn(async move { kvp.read_kvp(&serialization_key) }) + .await + .context("loading outline panel") + .log_err() + .flatten() + .map(|panel| serde_json::from_str::(&panel)) + .transpose() + .log_err() + .flatten() + } None => None, }; @@ -958,14 +960,14 @@ impl OutlinePanel { }; let width = self.width; let active = Some(self.active); + let kvp = KeyValueStore::global(cx); self.pending_serialization = cx.background_spawn( async move { - KEY_VALUE_STORE - .write_kvp( - serialization_key, - serde_json::to_string(&SerializedOutlinePanel { width, active })?, - ) - .await?; + kvp.write_kvp( + serialization_key, + serde_json::to_string(&SerializedOutlinePanel { width, active })?, + ) + .await?; anyhow::Ok(()) } .log_err(), diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 96e680c0d1648bd4cf337cbc55e321e3948c217a..304398eca6dd05dfad8cc3e9788ad091b41baa54 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -5,7 +5,7 @@ use anyhow::{Context as _, Result}; use client::{ErrorCode, ErrorExt}; use collections::{BTreeSet, HashMap, hash_map}; use command_palette_hooks::CommandPaletteFilter; -use db::kvp::KEY_VALUE_STORE; +use db::kvp::KeyValueStore; use editor::{ Editor, EditorEvent, MultiBufferOffset, items::{ @@ -999,16 +999,18 @@ impl ProjectPanel { .ok() .flatten() { - Some(serialization_key) => cx - .background_spawn(async move { KEY_VALUE_STORE.read_kvp(&serialization_key) }) - .await - .context("loading project panel") - .log_err() - .flatten() - .map(|panel| serde_json::from_str::(&panel)) - .transpose() - .log_err() - .flatten(), + Some(serialization_key) => { + let kvp = cx.update(|_, cx| KeyValueStore::global(cx))?; + cx.background_spawn(async move { kvp.read_kvp(&serialization_key) }) + .await + .context("loading project panel") + .log_err() + .flatten() + .map(|panel| serde_json::from_str::(&panel)) + .transpose() + .log_err() + .flatten() + } None => None, }; @@ -1114,14 +1116,14 @@ impl ProjectPanel { return; }; let width = self.width; + let kvp = KeyValueStore::global(cx); self.pending_serialization = cx.background_spawn( async move { - KEY_VALUE_STORE - .write_kvp( - serialization_key, - serde_json::to_string(&SerializedProjectPanel { width })?, - ) - .await?; + kvp.write_kvp( + serialization_key, + serde_json::to_string(&SerializedProjectPanel { width })?, + ) + .await?; anyhow::Ok(()) } .log_err(), diff --git a/crates/recent_projects/src/dev_container_suggest.rs b/crates/recent_projects/src/dev_container_suggest.rs index fd7fe4757a0f629579c5a5fdae7b16f12f1bba7a..b134833688fa081c288e5b90a371bc3c462401f0 100644 --- a/crates/recent_projects/src/dev_container_suggest.rs +++ b/crates/recent_projects/src/dev_container_suggest.rs @@ -1,9 +1,10 @@ -use db::kvp::KEY_VALUE_STORE; +use db::kvp::KeyValueStore; use dev_container::find_configs_in_snapshot; use gpui::{SharedString, Window}; use project::{Project, WorktreeId}; use std::sync::LazyLock; use ui::prelude::*; +use util::ResultExt; use util::rel_path::RelPath; use workspace::Workspace; use workspace::notifications::NotificationId; @@ -61,7 +62,7 @@ pub fn suggest_on_worktree_updated( let project_path = abs_path.to_string_lossy().to_string(); let key_for_dismiss = project_devcontainer_key(&project_path); - let already_dismissed = KEY_VALUE_STORE + let already_dismissed = KeyValueStore::global(cx) .read_kvp(&key_for_dismiss) .ok() .flatten() @@ -98,9 +99,13 @@ pub fn suggest_on_worktree_updated( .secondary_on_click({ move |_window, cx| { let key = key_for_dismiss.clone(); - db::write_and_log(cx, move || { - KEY_VALUE_STORE.write_kvp(key, "dismissed".to_string()) - }); + let kvp = KeyValueStore::global(cx); + cx.background_spawn(async move { + kvp.write_kvp(key, "dismissed".to_string()) + .await + .log_err(); + }) + .detach(); } }) }) diff --git a/crates/recent_projects/src/recent_projects.rs b/crates/recent_projects/src/recent_projects.rs index 92dc632d1c309a49ae984115bb1f753ca66164bc..4904d79ff73b903e01e6022fa47d7a7928213a5e 100644 --- a/crates/recent_projects/src/recent_projects.rs +++ b/crates/recent_projects/src/recent_projects.rs @@ -46,7 +46,7 @@ use ui::{ use util::{ResultExt, paths::PathExt}; use workspace::{ HistoryManager, ModalView, MultiWorkspace, OpenOptions, OpenVisible, PathList, - SerializedWorkspaceLocation, WORKSPACE_DB, Workspace, WorkspaceId, + SerializedWorkspaceLocation, Workspace, WorkspaceDb, WorkspaceId, notifications::DetachAndPromptErr, with_active_or_new_workspace, }; use zed_actions::{OpenDevContainer, OpenRecent, OpenRemote}; @@ -88,8 +88,9 @@ pub async fn get_recent_projects( current_workspace_id: Option, limit: Option, fs: Arc, + db: &WorkspaceDb, ) -> Vec { - let workspaces = WORKSPACE_DB + let workspaces = db .recent_workspaces_on_disk(fs.as_ref()) .await .unwrap_or_default(); @@ -138,8 +139,8 @@ pub async fn get_recent_projects( } } -pub async fn delete_recent_project(workspace_id: WorkspaceId) { - let _ = WORKSPACE_DB.delete_workspace_by_id(workspace_id).await; +pub async fn delete_recent_project(workspace_id: WorkspaceId, db: &WorkspaceDb) { + let _ = db.delete_workspace_by_id(workspace_id).await; } fn get_open_folders(workspace: &Workspace, cx: &App) -> Vec { @@ -508,9 +509,10 @@ impl RecentProjects { let _subscription = cx.subscribe(&picker, |_, _, _, cx| cx.emit(DismissEvent)); // We do not want to block the UI on a potentially lengthy call to DB, so we're gonna swap // out workspace locations once the future runs to completion. + let db = WorkspaceDb::global(cx); cx.spawn_in(window, async move |this, cx| { let Some(fs) = fs else { return }; - let workspaces = WORKSPACE_DB + let workspaces = db .recent_workspaces_on_disk(fs.as_ref()) .await .log_err() @@ -1500,13 +1502,11 @@ impl RecentProjectsDelegate { .workspace .upgrade() .map(|ws| ws.read(cx).app_state().fs.clone()); + let db = WorkspaceDb::global(cx); cx.spawn_in(window, async move |this, cx| { - WORKSPACE_DB - .delete_workspace_by_id(workspace_id) - .await - .log_err(); + db.delete_workspace_by_id(workspace_id).await.log_err(); let Some(fs) = fs else { return }; - let workspaces = WORKSPACE_DB + let workspaces = db .recent_workspaces_on_disk(fs.as_ref()) .await .unwrap_or_default(); diff --git a/crates/session/src/session.rs b/crates/session/src/session.rs index de6be034f9732f2c24dd860ebccd0c677d4fc623..76f2398b382cf1c1a6d2f8da687f7e352acb8c3b 100644 --- a/crates/session/src/session.rs +++ b/crates/session/src/session.rs @@ -1,4 +1,4 @@ -use db::kvp::KEY_VALUE_STORE; +use db::kvp::KeyValueStore; use gpui::{App, AppContext as _, Context, Subscription, Task, WindowId}; use util::ResultExt; @@ -12,20 +12,19 @@ const SESSION_ID_KEY: &str = "session_id"; const SESSION_WINDOW_STACK_KEY: &str = "session_window_stack"; impl Session { - pub async fn new(session_id: String) -> Self { - let old_session_id = KEY_VALUE_STORE.read_kvp(SESSION_ID_KEY).ok().flatten(); + pub async fn new(session_id: String, db: KeyValueStore) -> Self { + let old_session_id = db.read_kvp(SESSION_ID_KEY).ok().flatten(); - KEY_VALUE_STORE - .write_kvp(SESSION_ID_KEY.to_string(), session_id.clone()) + db.write_kvp(SESSION_ID_KEY.to_string(), session_id.clone()) .await .log_err(); - let old_window_ids = KEY_VALUE_STORE + let old_window_ids = db .read_kvp(SESSION_WINDOW_STACK_KEY) .ok() .flatten() .and_then(|json| serde_json::from_str::>(&json).ok()) - .map(|vec| { + .map(|vec: Vec| { vec.into_iter() .map(WindowId::from) .collect::>() @@ -72,25 +71,28 @@ impl AppSession { let _subscriptions = vec![cx.on_app_quit(Self::app_will_quit)]; #[cfg(not(any(test, feature = "test-support")))] - let _serialization_task = cx.spawn(async move |_, cx| { - // Disabled in tests: the infinite loop bypasses "parking forbidden" checks, - // causing tests to hang instead of panicking. - { - let mut current_window_stack = Vec::new(); - loop { - if let Some(windows) = cx.update(|cx| window_stack(cx)) - && windows != current_window_stack - { - store_window_stack(&windows).await; - current_window_stack = windows; + let _serialization_task = { + let db = KeyValueStore::global(cx); + cx.spawn(async move |_, cx| { + // Disabled in tests: the infinite loop bypasses "parking forbidden" checks, + // causing tests to hang instead of panicking. + { + let mut current_window_stack = Vec::new(); + loop { + if let Some(windows) = cx.update(|cx| window_stack(cx)) + && windows != current_window_stack + { + store_window_stack(db.clone(), &windows).await; + current_window_stack = windows; + } + + cx.background_executor() + .timer(std::time::Duration::from_millis(500)) + .await; } - - cx.background_executor() - .timer(std::time::Duration::from_millis(500)) - .await; } - } - }); + }) + }; #[cfg(any(test, feature = "test-support"))] let _serialization_task = Task::ready(()); @@ -104,7 +106,8 @@ impl AppSession { fn app_will_quit(&mut self, cx: &mut Context) -> Task<()> { if let Some(window_stack) = window_stack(cx) { - cx.background_spawn(async move { store_window_stack(&window_stack).await }) + let db = KeyValueStore::global(cx); + cx.background_spawn(async move { store_window_stack(db, &window_stack).await }) } else { Task::ready(()) } @@ -137,10 +140,9 @@ fn window_stack(cx: &App) -> Option> { ) } -async fn store_window_stack(windows: &[u64]) { +async fn store_window_stack(db: KeyValueStore, windows: &[u64]) { if let Ok(window_ids_json) = serde_json::to_string(windows) { - KEY_VALUE_STORE - .write_kvp(SESSION_WINDOW_STACK_KEY.to_string(), window_ids_json) + db.write_kvp(SESSION_WINDOW_STACK_KEY.to_string(), window_ids_json) .await .log_err(); } diff --git a/crates/terminal_view/src/persistence.rs b/crates/terminal_view/src/persistence.rs index 1c215c1703278c8e54046ea305273242570c6b7f..8a022e4f74d52e993f2256dadc546a126fe23c9b 100644 --- a/crates/terminal_view/src/persistence.rs +++ b/crates/terminal_view/src/persistence.rs @@ -425,7 +425,7 @@ impl Domain for TerminalDb { ]; } -db::static_connection!(TERMINAL_DB, TerminalDb, [WorkspaceDb]); +db::static_connection!(TerminalDb, [WorkspaceDb]); impl TerminalDb { query! { diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index b3c1f0bf1754d9b0d814bea3dff48b5a7f205613..81dbbcb741fe3f3091b4488636c3a7b3cada487b 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -8,7 +8,7 @@ use crate::{ }; use breadcrumbs::Breadcrumbs; use collections::HashMap; -use db::kvp::KEY_VALUE_STORE; +use db::kvp::KeyValueStore; use futures::{channel::oneshot, future::join_all}; use gpui::{ Action, AnyView, App, AsyncApp, AsyncWindowContext, Context, Corner, Entity, EventEmitter, @@ -250,16 +250,17 @@ impl TerminalPanel { ) -> Result> { let mut terminal_panel = None; - if let Some((database_id, serialization_key)) = workspace - .read_with(&cx, |workspace, _| { + if let Some((database_id, serialization_key, kvp)) = workspace + .read_with(&cx, |workspace, cx| { workspace .database_id() .zip(TerminalPanel::serialization_key(workspace)) + .map(|(id, key)| (id, key, KeyValueStore::global(cx))) }) .ok() .flatten() && let Some(serialized_panel) = cx - .background_spawn(async move { KEY_VALUE_STORE.read_kvp(&serialization_key) }) + .background_spawn(async move { kvp.read_kvp(&serialization_key) }) .await .log_err() .flatten() @@ -939,6 +940,7 @@ impl TerminalPanel { else { return; }; + let kvp = KeyValueStore::global(cx); self.pending_serialization = cx.spawn(async move |terminal_panel, cx| { cx.background_executor() .timer(Duration::from_millis(50)) @@ -953,17 +955,16 @@ impl TerminalPanel { }); cx.background_spawn( async move { - KEY_VALUE_STORE - .write_kvp( - serialization_key, - serde_json::to_string(&SerializedTerminalPanel { - items, - active_item_id: None, - height, - width, - })?, - ) - .await?; + kvp.write_kvp( + serialization_key, + serde_json::to_string(&SerializedTerminalPanel { + items, + active_item_id: None, + height, + width, + })?, + ) + .await?; anyhow::Ok(()) } .log_err(), diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index c1a6542fbc17526eed4914815738212cf74eca8f..fc1b0b12f9238e776164b23776eb5cea0208270f 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -15,7 +15,7 @@ use gpui::{ }; use itertools::Itertools; use menu; -use persistence::TERMINAL_DB; +use persistence::TerminalDb; use project::{Project, ProjectEntryId, search::SearchQuery}; use schemars::JsonSchema; use serde::Deserialize; @@ -1676,11 +1676,11 @@ impl Item for TerminalView { log::debug!( "Updating workspace id for the terminal, old: {old_id:?}, new: {new_id:?}", ); - cx.background_spawn(TERMINAL_DB.update_workspace_id( - new_id, - old_id, - cx.entity_id().as_u64(), - )) + let db = TerminalDb::global(cx); + let entity_id = cx.entity_id().as_u64(); + cx.background_spawn(async move { + db.update_workspace_id(new_id, old_id, entity_id).await + }) .detach(); } self.workspace_id = workspace.database_id(); @@ -1703,7 +1703,8 @@ impl SerializableItem for TerminalView { _window: &mut Window, cx: &mut App, ) -> Task> { - delete_unloaded_items(alive_items, workspace_id, "terminals", &TERMINAL_DB, cx) + let db = TerminalDb::global(cx); + delete_unloaded_items(alive_items, workspace_id, "terminals", &db, cx) } fn serialize( @@ -1728,14 +1729,13 @@ impl SerializableItem for TerminalView { let custom_title = self.custom_title.clone(); self.needs_serialize = false; + let db = TerminalDb::global(cx); Some(cx.background_spawn(async move { if let Some(cwd) = cwd { - TERMINAL_DB - .save_working_directory(item_id, workspace_id, cwd) + db.save_working_directory(item_id, workspace_id, cwd) .await?; } - TERMINAL_DB - .save_custom_title(item_id, workspace_id, custom_title) + db.save_custom_title(item_id, workspace_id, custom_title) .await?; Ok(()) })) @@ -1756,7 +1756,8 @@ impl SerializableItem for TerminalView { window.spawn(cx, async move |cx| { let (cwd, custom_title) = cx .update(|_window, cx| { - let from_db = TERMINAL_DB + let db = TerminalDb::global(cx); + let from_db = db .get_working_directory(item_id, workspace_id) .log_err() .flatten(); @@ -1770,7 +1771,7 @@ impl SerializableItem for TerminalView { .upgrade() .and_then(|workspace| default_working_directory(workspace.read(cx), cx)) }; - let custom_title = TERMINAL_DB + let custom_title = db .get_custom_title(item_id, workspace_id) .log_err() .flatten() diff --git a/crates/title_bar/src/onboarding_banner.rs b/crates/title_bar/src/onboarding_banner.rs index ac3e80e179babc8ae9ee1c86c93c11f57cedb9b7..f96ce3a92740da4a0aac3dc154384f20f3b05eb0 100644 --- a/crates/title_bar/src/onboarding_banner.rs +++ b/crates/title_bar/src/onboarding_banner.rs @@ -44,7 +44,7 @@ impl OnboardingBanner { subtitle: subtitle.or(Some(SharedString::from("Introducing:"))), }, visible_when: None, - dismissed: get_dismissed(source), + dismissed: get_dismissed(source, cx), } } @@ -75,9 +75,9 @@ fn dismissed_at_key(source: &str) -> String { } } -fn get_dismissed(source: &str) -> bool { +fn get_dismissed(source: &str, cx: &App) -> bool { let dismissed_at = dismissed_at_key(source); - db::kvp::KEY_VALUE_STORE + db::kvp::KeyValueStore::global(cx) .read_kvp(&dismissed_at) .log_err() .is_some_and(|dismissed| dismissed.is_some()) @@ -85,9 +85,10 @@ fn get_dismissed(source: &str) -> bool { fn persist_dismissed(source: &str, cx: &mut App) { let dismissed_at = dismissed_at_key(source); - cx.spawn(async |_| { + let kvp = db::kvp::KeyValueStore::global(cx); + cx.spawn(async move |_| { let time = chrono::Utc::now().to_rfc3339(); - db::kvp::KEY_VALUE_STORE.write_kvp(dismissed_at, time).await + kvp.write_kvp(dismissed_at, time).await }) .detach_and_log_err(cx); } @@ -105,7 +106,8 @@ pub fn restore_banner(cx: &mut App) { let source = &cx.global::().entity.read(cx).source; let dismissed_at = dismissed_at_key(source); - cx.spawn(async |_| db::kvp::KEY_VALUE_STORE.delete_kvp(dismissed_at).await) + let kvp = db::kvp::KeyValueStore::global(cx); + cx.spawn(async move |_| kvp.delete_kvp(dismissed_at).await) .detach_and_log_err(cx); } diff --git a/crates/toolchain_selector/src/active_toolchain.rs b/crates/toolchain_selector/src/active_toolchain.rs index 36af60e0f792f5146b9b573bb6a060a8461fe117..e3766e73bbc29d9548f785018e9f4aa40ab968a1 100644 --- a/crates/toolchain_selector/src/active_toolchain.rs +++ b/crates/toolchain_selector/src/active_toolchain.rs @@ -202,15 +202,15 @@ impl ActiveToolchain { this.worktree_for_id(worktree_id, cx) .map(|worktree| worktree.read(cx).abs_path()) })?; - workspace::WORKSPACE_DB - .set_toolchain( - workspace_id, - worktree_root_path, - relative_path.clone(), - toolchain.clone(), - ) - .await - .ok()?; + let db = cx.update(|_, cx| workspace::WorkspaceDb::global(cx)).ok()?; + db.set_toolchain( + workspace_id, + worktree_root_path, + relative_path.clone(), + toolchain.clone(), + ) + .await + .ok()?; project .update(cx, |this, cx| { this.activate_toolchain( diff --git a/crates/toolchain_selector/src/toolchain_selector.rs b/crates/toolchain_selector/src/toolchain_selector.rs index f7b451e876cb945633a951b4c00920d2ce59f455..7447975aa835c7a4c73068d20b55619f7db5231c 100644 --- a/crates/toolchain_selector/src/toolchain_selector.rs +++ b/crates/toolchain_selector/src/toolchain_selector.rs @@ -920,16 +920,16 @@ impl PickerDelegate for ToolchainSelectorDelegate { let worktree_abs_path_root = self.worktree_abs_path_root.clone(); let path = self.relative_path.clone(); let relative_path = self.relative_path.clone(); + let db = workspace::WorkspaceDb::global(cx); cx.spawn_in(window, async move |_, cx| { - workspace::WORKSPACE_DB - .set_toolchain( - workspace_id, - worktree_abs_path_root, - relative_path, - toolchain.clone(), - ) - .await - .log_err(); + db.set_toolchain( + workspace_id, + worktree_abs_path_root, + relative_path, + toolchain.clone(), + ) + .await + .log_err(); workspace .update(cx, |this, cx| { this.project().update(cx, |this, cx| { diff --git a/crates/vim/src/state.rs b/crates/vim/src/state.rs index 9ba744de6855e101a1871ddcf0a84cc3fc931830..b0d21badc730dae973ab43786d46c7682ecc5263 100644 --- a/crates/vim/src/state.rs +++ b/crates/vim/src/state.rs @@ -322,10 +322,11 @@ impl MarksState { let Some(workspace_id) = this.update(cx, |this, cx| this.workspace_id(cx)).ok()? else { return None; }; + let db = cx.update(|cx| VimDb::global(cx)); let (marks, paths) = cx .background_spawn(async move { - let marks = DB.get_marks(workspace_id)?; - let paths = DB.get_global_marks_paths(workspace_id)?; + let marks = db.get_marks(workspace_id)?; + let paths = db.get_global_marks_paths(workspace_id)?; anyhow::Ok((marks, paths)) }) .await @@ -444,8 +445,9 @@ impl MarksState { if let Some(workspace_id) = self.workspace_id(cx) { let path = path.clone(); let key = key.clone(); + let db = VimDb::global(cx); cx.background_spawn(async move { - DB.set_global_mark_path(workspace_id, key, path).await + db.set_global_mark_path(workspace_id, key, path).await }) .detach_and_log_err(cx); } @@ -461,8 +463,9 @@ impl MarksState { self.serialized_marks.insert(path.clone(), new_points); if let Some(workspace_id) = self.workspace_id(cx) { + let db = VimDb::global(cx); cx.background_spawn(async move { - DB.set_marks(workspace_id, path.clone(), to_write).await?; + db.set_marks(workspace_id, path.clone(), to_write).await?; anyhow::Ok(()) }) .detach_and_log_err(cx); @@ -655,8 +658,9 @@ impl MarksState { let path = if let Some(target) = self.global_marks.get(&mark_name.clone()) { let name = mark_name.clone(); if let Some(workspace_id) = self.workspace_id(cx) { + let db = VimDb::global(cx); cx.background_spawn(async move { - DB.delete_global_marks_path(workspace_id, name).await + db.delete_global_marks_path(workspace_id, name).await }) .detach_and_log_err(cx); } @@ -696,7 +700,8 @@ impl MarksState { .get_mut(&path) .map(|m| m.remove(&mark_name.clone())); if let Some(workspace_id) = self.workspace_id(cx) { - cx.background_spawn(async move { DB.delete_mark(workspace_id, path, mark_name).await }) + let db = VimDb::global(cx); + cx.background_spawn(async move { db.delete_mark(workspace_id, path, mark_name).await }) .detach_and_log_err(cx); } } @@ -1764,7 +1769,7 @@ impl Domain for VimDb { ]; } -db::static_connection!(DB, VimDb, [WorkspaceDb]); +db::static_connection!(VimDb, [WorkspaceDb]); struct SerializedMark { path: Arc, diff --git a/crates/workspace/src/history_manager.rs b/crates/workspace/src/history_manager.rs index 52f6be08b5972ab77a384aa8c0cf34fb29c2753c..9b03a3252d32793e12495817c2d9801d610d3ce4 100644 --- a/crates/workspace/src/history_manager.rs +++ b/crates/workspace/src/history_manager.rs @@ -7,7 +7,8 @@ use ui::{App, Context}; use util::{ResultExt, paths::PathExt}; use crate::{ - NewWindow, SerializedWorkspaceLocation, WORKSPACE_DB, WorkspaceId, path_list::PathList, + NewWindow, SerializedWorkspaceLocation, WorkspaceId, path_list::PathList, + persistence::WorkspaceDb, }; pub fn init(fs: Arc, cx: &mut App) { @@ -40,8 +41,9 @@ impl HistoryManager { } fn init(this: Entity, fs: Arc, cx: &App) { + let db = WorkspaceDb::global(cx); cx.spawn(async move |cx| { - let recent_folders = WORKSPACE_DB + let recent_folders = db .recent_workspaces_on_disk(fs.as_ref()) .await .unwrap_or_default() @@ -102,6 +104,7 @@ impl HistoryManager { .map(|entry| entry.path.clone()) .collect::>(); let user_removed = cx.update_jump_list(menus, entries); + let db = WorkspaceDb::global(cx); cx.spawn(async move |this, cx| { let user_removed = user_removed.await; if user_removed.is_empty() { @@ -119,7 +122,7 @@ impl HistoryManager { } }) { for id in deleted_ids.iter() { - WORKSPACE_DB.delete_workspace_by_id(*id).await.log_err(); + db.delete_workspace_by_id(*id).await.log_err(); } } }) diff --git a/crates/workspace/src/multi_workspace.rs b/crates/workspace/src/multi_workspace.rs index 2028a2e28c1b1a539562a195b0d3737a9f739fc5..fb87ec241291b8b249b2b66a8a47ca464f6cdd0c 100644 --- a/crates/workspace/src/multi_workspace.rs +++ b/crates/workspace/src/multi_workspace.rs @@ -387,8 +387,9 @@ impl MultiWorkspace { active_workspace_id: self.workspace().read(cx).database_id(), sidebar_open: self.sidebar_open, }; + let kvp = db::kvp::KeyValueStore::global(cx); self._serialize_task = Some(cx.background_spawn(async move { - crate::persistence::write_multi_workspace_state(window_id, state).await; + crate::persistence::write_multi_workspace_state(&kvp, window_id, state).await; })); } @@ -560,8 +561,9 @@ impl MultiWorkspace { self.focus_active_workspace(window, cx); let weak_workspace = new_workspace.downgrade(); + let db = crate::persistence::WorkspaceDb::global(cx); cx.spawn_in(window, async move |this, cx| { - let workspace_id = crate::persistence::DB.next_id().await.unwrap(); + let workspace_id = db.next_id().await.unwrap(); let workspace = weak_workspace.upgrade().unwrap(); let task: Task<()> = this .update_in(cx, |this, window, cx| { @@ -571,9 +573,9 @@ impl MultiWorkspace { workspace.set_database_id(workspace_id); }); this.serialize(cx); + let db = db.clone(); cx.background_spawn(async move { - crate::persistence::DB - .set_session_binding(workspace_id, session_id, Some(window_id)) + db.set_session_binding(workspace_id, session_id, Some(window_id)) .await .log_err(); }) @@ -597,13 +599,13 @@ impl MultiWorkspace { } if let Some(workspace_id) = removed_workspace.read(cx).database_id() { + let db = crate::persistence::WorkspaceDb::global(cx); self.pending_removal_tasks.retain(|task| !task.is_ready()); self.pending_removal_tasks .push(cx.background_spawn(async move { // Clear the session binding instead of deleting the row so // the workspace still appears in the recent-projects list. - crate::persistence::DB - .set_session_binding(workspace_id, None, None) + db.set_session_binding(workspace_id, None, None) .await .log_err(); })); diff --git a/crates/workspace/src/persistence.rs b/crates/workspace/src/persistence.rs index 7202a216cb7da94637c2bd24a76c61bb6c36c73b..020f04578b0dc325c09130b195f4cce95393126a 100644 --- a/crates/workspace/src/persistence.rs +++ b/crates/workspace/src/persistence.rs @@ -14,7 +14,7 @@ use fs::Fs; use anyhow::{Context as _, Result, bail}; use collections::{HashMap, HashSet, IndexSet}; use db::{ - kvp::KEY_VALUE_STORE, + kvp::KeyValueStore, query, sqlez::{connection::Connection, domain::Domain}, sqlez_macros::sql, @@ -174,8 +174,8 @@ impl Column for SerializedWindowBounds { const DEFAULT_WINDOW_BOUNDS_KEY: &str = "default_window_bounds"; -pub fn read_default_window_bounds() -> Option<(Uuid, WindowBounds)> { - let json_str = KEY_VALUE_STORE +pub fn read_default_window_bounds(kvp: &KeyValueStore) -> Option<(Uuid, WindowBounds)> { + let json_str = kvp .read_kvp(DEFAULT_WINDOW_BOUNDS_KEY) .log_err() .flatten()?; @@ -186,13 +186,13 @@ pub fn read_default_window_bounds() -> Option<(Uuid, WindowBounds)> { } pub async fn write_default_window_bounds( + kvp: &KeyValueStore, bounds: WindowBounds, display_uuid: Uuid, ) -> anyhow::Result<()> { let persisted = WindowBoundsJson::from(bounds); let json_str = serde_json::to_string(&(display_uuid, persisted))?; - KEY_VALUE_STORE - .write_kvp(DEFAULT_WINDOW_BOUNDS_KEY.to_string(), json_str) + kvp.write_kvp(DEFAULT_WINDOW_BOUNDS_KEY.to_string(), json_str) .await?; Ok(()) } @@ -290,12 +290,9 @@ impl From for WindowBounds { } } -fn multi_workspace_states() -> db::kvp::ScopedKeyValueStore<'static> { - KEY_VALUE_STORE.scoped("multi_workspace_state") -} - -fn read_multi_workspace_state(window_id: WindowId) -> model::MultiWorkspaceState { - multi_workspace_states() +fn read_multi_workspace_state(window_id: WindowId, cx: &App) -> model::MultiWorkspaceState { + let kvp = KeyValueStore::global(cx); + kvp.scoped("multi_workspace_state") .read(&window_id.as_u64().to_string()) .log_err() .flatten() @@ -303,9 +300,13 @@ fn read_multi_workspace_state(window_id: WindowId) -> model::MultiWorkspaceState .unwrap_or_default() } -pub async fn write_multi_workspace_state(window_id: WindowId, state: model::MultiWorkspaceState) { +pub async fn write_multi_workspace_state( + kvp: &KeyValueStore, + window_id: WindowId, + state: model::MultiWorkspaceState, +) { if let Ok(json_str) = serde_json::to_string(&state) { - multi_workspace_states() + kvp.scoped("multi_workspace_state") .write(window_id.as_u64().to_string(), json_str) .await .log_err(); @@ -314,6 +315,7 @@ pub async fn write_multi_workspace_state(window_id: WindowId, state: model::Mult pub fn read_serialized_multi_workspaces( session_workspaces: Vec, + cx: &App, ) -> Vec { let mut window_groups: Vec> = Vec::new(); let mut window_id_to_group: HashMap = HashMap::default(); @@ -338,7 +340,7 @@ pub fn read_serialized_multi_workspaces( .map(|group| { let window_id = group.first().and_then(|sw| sw.window_id); let state = window_id - .map(read_multi_workspace_state) + .map(|wid| read_multi_workspace_state(wid, cx)) .unwrap_or_default(); model::SerializedMultiWorkspace { workspaces: group, @@ -350,19 +352,18 @@ pub fn read_serialized_multi_workspaces( const DEFAULT_DOCK_STATE_KEY: &str = "default_dock_state"; -pub fn read_default_dock_state() -> Option { - let json_str = KEY_VALUE_STORE - .read_kvp(DEFAULT_DOCK_STATE_KEY) - .log_err() - .flatten()?; +pub fn read_default_dock_state(kvp: &KeyValueStore) -> Option { + let json_str = kvp.read_kvp(DEFAULT_DOCK_STATE_KEY).log_err().flatten()?; serde_json::from_str::(&json_str).ok() } -pub async fn write_default_dock_state(docks: DockStructure) -> anyhow::Result<()> { +pub async fn write_default_dock_state( + kvp: &KeyValueStore, + docks: DockStructure, +) -> anyhow::Result<()> { let json_str = serde_json::to_string(&docks)?; - KEY_VALUE_STORE - .write_kvp(DEFAULT_DOCK_STATE_KEY.to_string(), json_str) + kvp.write_kvp(DEFAULT_DOCK_STATE_KEY.to_string(), json_str) .await?; Ok(()) } @@ -980,7 +981,7 @@ impl Domain for WorkspaceDb { } } -db::static_connection!(DB, WorkspaceDb, []); +db::static_connection!(WorkspaceDb, []); impl WorkspaceDb { /// Returns a serialized workspace for the given worktree_roots. If the passed array @@ -2252,7 +2253,7 @@ impl WorkspaceDb { use db::sqlez::statement::Statement; use itertools::Itertools as _; - DB.clear_trusted_worktrees() + self.clear_trusted_worktrees() .await .context("clearing previous trust state")?; @@ -2319,7 +2320,7 @@ VALUES {placeholders};"# } pub fn fetch_trusted_worktrees(&self) -> Result { - let trusted_worktrees = DB.trusted_worktrees()?; + let trusted_worktrees = self.trusted_worktrees()?; Ok(trusted_worktrees .into_iter() .filter_map(|(abs_path, user_name, host_name)| { @@ -2450,7 +2451,7 @@ mod tests { cx.run_until_parked(); // Read back the persisted state and check that the active workspace ID was written. - let state_after_add = read_multi_workspace_state(window_id); + let state_after_add = cx.update(|_, cx| read_multi_workspace_state(window_id, cx)); let active_workspace2_db_id = workspace2.read_with(cx, |ws, _| ws.database_id()); assert_eq!( state_after_add.active_workspace_id, active_workspace2_db_id, @@ -2465,7 +2466,7 @@ mod tests { cx.run_until_parked(); - let state_after_remove = read_multi_workspace_state(window_id); + let state_after_remove = cx.update(|_, cx| read_multi_workspace_state(window_id, cx)); let remaining_db_id = multi_workspace.read_with(cx, |mw, cx| mw.workspace().read(cx).database_id()); assert_eq!( @@ -3882,14 +3883,17 @@ mod tests { } #[gpui::test] - async fn test_read_serialized_multi_workspaces_with_state() { + async fn test_read_serialized_multi_workspaces_with_state(cx: &mut gpui::TestAppContext) { use crate::persistence::model::MultiWorkspaceState; // Write multi-workspace state for two windows via the scoped KVP. let window_10 = WindowId::from(10u64); let window_20 = WindowId::from(20u64); + let kvp = cx.update(|cx| KeyValueStore::global(cx)); + write_multi_workspace_state( + &kvp, window_10, MultiWorkspaceState { active_workspace_id: Some(WorkspaceId(2)), @@ -3899,6 +3903,7 @@ mod tests { .await; write_multi_workspace_state( + &kvp, window_20, MultiWorkspaceState { active_workspace_id: Some(WorkspaceId(3)), @@ -3935,7 +3940,7 @@ mod tests { }, ]; - let results = read_serialized_multi_workspaces(session_workspaces); + let results = cx.update(|cx| read_serialized_multi_workspaces(session_workspaces, cx)); // Should produce 3 groups: window 10, window 20, and the orphan. assert_eq!(results.len(), 3); @@ -3981,14 +3986,16 @@ mod tests { let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone()); + let db = cx.update(|_, cx| WorkspaceDb::global(cx)); + // Assign a database_id so serialization will actually persist. - let workspace_id = DB.next_id().await.unwrap(); + let workspace_id = db.next_id().await.unwrap(); workspace.update(cx, |ws, _cx| { ws.set_database_id(workspace_id); }); // Mutate some workspace state. - DB.set_centered_layout(workspace_id, true).await.unwrap(); + db.set_centered_layout(workspace_id, true).await.unwrap(); // Call flush_serialization and await the returned task directly // (without run_until_parked — the point is that awaiting the task @@ -4000,7 +4007,7 @@ mod tests { task.await; // Read the workspace back from the DB and verify serialization happened. - let serialized = DB.workspace_for_id(workspace_id); + let serialized = db.workspace_for_id(workspace_id); assert!( serialized.is_some(), "flush_serialization should have persisted the workspace to DB" @@ -4053,7 +4060,7 @@ mod tests { ); // The multi-workspace state should record it as the active workspace. - let state = read_multi_workspace_state(window_id); + let state = cx.update(|_, cx| read_multi_workspace_state(window_id, cx)); assert_eq!( state.active_workspace_id, new_workspace_db_id, "Serialized active_workspace_id should match the new workspace's database_id" @@ -4062,7 +4069,8 @@ mod tests { // The individual workspace row should exist with real data // (not just the bare DEFAULT VALUES row from next_id). let workspace_id = new_workspace_db_id.unwrap(); - let serialized = DB.workspace_for_id(workspace_id); + let db = cx.update(|_, cx| WorkspaceDb::global(cx)); + let serialized = db.workspace_for_id(workspace_id); assert!( serialized.is_some(), "Newly created workspace should be fully serialized in the DB after database_id assignment" @@ -4095,8 +4103,10 @@ mod tests { mw.set_random_database_id(cx); }); + let db = cx.update(|_, cx| WorkspaceDb::global(cx)); + // Get a real DB id for workspace2 so the row actually exists. - let workspace2_db_id = DB.next_id().await.unwrap(); + let workspace2_db_id = db.next_id().await.unwrap(); multi_workspace.update_in(cx, |mw, window, cx| { let workspace = cx.new(|cx| crate::Workspace::test_new(project2.clone(), window, cx)); @@ -4108,7 +4118,7 @@ mod tests { // Save a full workspace row to the DB directly. let session_id = format!("remove-test-session-{}", Uuid::new_v4()); - DB.save_workspace(SerializedWorkspace { + db.save_workspace(SerializedWorkspace { id: workspace2_db_id, paths: PathList::new(&[&dir]), location: SerializedWorkspaceLocation::Local, @@ -4125,7 +4135,7 @@ mod tests { .await; assert!( - DB.workspace_for_id(workspace2_db_id).is_some(), + db.workspace_for_id(workspace2_db_id).is_some(), "Workspace2 should exist in DB before removal" ); @@ -4140,11 +4150,11 @@ mod tests { // projects, but the session binding should be cleared so it is not // restored as part of any future session. assert!( - DB.workspace_for_id(workspace2_db_id).is_some(), + db.workspace_for_id(workspace2_db_id).is_some(), "Removed workspace's DB row should be preserved for recent projects" ); - let session_workspaces = DB + let session_workspaces = db .last_session_workspace_locations("remove-test-session", None, fs.as_ref()) .await .unwrap(); @@ -4181,9 +4191,11 @@ mod tests { let project1 = Project::test(fs.clone(), [], cx).await; let project2 = Project::test(fs.clone(), [], cx).await; + let db = cx.update(|cx| WorkspaceDb::global(cx)); + // Get real DB ids so the rows actually exist. - let ws1_id = DB.next_id().await.unwrap(); - let ws2_id = DB.next_id().await.unwrap(); + let ws1_id = db.next_id().await.unwrap(); + let ws2_id = db.next_id().await.unwrap(); let (multi_workspace, cx) = cx.add_window_view(|window, cx| MultiWorkspace::test_new(project1.clone(), window, cx)); @@ -4205,7 +4217,7 @@ mod tests { let session_id = "test-zombie-session"; let window_id_val: u64 = 42; - DB.save_workspace(SerializedWorkspace { + db.save_workspace(SerializedWorkspace { id: ws1_id, paths: PathList::new(&[dir1.path()]), location: SerializedWorkspaceLocation::Local, @@ -4221,7 +4233,7 @@ mod tests { }) .await; - DB.save_workspace(SerializedWorkspace { + db.save_workspace(SerializedWorkspace { id: ws2_id, paths: PathList::new(&[dir2.path()]), location: SerializedWorkspaceLocation::Local, @@ -4245,7 +4257,7 @@ mod tests { cx.run_until_parked(); // The removed workspace should NOT appear in session restoration. - let locations = DB + let locations = db .last_session_workspace_locations(session_id, None, fs.as_ref()) .await .unwrap(); @@ -4281,8 +4293,10 @@ mod tests { let project1 = Project::test(fs.clone(), [], cx).await; let project2 = Project::test(fs.clone(), [], cx).await; + let db = cx.update(|cx| WorkspaceDb::global(cx)); + // Get a real DB id for workspace2 so the row actually exists. - let workspace2_db_id = DB.next_id().await.unwrap(); + let workspace2_db_id = db.next_id().await.unwrap(); let (multi_workspace, cx) = cx.add_window_view(|window, cx| MultiWorkspace::test_new(project1.clone(), window, cx)); @@ -4301,7 +4315,7 @@ mod tests { // Save a full workspace row to the DB directly and let it settle. let session_id = format!("pending-removal-session-{}", Uuid::new_v4()); - DB.save_workspace(SerializedWorkspace { + db.save_workspace(SerializedWorkspace { id: workspace2_db_id, paths: PathList::new(&[&dir]), location: SerializedWorkspaceLocation::Local, @@ -4347,11 +4361,11 @@ mod tests { // The row should still exist (for recent projects), but the session // binding should have been cleared by the pending removal task. assert!( - DB.workspace_for_id(workspace2_db_id).is_some(), + db.workspace_for_id(workspace2_db_id).is_some(), "Workspace row should be preserved for recent projects" ); - let session_workspaces = DB + let session_workspaces = db .last_session_workspace_locations("pending-removal-session", None, fs.as_ref()) .await .unwrap(); @@ -4401,8 +4415,10 @@ mod tests { let workspace_id = new_workspace_db_id.unwrap(); + let db = cx.update(|_, cx| WorkspaceDb::global(cx)); + assert!( - DB.workspace_for_id(workspace_id).is_some(), + db.workspace_for_id(workspace_id).is_some(), "The workspace row should exist in the DB" ); @@ -4413,7 +4429,7 @@ mod tests { cx.executor().advance_clock(Duration::from_millis(200)); cx.run_until_parked(); - let serialized = DB + let serialized = db .workspace_for_id(workspace_id) .expect("workspace row should still exist"); assert!( @@ -4446,7 +4462,8 @@ mod tests { let (multi_workspace, cx) = cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx)); - let workspace_id = DB.next_id().await.unwrap(); + let db = cx.update(|_, cx| WorkspaceDb::global(cx)); + let workspace_id = db.next_id().await.unwrap(); multi_workspace.update_in(cx, |mw, _, cx| { mw.workspace().update(cx, |ws, _cx| { ws.set_database_id(workspace_id); @@ -4459,7 +4476,7 @@ mod tests { }); task.await; - let after = DB + let after = db .workspace_for_id(workspace_id) .expect("workspace row should exist after flush_serialization"); assert!( diff --git a/crates/workspace/src/welcome.rs b/crates/workspace/src/welcome.rs index 92f1cb4840731bedda5b0b6751f44bfdcdb8ea52..1b0566bf561b80137bf222a9d7c3348012cfce27 100644 --- a/crates/workspace/src/welcome.rs +++ b/crates/workspace/src/welcome.rs @@ -1,6 +1,7 @@ use crate::{ - NewFile, Open, PathList, SerializedWorkspaceLocation, WORKSPACE_DB, Workspace, WorkspaceId, + NewFile, Open, PathList, SerializedWorkspaceLocation, Workspace, WorkspaceId, item::{Item, ItemEvent}, + persistence::WorkspaceDb, }; use chrono::{DateTime, Utc}; use git::Clone as GitClone; @@ -271,9 +272,10 @@ impl WelcomePage { let fs = workspace .upgrade() .map(|ws| ws.read(cx).app_state().fs.clone()); + let db = WorkspaceDb::global(cx); cx.spawn_in(window, async move |this: WeakEntity, cx| { let Some(fs) = fs else { return }; - let workspaces = WORKSPACE_DB + let workspaces = db .recent_workspaces_on_disk(fs.as_ref()) .await .log_err() @@ -518,7 +520,7 @@ impl crate::SerializableItem for WelcomePage { alive_items, workspace_id, "welcome_pages", - &persistence::WELCOME_PAGES, + &persistence::WelcomePagesDb::global(cx), cx, ) } @@ -531,7 +533,7 @@ impl crate::SerializableItem for WelcomePage { window: &mut Window, cx: &mut App, ) -> Task>> { - if persistence::WELCOME_PAGES + if persistence::WelcomePagesDb::global(cx) .get_welcome_page(item_id, workspace_id) .ok() .is_some_and(|is_open| is_open) @@ -553,11 +555,10 @@ impl crate::SerializableItem for WelcomePage { cx: &mut Context, ) -> Option>> { let workspace_id = workspace.database_id()?; - Some(cx.background_spawn(async move { - persistence::WELCOME_PAGES - .save_welcome_page(item_id, workspace_id, true) - .await - })) + let db = persistence::WelcomePagesDb::global(cx); + Some(cx.background_spawn( + async move { db.save_welcome_page(item_id, workspace_id, true).await }, + )) } fn should_serialize(&self, event: &Self::Event) -> bool { @@ -591,7 +592,7 @@ mod persistence { )]); } - db::static_connection!(WELCOME_PAGES, WelcomePagesDb, [WorkspaceDb]); + db::static_connection!(WelcomePagesDb, [WorkspaceDb]); impl WelcomePagesDb { query! { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index d6549a1a0578a439d848cc1956a2e437008a17ca..6c159bd5c9ca06902033358d37dc8810e38c35c3 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -75,9 +75,9 @@ pub use pane_group::{ ActivePaneDecorator, HANDLE_HITBOX_SIZE, Member, PaneAxis, PaneGroup, PaneRenderContext, SplitDirection, }; -use persistence::{DB, SerializedWindowBounds, model::SerializedWorkspace}; +use persistence::{SerializedWindowBounds, model::SerializedWorkspace}; pub use persistence::{ - DB as WORKSPACE_DB, WorkspaceDb, delete_unloaded_items, + WorkspaceDb, delete_unloaded_items, model::{ DockStructure, ItemId, SerializedMultiWorkspace, SerializedWorkspaceLocation, SessionWorkspace, @@ -1382,10 +1382,10 @@ impl Workspace { |new_trusted_worktrees, cx| { let timeout = cx.background_executor().timer(SERIALIZATION_THROTTLE_TIME); + let db = WorkspaceDb::global(cx); cx.background_spawn(async move { timeout.await; - persistence::DB - .save_trusted_worktrees(new_trusted_worktrees) + db.save_trusted_worktrees(new_trusted_worktrees) .await .log_err(); }) @@ -1770,6 +1770,8 @@ impl Workspace { cx, ); + let db = WorkspaceDb::global(cx); + let kvp = db::kvp::KeyValueStore::global(cx); cx.spawn(async move |cx| { let mut paths_to_open = Vec::with_capacity(abs_paths.len()); for path in abs_paths.into_iter() { @@ -1780,8 +1782,7 @@ impl Workspace { } } - let serialized_workspace = - persistence::DB.workspace_for_roots(paths_to_open.as_slice()); + let serialized_workspace = db.workspace_for_roots(paths_to_open.as_slice()); if let Some(paths) = serialized_workspace.as_ref().map(|ws| &ws.paths) { paths_to_open = paths.ordered_paths().cloned().collect(); @@ -1813,10 +1814,10 @@ impl Workspace { let workspace_id = if let Some(serialized_workspace) = serialized_workspace.as_ref() { serialized_workspace.id } else { - DB.next_id().await.unwrap_or_else(|_| Default::default()) + db.next_id().await.unwrap_or_else(|_| Default::default()) }; - let toolchains = DB.toolchains(workspace_id).await?; + let toolchains = db.toolchains(workspace_id).await?; for (toolchain, worktree_path, path) in toolchains { let toolchain_path = PathBuf::from(toolchain.path.clone().to_string()); @@ -1899,7 +1900,7 @@ impl Workspace { // Reopening an existing workspace - restore its saved bounds (Some(bounds.0), Some(display)) } else if let Some((display, bounds)) = - persistence::read_default_window_bounds() + persistence::read_default_window_bounds(&kvp) { // New or empty workspace - use the last known window bounds (Some(bounds), Some(display)) @@ -1970,7 +1971,7 @@ impl Workspace { // 1. This is an empty workspace (no paths), AND // 2. The serialized workspace either doesn't exist or has no paths if is_empty_workspace && !serialized_workspace_has_paths { - if let Some(default_docks) = persistence::read_default_dock_state() { + if let Some(default_docks) = persistence::read_default_dock_state(&kvp) { window .update(cx, |_, window, cx| { workspace.update(cx, |workspace, cx| { @@ -5979,7 +5980,8 @@ impl Workspace { self.update_active_view_for_followers(window, cx); if let Some(database_id) = self.database_id { - cx.background_spawn(persistence::DB.update_timestamp(database_id)) + let db = WorkspaceDb::global(cx); + cx.background_spawn(async move { db.update_timestamp(database_id).await }) .detach(); } } else { @@ -6048,15 +6050,17 @@ impl Workspace { let window_bounds = window.inner_window_bounds(); let database_id = self.database_id; let has_paths = !self.root_paths(cx).is_empty(); + let db = WorkspaceDb::global(cx); + let kvp = db::kvp::KeyValueStore::global(cx); cx.background_executor().spawn(async move { if !has_paths { - persistence::write_default_window_bounds(window_bounds, display_uuid) + persistence::write_default_window_bounds(&kvp, window_bounds, display_uuid) .await .log_err(); } if let Some(database_id) = database_id { - DB.set_window_open_status( + db.set_window_open_status( database_id, SerializedWindowBounds(window_bounds), display_uuid, @@ -6064,7 +6068,7 @@ impl Workspace { .await .log_err(); } else { - persistence::write_default_window_bounds(window_bounds, display_uuid) + persistence::write_default_window_bounds(&kvp, window_bounds, display_uuid) .await .log_err(); } @@ -6253,8 +6257,9 @@ impl Workspace { user_toolchains, }; + let db = WorkspaceDb::global(cx); window.spawn(cx, async move |_| { - persistence::DB.save_workspace(serialized_workspace).await; + db.save_workspace(serialized_workspace).await; }) } WorkspaceLocation::DetachFromSession => { @@ -6262,27 +6267,30 @@ impl Workspace { let display = window.display(cx).and_then(|d| d.uuid().ok()); // Save dock state for empty local workspaces let docks = build_serialized_docks(self, window, cx); + let db = WorkspaceDb::global(cx); + let kvp = db::kvp::KeyValueStore::global(cx); window.spawn(cx, async move |_| { - persistence::DB - .set_window_open_status( - database_id, - window_bounds, - display.unwrap_or_default(), - ) - .await - .log_err(); - persistence::DB - .set_session_id(database_id, None) + db.set_window_open_status( + database_id, + window_bounds, + display.unwrap_or_default(), + ) + .await + .log_err(); + db.set_session_id(database_id, None).await.log_err(); + persistence::write_default_dock_state(&kvp, docks) .await .log_err(); - persistence::write_default_dock_state(docks).await.log_err(); }) } WorkspaceLocation::None => { // Save dock state for empty non-local workspaces let docks = build_serialized_docks(self, window, cx); + let kvp = db::kvp::KeyValueStore::global(cx); window.spawn(cx, async move |_| { - persistence::write_default_dock_state(docks).await.log_err(); + persistence::write_default_dock_state(&kvp, docks) + .await + .log_err(); }) } } @@ -6712,9 +6720,9 @@ impl Workspace { trusted_worktrees.update(cx, |trusted_worktrees, _| { trusted_worktrees.clear_trusted_paths() }); - let clear_task = persistence::DB.clear_trusted_worktrees(); + let db = WorkspaceDb::global(cx); cx.spawn(async move |_, cx| { - if clear_task.await.log_err().is_some() { + if db.clear_trusted_worktrees().await.log_err().is_some() { cx.update(|cx| reload(cx)); } }) @@ -7020,8 +7028,12 @@ impl Workspace { ) { self.centered_layout = !self.centered_layout; if let Some(database_id) = self.database_id() { - cx.background_spawn(DB.set_centered_layout(database_id, self.centered_layout)) - .detach_and_log_err(cx); + let db = WorkspaceDb::global(cx); + let centered_layout = self.centered_layout; + cx.background_spawn(async move { + db.set_centered_layout(database_id, centered_layout).await + }) + .detach_and_log_err(cx); } cx.notify(); } @@ -8238,9 +8250,10 @@ impl WorkspaceHandle for Entity { } pub async fn last_opened_workspace_location( + db: &WorkspaceDb, fs: &dyn fs::Fs, ) -> Option<(WorkspaceId, SerializedWorkspaceLocation, PathList)> { - DB.last_workspace(fs) + db.last_workspace(fs) .await .log_err() .flatten() @@ -8248,11 +8261,12 @@ pub async fn last_opened_workspace_location( } pub async fn last_session_workspace_locations( + db: &WorkspaceDb, last_session_id: &str, last_session_window_stack: Option>, fs: &dyn fs::Fs, ) -> Option> { - DB.last_session_workspace_locations(last_session_id, last_session_window_stack, fs) + db.last_session_workspace_locations(last_session_id, last_session_window_stack, fs) .await .log_err() } @@ -8874,8 +8888,10 @@ pub fn open_workspace_by_id( cx, ); + let db = WorkspaceDb::global(cx); + let kvp = db::kvp::KeyValueStore::global(cx); cx.spawn(async move |cx| { - let serialized_workspace = persistence::DB + let serialized_workspace = db .workspace_for_id(workspace_id) .with_context(|| format!("Workspace {workspace_id:?} not found"))?; @@ -8907,7 +8923,7 @@ pub fn open_workspace_by_id( && let Some(bounds) = serialized_workspace.window_bounds.as_ref() { (Some(bounds.0), Some(display)) - } else if let Some((display, bounds)) = persistence::read_default_window_bounds() { + } else if let Some((display, bounds)) = persistence::read_default_window_bounds(&kvp) { (Some(bounds), Some(display)) } else { (None, None) @@ -9275,7 +9291,8 @@ async fn open_remote_project_inner( window: WindowHandle, cx: &mut AsyncApp, ) -> Result>>> { - let toolchains = DB.toolchains(workspace_id).await?; + let db = cx.update(|cx| WorkspaceDb::global(cx)); + let toolchains = db.toolchains(workspace_id).await?; for (toolchain, worktree_path, path) in toolchains { project .update(cx, |this, cx| { @@ -9365,20 +9382,20 @@ fn deserialize_remote_project( paths: Vec, cx: &AsyncApp, ) -> Task)>> { + let db = cx.update(|cx| WorkspaceDb::global(cx)); cx.background_spawn(async move { - let remote_connection_id = persistence::DB + let remote_connection_id = db .get_or_create_remote_connection(connection_options) .await?; - let serialized_workspace = - persistence::DB.remote_workspace_for_roots(&paths, remote_connection_id); + let serialized_workspace = db.remote_workspace_for_roots(&paths, remote_connection_id); let workspace_id = if let Some(workspace_id) = serialized_workspace.as_ref().map(|workspace| workspace.id) { workspace_id } else { - persistence::DB.next_id().await? + db.next_id().await? }; Ok((workspace_id, serialized_workspace)) @@ -9997,14 +10014,15 @@ pub fn remote_workspace_position_from_db( cx: &App, ) -> Task> { let paths = paths_to_open.to_vec(); + let db = WorkspaceDb::global(cx); + let kvp = db::kvp::KeyValueStore::global(cx); cx.background_spawn(async move { - let remote_connection_id = persistence::DB + let remote_connection_id = db .get_or_create_remote_connection(connection_options) .await .context("fetching serialized ssh project")?; - let serialized_workspace = - persistence::DB.remote_workspace_for_roots(&paths, remote_connection_id); + let serialized_workspace = db.remote_workspace_for_roots(&paths, remote_connection_id); let (window_bounds, display) = if let Some(bounds) = window_bounds_env_override() { (Some(WindowBounds::Windowed(bounds)), None) @@ -10014,7 +10032,7 @@ pub fn remote_workspace_position_from_db( .and_then(|workspace| { Some((workspace.display?, workspace.window_bounds.map(|b| b.0)?)) }) - .or_else(|| persistence::read_default_window_bounds()); + .or_else(|| persistence::read_default_window_bounds(&kvp)); if let Some((serialized_display, serialized_bounds)) = restorable_bounds { (Some(serialized_bounds), Some(serialized_display)) @@ -13644,6 +13662,7 @@ mod tests { cx.update(|cx| { let settings_store = SettingsStore::test(cx); cx.set_global(settings_store); + cx.set_global(db::AppDatabase::test_new()); theme::init(theme::LoadThemes::JustBase, cx); }); } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index f98d51061630fefba33f7703eac68670cde67502..0a55953931ff4527851f9c9e7d6ac5f451eea0fd 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -14,7 +14,7 @@ use client::{Client, ProxySettings, UserStore, parse_zed_link}; use collab_ui::channel_view::ChannelView; use collections::HashMap; use crashes::InitCrashHandler; -use db::kvp::{GLOBAL_KEY_VALUE_STORE, KEY_VALUE_STORE}; +use db::kvp::{GlobalKeyValueStore, KeyValueStore}; use editor::Editor; use extension::ExtensionHostProxy; use fs::{Fs, RealFs}; @@ -325,12 +325,16 @@ fn main() { let app = Application::with_platform(gpui_platform::current_platform(false)).with_assets(Assets); + let app_db = db::AppDatabase::new(); let system_id = app.background_executor().spawn(system_id()); - let installation_id = app.background_executor().spawn(installation_id()); - let session_id = Uuid::new_v4().to_string(); - let session = app + let installation_id = app .background_executor() - .spawn(Session::new(session_id.clone())); + .spawn(installation_id(KeyValueStore::from_app_db(&app_db))); + let session_id = Uuid::new_v4().to_string(); + let session = app.background_executor().spawn(Session::new( + session_id.clone(), + KeyValueStore::from_app_db(&app_db), + )); crashes::init( InitCrashHandler { @@ -451,7 +455,8 @@ fn main() { }); app.run(move |cx| { - let db_trusted_paths = match workspace::WORKSPACE_DB.fetch_trusted_worktrees() { + cx.set_global(app_db); + let db_trusted_paths = match workspace::WorkspaceDb::global(cx).fetch_trusted_worktrees() { Ok(trusted_paths) => trusted_paths, Err(e) => { log::error!("Failed to do initial trusted worktrees fetch: {e:#}"); @@ -1300,42 +1305,37 @@ async fn authenticate(client: Arc, cx: &AsyncApp) -> Result<()> { async fn system_id() -> Result { let key_name = "system_id".to_string(); + let db = GlobalKeyValueStore::global(); - if let Ok(Some(system_id)) = GLOBAL_KEY_VALUE_STORE.read_kvp(&key_name) { + if let Ok(Some(system_id)) = db.read_kvp(&key_name) { return Ok(IdType::Existing(system_id)); } let system_id = Uuid::new_v4().to_string(); - GLOBAL_KEY_VALUE_STORE - .write_kvp(key_name, system_id.clone()) - .await?; + db.write_kvp(key_name, system_id.clone()).await?; Ok(IdType::New(system_id)) } -async fn installation_id() -> Result { +async fn installation_id(db: KeyValueStore) -> Result { let legacy_key_name = "device_id".to_string(); let key_name = "installation_id".to_string(); // Migrate legacy key to new key - if let Ok(Some(installation_id)) = KEY_VALUE_STORE.read_kvp(&legacy_key_name) { - KEY_VALUE_STORE - .write_kvp(key_name, installation_id.clone()) - .await?; - KEY_VALUE_STORE.delete_kvp(legacy_key_name).await?; + if let Ok(Some(installation_id)) = db.read_kvp(&legacy_key_name) { + db.write_kvp(key_name, installation_id.clone()).await?; + db.delete_kvp(legacy_key_name).await?; return Ok(IdType::Existing(installation_id)); } - if let Ok(Some(installation_id)) = KEY_VALUE_STORE.read_kvp(&key_name) { + if let Ok(Some(installation_id)) = db.read_kvp(&key_name) { return Ok(IdType::Existing(installation_id)); } let installation_id = Uuid::new_v4().to_string(); - KEY_VALUE_STORE - .write_kvp(key_name, installation_id.clone()) - .await?; + db.write_kvp(key_name, installation_id.clone()).await?; Ok(IdType::New(installation_id)) } @@ -1344,6 +1344,7 @@ pub(crate) async fn restore_or_create_workspace( app_state: Arc, cx: &mut AsyncApp, ) -> Result<()> { + let kvp = cx.update(|cx| KeyValueStore::global(cx)); if let Some((multi_workspaces, remote_workspaces)) = restorable_workspaces(cx, &app_state).await { let mut results: Vec> = Vec::new(); @@ -1452,7 +1453,7 @@ pub(crate) async fn restore_or_create_workspace( .await?; } } - } else if matches!(KEY_VALUE_STORE.read_kvp(FIRST_OPEN), Ok(None)) { + } else if matches!(kvp.read_kvp(FIRST_OPEN), Ok(None)) { cx.update(|cx| show_onboarding_view(app_state, cx)).await?; } else { cx.update(|cx| { @@ -1488,7 +1489,8 @@ async fn restorable_workspaces( let (remote_workspaces, local_workspaces) = locations .into_iter() .partition(|sw| matches!(sw.location, SerializedWorkspaceLocation::Remote(_))); - let multi_workspaces = workspace::read_serialized_multi_workspaces(local_workspaces); + let multi_workspaces = + cx.update(|cx| workspace::read_serialized_multi_workspaces(local_workspaces, cx)); Some((multi_workspaces, remote_workspaces)) } @@ -1496,7 +1498,12 @@ pub(crate) async fn restorable_workspace_locations( cx: &mut AsyncApp, app_state: &Arc, ) -> Option> { - let mut restore_behavior = cx.update(|cx| WorkspaceSettings::get(None, cx).restore_on_startup); + let (mut restore_behavior, db) = cx.update(|cx| { + ( + WorkspaceSettings::get(None, cx).restore_on_startup, + workspace::WorkspaceDb::global(cx), + ) + }); let session_handle = app_state.session.clone(); let (last_session_id, last_session_window_stack) = cx.update(|cx| { @@ -1519,7 +1526,7 @@ pub(crate) async fn restorable_workspace_locations( match restore_behavior { workspace::RestoreOnStartupBehavior::LastWorkspace => { - workspace::last_opened_workspace_location(app_state.fs.as_ref()) + workspace::last_opened_workspace_location(&db, app_state.fs.as_ref()) .await .map(|(workspace_id, location, paths)| { vec![SessionWorkspace { @@ -1535,6 +1542,7 @@ pub(crate) async fn restorable_workspace_locations( let ordered = last_session_window_stack.is_some(); let mut locations = workspace::last_session_workspace_locations( + &db, &last_session_id, last_session_window_stack, app_state.fs.as_ref(), diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 13b8b7aa158af929445fa3f8a2b2b1b68990b8e1..7624a027a2c4fc59f14bab025b9061e070116f76 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -5960,9 +5960,11 @@ mod tests { cx.run_until_parked(); // Verify all workspaces retained their session_ids. - let locations = workspace::last_session_workspace_locations(&session_id, None, fs.as_ref()) - .await - .expect("expected session workspace locations"); + let db = cx.update(|cx| workspace::WorkspaceDb::global(cx)); + let locations = + workspace::last_session_workspace_locations(&db, &session_id, None, fs.as_ref()) + .await + .expect("expected session workspace locations"); assert_eq!( locations.len(), 3, @@ -5989,9 +5991,10 @@ mod tests { }); // --- Read back from DB and verify grouping --- - let locations = workspace::last_session_workspace_locations(&session_id, None, fs.as_ref()) - .await - .expect("expected session workspace locations"); + let locations = + workspace::last_session_workspace_locations(&db, &session_id, None, fs.as_ref()) + .await + .expect("expected session workspace locations"); assert_eq!(locations.len(), 3, "expected 3 session workspaces"); diff --git a/crates/zed/src/zed/open_listener.rs b/crates/zed/src/zed/open_listener.rs index ca376f300d97de83d0b4a9af7620ee98ba5b4215..53347e501f7ba23be62466779f7775d0d432dfab 100644 --- a/crates/zed/src/zed/open_listener.rs +++ b/crates/zed/src/zed/open_listener.rs @@ -5,7 +5,7 @@ use anyhow::{Context as _, Result, anyhow}; use cli::{CliRequest, CliResponse, ipc::IpcSender}; use cli::{IpcHandshake, ipc}; use client::{ZedLink, parse_zed_link}; -use db::kvp::KEY_VALUE_STORE; +use db::kvp::KeyValueStore; use editor::Editor; use fs::Fs; use futures::channel::mpsc::{UnboundedReceiver, UnboundedSender}; @@ -491,7 +491,8 @@ async fn open_workspaces( if grouped_locations.is_empty() { // If we have no paths to open, show the welcome screen if this is the first launch - if matches!(KEY_VALUE_STORE.read_kvp(FIRST_OPEN), Ok(None)) { + let kvp = cx.update(|cx| KeyValueStore::global(cx)); + if matches!(kvp.read_kvp(FIRST_OPEN), Ok(None)) { cx.update(|cx| show_onboarding_view(app_state, cx).detach()); } // If not the first launch, show an empty window with empty editor