From 092fedc42cb13e94f68a0977d4c906db34ac64c7 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 13 Apr 2026 15:51:38 -0700 Subject: [PATCH] Fix the opening of the agent panel when clicking Try Now in new onboarding notification (#53845) This fixes an issue reported by @Veykril where the "Try Now" button would open a different panel than the agent panel. The bug was caused by us attempting to focus the agent panel before layout settings updates had been applied. This should also make all programmatic settings updates more responsive, because they won't have to wait for FS watchers to take effect. Release Notes: - N/A --------- Co-authored-by: Anthony Eid --- Cargo.lock | 1 + crates/agent_settings/Cargo.toml | 1 + crates/agent_settings/src/agent_settings.rs | 27 ++- crates/agent_ui/src/agent_panel.rs | 34 ++- crates/auto_update_ui/src/auto_update_ui.rs | 124 ++++++----- crates/settings/src/settings_file.rs | 10 +- crates/settings/src/settings_store.rs | 193 +++++++++++++++++- .../settings_content/src/settings_content.rs | 2 + crates/zed/src/main.rs | 21 +- crates/zed/src/zed.rs | 123 +++-------- crates/zed_actions/src/lib.rs | 5 +- 11 files changed, 343 insertions(+), 198 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a3ece33b1495aef7a38f96619945284ab663b8cf..2ad9a3e29ba927e23946e2a0867ace5722e03ee5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -301,6 +301,7 @@ dependencies = [ "collections", "convert_case 0.8.0", "fs", + "futures 0.3.32", "gpui", "language_model", "log", diff --git a/crates/agent_settings/Cargo.toml b/crates/agent_settings/Cargo.toml index b2db5677dcfdc0994e7ce7a03c9c1dd850eb8514..985c0309afbebc532cc0a3af31adb1e02c8eae90 100644 --- a/crates/agent_settings/Cargo.toml +++ b/crates/agent_settings/Cargo.toml @@ -17,6 +17,7 @@ anyhow.workspace = true collections.workspace = true convert_case.workspace = true fs.workspace = true +futures.workspace = true gpui.workspace = true language_model.workspace = true log.workspace = true diff --git a/crates/agent_settings/src/agent_settings.rs b/crates/agent_settings/src/agent_settings.rs index f04095662513a31ad5e8b204509669942ec4793f..a8b21fcbd849962a4a1d098eaa681b0f56016bc6 100644 --- a/crates/agent_settings/src/agent_settings.rs +++ b/crates/agent_settings/src/agent_settings.rs @@ -6,6 +6,7 @@ use std::sync::{Arc, LazyLock}; use agent_client_protocol::ModelId; use collections::{HashSet, IndexMap}; use fs::Fs; +use futures::channel::oneshot; use gpui::{App, Pixels, px}; use language_model::LanguageModel; use project::DisableAiSettings; @@ -15,7 +16,7 @@ use settings::{ DockPosition, DockSide, LanguageModelParameters, LanguageModelSelection, NewThreadLocation, NotifyWhenAgentWaiting, PlaySoundWhenAgentDone, RegisterSetting, Settings, SettingsContent, SettingsStore, SidebarDockPosition, SidebarSide, ThinkingBlockDisplay, ToolPermissionMode, - update_settings_file, + update_settings_file, update_settings_file_with_completion, }; pub use crate::agent_profile::*; @@ -242,26 +243,30 @@ impl AgentSettings { }); } - pub fn set_layout(layout: WindowLayout, fs: Arc, cx: &App) { + pub fn set_layout( + layout: WindowLayout, + fs: Arc, + cx: &App, + ) -> oneshot::Receiver> { let merged = PanelLayout::read_from(cx.global::().merged_settings()); match layout { WindowLayout::Agent(None) => { - update_settings_file(fs, cx, move |settings, _cx| { + update_settings_file_with_completion(fs, cx, move |settings, _cx| { PanelLayout::AGENT.write_diff_to(&merged, settings); - }); + }) } WindowLayout::Editor(None) => { - update_settings_file(fs, cx, move |settings, _cx| { + update_settings_file_with_completion(fs, cx, move |settings, _cx| { PanelLayout::EDITOR.write_diff_to(&merged, settings); - }); + }) } WindowLayout::Agent(Some(saved)) | WindowLayout::Editor(Some(saved)) | WindowLayout::Custom(saved) => { - update_settings_file(fs, cx, move |settings, _cx| { + update_settings_file_with_completion(fs, cx, move |settings, _cx| { saved.write_to(settings); - }); + }) } } } @@ -1356,8 +1361,10 @@ mod tests { let layout = AgentSettings::get_layout(cx); assert!(matches!(layout, WindowLayout::Custom(_))); - AgentSettings::set_layout(WindowLayout::agent(), fs.clone(), cx); - }); + AgentSettings::set_layout(WindowLayout::agent(), fs.clone(), cx) + }) + .await + .ok(); cx.run_until_parked(); diff --git a/crates/agent_ui/src/agent_panel.rs b/crates/agent_ui/src/agent_panel.rs index 08e70c5cd5b9adccc86d441a873b0c03dc9eed4a..86d2132123bcca1fdeaf54d04b337c2deeea1554 100644 --- a/crates/agent_ui/src/agent_panel.rs +++ b/crates/agent_ui/src/agent_panel.rs @@ -19,9 +19,14 @@ use project::AgentId; use serde::{Deserialize, Serialize}; use settings::{LanguageModelProviderSetting, LanguageModelSelection}; -use zed_actions::agent::{ - AddSelectionToThread, ConflictContent, ReauthenticateAgent, ResolveConflictedFilesWithAgent, - ResolveConflictsWithAgent, ReviewBranchDiff, +use zed_actions::{ + DecreaseBufferFontSize, IncreaseBufferFontSize, ResetBufferFontSize, + agent::{ + AddSelectionToThread, ConflictContent, OpenSettings, ReauthenticateAgent, ResetAgentZoom, + ResetOnboarding, ResolveConflictedFilesWithAgent, ResolveConflictsWithAgent, + ReviewBranchDiff, + }, + assistant::{FocusAgent, OpenRulesLibrary, Toggle, ToggleFocus}, }; use crate::DEFAULT_THREAD_TITLE; @@ -83,11 +88,6 @@ use workspace::{ ToggleWorkspaceSidebar, ToggleZoom, Workspace, WorkspaceId, WorkspaceSidebarDelegate, dock::{DockPosition, Panel, PanelEvent}, }; -use zed_actions::{ - DecreaseBufferFontSize, IncreaseBufferFontSize, ResetBufferFontSize, - agent::{OpenSettings, ResetAgentZoom, ResetOnboarding}, - assistant::{OpenRulesLibrary, Toggle, ToggleFocus}, -}; const AGENT_PANEL_KEY: &str = "agent_panel"; const MIN_PANEL_WIDTH: Pixels = px(300.); @@ -1142,7 +1142,7 @@ impl AgentPanel { let fs = fs.clone(); let weak_panel = weak_panel.clone(); move |_window, cx| { - AgentSettings::set_layout(WindowLayout::Agent(None), fs.clone(), cx); + let _ = AgentSettings::set_layout(WindowLayout::Agent(None), fs.clone(), cx); weak_panel .update(cx, |panel, cx| { panel.dismiss_agent_layout_onboarding(cx); @@ -1154,7 +1154,7 @@ impl AgentPanel { let fs = fs.clone(); let weak_panel = weak_panel.clone(); move |_window, cx| { - AgentSettings::set_layout(WindowLayout::Editor(None), fs.clone(), cx); + let _ = AgentSettings::set_layout(WindowLayout::Editor(None), fs.clone(), cx); weak_panel .update(cx, |panel, cx| { panel.dismiss_agent_layout_onboarding(cx); @@ -1294,6 +1294,20 @@ impl AgentPanel { } } + pub fn focus( + workspace: &mut Workspace, + _: &FocusAgent, + window: &mut Window, + cx: &mut Context, + ) { + if workspace + .panel::(cx) + .is_some_and(|panel| panel.read(cx).enabled(cx)) + { + workspace.focus_panel::(window, cx); + } + } + pub fn toggle( workspace: &mut Workspace, _: &Toggle, diff --git a/crates/auto_update_ui/src/auto_update_ui.rs b/crates/auto_update_ui/src/auto_update_ui.rs index 6773dea5a09ad3bbe1a43761b6df43a56e195d8d..01691bdb80b09fd7ebaa3a428a2328c942587b33 100644 --- a/crates/auto_update_ui/src/auto_update_ui.rs +++ b/crates/auto_update_ui/src/auto_update_ui.rs @@ -22,6 +22,7 @@ use workspace::{ simple_message_notification::MessageNotification, }, }; +use zed_actions::{ShowUpdateNotification, assistant::FocusAgent}; actions!( auto_update, @@ -33,10 +34,19 @@ actions!( pub fn init(cx: &mut App) { notify_if_app_was_updated(cx); - cx.observe_new(|workspace: &mut Workspace, _window, _cx| { + cx.observe_new(|workspace: &mut Workspace, _window, cx| { workspace.register_action(|workspace, _: &ViewReleaseNotesLocally, window, cx| { view_release_notes_locally(workspace, window, cx); }); + + if matches!( + ReleaseChannel::global(cx), + ReleaseChannel::Nightly | ReleaseChannel::Dev + ) { + workspace.register_action(|_workspace, _: &ShowUpdateNotification, _window, cx| { + show_update_notification(cx); + }); + } }) .detach(); } @@ -187,7 +197,7 @@ impl Dismissable for ParallelAgentAnnouncement { fn announcement_for_version(version: &Version, cx: &App) -> Option { match (version.major, version.minor, version.patch) { - (0, 232, _) => { + (0, _, _) => { if ParallelAgentAnnouncement::dismissed(cx) { None } else { @@ -207,12 +217,29 @@ fn announcement_for_version(version: &Version, cx: &App) -> Option(), + cx, + move |cx| cx.new(|cx| AnnouncementToastNotification::new(content.clone(), cx)), + ); + } else { + show_app_notification( + NotificationId::unique::(), + cx, + move |cx| { + let workspace_handle = cx.entity().downgrade(); + cx.new(|cx| { + MessageNotification::new(format!("Updated to {app_name} {}", version), cx) + .primary_message("View Release Notes") + .primary_on_click(move |window, cx| { + if let Some(workspace) = workspace_handle.upgrade() { + workspace.update(cx, |workspace, cx| { + crate::view_release_notes_locally(workspace, window, cx); + }) + } + cx.emit(DismissEvent); + }) + .show_suppress_button(false) + }) + }, + ); + } +} + /// Shows a notification across all workspaces if an update was previously automatically installed /// and this notification had not yet been shown. pub fn notify_if_app_was_updated(cx: &mut App) { @@ -310,55 +379,12 @@ pub fn notify_if_app_was_updated(cx: &mut App) { return; } - struct UpdateNotification; - let should_show_notification = updater.read(cx).should_show_update_notification(cx); cx.spawn(async move |cx| { let should_show_notification = should_show_notification.await?; - // if true { // Hardcode it to true for testing it outside of the component preview if should_show_notification { cx.update(|cx| { - let mut version = updater.read(cx).current_version(); - version.pre = semver::Prerelease::EMPTY; - version.build = semver::BuildMetadata::EMPTY; - let app_name = ReleaseChannel::global(cx).display_name(); - - if let Some(content) = announcement_for_version(&version, cx) { - show_app_notification( - NotificationId::unique::(), - cx, - move |cx| { - cx.new(|cx| AnnouncementToastNotification::new(content.clone(), cx)) - }, - ); - } else { - show_app_notification( - NotificationId::unique::(), - cx, - move |cx| { - let workspace_handle = cx.entity().downgrade(); - cx.new(|cx| { - MessageNotification::new( - format!("Updated to {app_name} {}", version), - cx, - ) - .primary_message("View Release Notes") - .primary_on_click(move |window, cx| { - if let Some(workspace) = workspace_handle.upgrade() { - workspace.update(cx, |workspace, cx| { - crate::view_release_notes_locally( - workspace, window, cx, - ); - }) - } - cx.emit(DismissEvent); - }) - .show_suppress_button(false) - }) - }, - ); - } - + show_update_notification(cx); updater.update(cx, |updater, cx| { updater .set_should_show_update_notification(false, cx) diff --git a/crates/settings/src/settings_file.rs b/crates/settings/src/settings_file.rs index 87ab85aae595faf9a69c45b77d98ea1230ea5162..efc5e45130e244b5da457b3f1f1f687800fe3b33 100644 --- a/crates/settings/src/settings_file.rs +++ b/crates/settings/src/settings_file.rs @@ -227,5 +227,13 @@ pub fn update_settings_file( cx: &App, update: impl 'static + Send + FnOnce(&mut SettingsContent, &App), ) { - SettingsStore::global(cx).update_settings_file(fs, update); + SettingsStore::global(cx).update_settings_file(fs, update) +} + +pub fn update_settings_file_with_completion( + fs: Arc, + cx: &App, + update: impl 'static + Send + FnOnce(&mut SettingsContent, &App), +) -> futures::channel::oneshot::Receiver> { + SettingsStore::global(cx).update_settings_file_with_completion(fs, update) } diff --git a/crates/settings/src/settings_store.rs b/crates/settings/src/settings_store.rs index a1b3b8c0ae23f58bdbe915a151e0825ab085a866..a98a60c5e16625f2a42cc1ab85b9c8a43bf7b57c 100644 --- a/crates/settings/src/settings_store.rs +++ b/crates/settings/src/settings_store.rs @@ -155,9 +155,12 @@ pub struct SettingsStore { merged_settings: Rc, + last_user_settings_content: Option, + last_global_settings_content: Option, local_settings: BTreeMap<(WorktreeId, Arc), SettingsContent>, pub editorconfig_store: Entity, + _settings_files_watcher: Option>, _setting_file_updates: Task<()>, setting_file_updates_tx: mpsc::UnboundedSender LocalBoxFuture<'static, Result<()>>>>, @@ -307,8 +310,11 @@ impl SettingsStore { language_semantic_token_rules: HashMap::default(), merged_settings: default_settings, + last_user_settings_content: None, + last_global_settings_content: None, local_settings: BTreeMap::default(), editorconfig_store: cx.new(|_| EditorconfigStore::default()), + _settings_files_watcher: None, setting_file_updates_tx, _setting_file_updates: cx.spawn(async move |cx| { while let Some(setting_file_update) = setting_file_updates_rx.next().await { @@ -338,6 +344,59 @@ impl SettingsStore { cx.update_global(f) } + pub fn watch_settings_files( + &mut self, + fs: Arc, + cx: &mut App, + settings_changed: impl 'static + Fn(SettingsFile, SettingsParseResult, &mut App), + ) { + let (mut user_settings_file_rx, user_settings_watcher) = crate::watch_config_file( + cx.background_executor(), + fs.clone(), + paths::settings_file().clone(), + ); + let (mut global_settings_file_rx, global_settings_watcher) = crate::watch_config_file( + cx.background_executor(), + fs, + paths::global_settings_file().clone(), + ); + + let global_content = cx + .foreground_executor() + .block_on(global_settings_file_rx.next()) + .unwrap(); + let user_content = cx + .foreground_executor() + .block_on(user_settings_file_rx.next()) + .unwrap(); + + let result = self.set_user_settings(&user_content, cx); + settings_changed(SettingsFile::User, result, cx); + let result = self.set_global_settings(&global_content, cx); + settings_changed(SettingsFile::Global, result, cx); + + self._settings_files_watcher = Some(cx.spawn(async move |cx| { + let _user_settings_watcher = user_settings_watcher; + let _global_settings_watcher = global_settings_watcher; + let mut settings_streams = futures::stream::select( + global_settings_file_rx.map(|content| (SettingsFile::Global, content)), + user_settings_file_rx.map(|content| (SettingsFile::User, content)), + ); + + while let Some((settings_file, content)) = settings_streams.next().await { + cx.update_global(|store: &mut SettingsStore, cx| { + let result = match settings_file { + SettingsFile::User => store.set_user_settings(&content, cx), + SettingsFile::Global => store.set_global_settings(&content, cx), + _ => return, + }; + settings_changed(settings_file, result, cx); + cx.refresh_windows(); + }); + } + })); + } + /// Add a new type of setting to the store. pub fn register_setting(&mut self) { self.register_setting_internal(&RegisteredSetting { @@ -498,7 +557,8 @@ impl SettingsStore { async move { let res = async move { let old_text = Self::load_settings(&fs).await?; - let new_text = update(old_text, cx)?; + let new_text = update(old_text, cx.clone())?; + let settings_path = paths::settings_file().as_path(); if fs.is_file(settings_path).await { let resolved_path = @@ -509,25 +569,28 @@ impl SettingsStore { ) })?; - fs.atomic_write(resolved_path.clone(), new_text) + fs.atomic_write(resolved_path.clone(), new_text.clone()) .await .with_context(|| { format!("Failed to write settings to file {:?}", resolved_path) })?; } else { - fs.atomic_write(settings_path.to_path_buf(), new_text) + fs.atomic_write(settings_path.to_path_buf(), new_text.clone()) .await .with_context(|| { format!("Failed to write settings to file {:?}", settings_path) })?; } - anyhow::Ok(()) + + cx.update_global(|store: &mut SettingsStore, cx| { + store.set_user_settings(&new_text, cx).result().map(|_| ()) + }) } .await; let new_res = match &res { Ok(_) => anyhow::Ok(()), - Err(e) => Err(anyhow::anyhow!("Failed to write settings to file {:?}", e)), + Err(e) => Err(anyhow::anyhow!("{:?}", e)), }; _ = tx.send(new_res); @@ -545,11 +608,19 @@ impl SettingsStore { fs: Arc, update: impl 'static + Send + FnOnce(&mut SettingsContent, &App), ) { - _ = self.update_settings_file_inner(fs, move |old_text: String, cx: AsyncApp| { + _ = self.update_settings_file_with_completion(fs, update); + } + + pub fn update_settings_file_with_completion( + &self, + fs: Arc, + update: impl 'static + Send + FnOnce(&mut SettingsContent, &App), + ) -> oneshot::Receiver> { + self.update_settings_file_inner(fs, move |old_text: String, cx: AsyncApp| { cx.read_global(|store: &SettingsStore, cx| { store.new_text_for_update(old_text, |content| update(content, cx)) }) - }); + }) } pub fn import_vscode_settings( @@ -836,6 +907,14 @@ impl SettingsStore { user_settings_content: &str, cx: &mut App, ) -> SettingsParseResult { + if self.last_user_settings_content.as_deref() == Some(user_settings_content) { + return SettingsParseResult { + parse_status: ParseStatus::Unchanged, + migration_status: MigrationStatus::NotNeeded, + }; + } + self.last_user_settings_content = Some(user_settings_content.to_string()); + let (settings, parse_result) = self.parse_and_migrate_zed_settings::( user_settings_content, SettingsFile::User, @@ -855,6 +934,14 @@ impl SettingsStore { global_settings_content: &str, cx: &mut App, ) -> SettingsParseResult { + if self.last_global_settings_content.as_deref() == Some(global_settings_content) { + return SettingsParseResult { + parse_status: ParseStatus::Unchanged, + migration_status: MigrationStatus::NotNeeded, + }; + } + self.last_global_settings_content = Some(global_settings_content.to_string()); + let (settings, parse_result) = self.parse_and_migrate_zed_settings::( global_settings_content, SettingsFile::Global, @@ -973,6 +1060,7 @@ impl SettingsStore { ); match parse_result.parse_status { ParseStatus::Success => Ok(()), + ParseStatus::Unchanged => Ok(()), ParseStatus::Failed { error } => Err(InvalidSettingsError::LocalSettings { path: directory_path.join(local_settings_file_relative_path()), message: error, @@ -1368,7 +1456,7 @@ impl SettingsParseResult { }; let parse_result = match self.parse_status { - ParseStatus::Success => Ok(()), + ParseStatus::Success | ParseStatus::Unchanged => Ok(()), ParseStatus::Failed { error } => { Err(anyhow::format_err!(error)).context("Failed to parse settings") } @@ -1397,7 +1485,7 @@ impl SettingsParseResult { pub fn parse_error(&self) -> Option { match &self.parse_status { ParseStatus::Failed { error } => Some(error.clone()), - ParseStatus::Success => None, + ParseStatus::Success | ParseStatus::Unchanged => None, } } } @@ -1517,7 +1605,7 @@ impl AnySettingValue for SettingValue { #[cfg(test)] mod tests { - use std::num::NonZeroU32; + use std::{cell::RefCell, num::NonZeroU32}; use crate::{ ClosePosition, ItemSettingsContent, VsCodeSettingsSource, default_settings, @@ -1525,6 +1613,7 @@ mod tests { }; use super::*; + use fs::FakeFs; use unindent::Unindent; use util::rel_path::rel_path; @@ -1589,6 +1678,90 @@ mod tests { } } + #[gpui::test] + async fn test_update_settings_file_updates_store_before_watcher(cx: &mut gpui::TestAppContext) { + let fs = FakeFs::new(cx.background_executor.clone()); + fs.create_dir(paths::settings_file().parent().unwrap()) + .await + .unwrap(); + fs.insert_file( + paths::settings_file(), + r#"{ "tabs": { "close_position": "right" } }"#.as_bytes().to_vec(), + ) + .await; + fs.pause_events(); + cx.run_until_parked(); + + let success = SettingsParseResult { + parse_status: ParseStatus::Success, + migration_status: MigrationStatus::NotNeeded, + }; + let parse_results = Rc::new(RefCell::new(Vec::new())); + + cx.update(|cx| { + let mut store = SettingsStore::new(cx, &default_settings()); + store.register_setting::(); + store.watch_settings_files(fs.clone(), cx, { + let parse_results = parse_results.clone(); + move |_, result, _| { + parse_results.borrow_mut().push(result); + } + }); + cx.set_global(store); + }); + + // Calling watch_settings_files loads user and global settings. + assert_eq!( + parse_results.borrow().as_slice(), + &[success.clone(), success.clone()] + ); + cx.update(|cx| { + assert_eq!( + cx.global::() + .get::(None) + .close_position, + ClosePosition::Right + ); + }); + + // Updating the settings file returns a channel that resolves once the settings are loaded. + let rx = cx.update(|cx| { + cx.global::() + .update_settings_file_with_completion(fs.clone(), move |settings, _| { + settings.tabs.get_or_insert_default().close_position = + Some(ClosePosition::Left); + }) + }); + assert!(rx.await.unwrap().is_ok()); + assert_eq!( + parse_results.borrow().as_slice(), + &[success.clone(), success.clone()] + ); + cx.update(|cx| { + assert_eq!( + cx.global::() + .get::(None) + .close_position, + ClosePosition::Left + ); + }); + + // When the FS event occurs, the settings are recognized as unchanged. + fs.flush_events(100); + cx.run_until_parked(); + assert_eq!( + parse_results.borrow().as_slice(), + &[ + success.clone(), + success.clone(), + SettingsParseResult { + parse_status: ParseStatus::Unchanged, + migration_status: MigrationStatus::NotNeeded + } + ] + ); + } + #[gpui::test] fn test_settings_store_basic(cx: &mut App) { let mut store = SettingsStore::new(cx, &default_settings()); diff --git a/crates/settings_content/src/settings_content.rs b/crates/settings_content/src/settings_content.rs index 3c3c0f600769b8437dc56016426eee4f84d2fc7a..34072825c9c8c0f118f56220c51a54d2c5f6b832 100644 --- a/crates/settings_content/src/settings_content.rs +++ b/crates/settings_content/src/settings_content.rs @@ -74,6 +74,8 @@ pub use util::serde::default_true; pub enum ParseStatus { /// Settings were parsed successfully Success, + /// Settings file was not changed, so no parsing was performed + Unchanged, /// Settings failed to parse Failed { error: String }, } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 97caf14639ce23a8c85392aa630267f146902602..342ff9487f46f7712b63c3a65350d67cea818d18 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -62,8 +62,7 @@ use workspace::{ use zed::{ OpenListener, OpenRequest, RawOpenRequest, app_menus, build_window_options, derive_paths_with_position, edit_prediction_registry, handle_cli_connection, - handle_keymap_file_changes, handle_settings_file_changes, initialize_workspace, - open_paths_with_positions, + handle_keymap_file_changes, initialize_workspace, open_paths_with_positions, }; use crate::zed::{OpenRequestKind, eager_load_active_theme_and_icon_theme}; @@ -401,16 +400,6 @@ fn main() { } let fs = Arc::new(RealFs::new(git_binary_path, app.background_executor())); - let (user_settings_file_rx, user_settings_watcher) = watch_config_file( - &app.background_executor(), - fs.clone(), - paths::settings_file().clone(), - ); - let (global_settings_file_rx, global_settings_watcher) = watch_config_file( - &app.background_executor(), - fs.clone(), - paths::global_settings_file().clone(), - ); let (user_keymap_file_rx, user_keymap_watcher) = watch_config_file( &app.background_executor(), fs.clone(), @@ -473,13 +462,7 @@ fn main() { } settings::init(cx); zlog_settings::init(cx); - handle_settings_file_changes( - user_settings_file_rx, - user_settings_watcher, - global_settings_file_rx, - global_settings_watcher, - cx, - ); + zed::watch_settings_files(fs.clone(), cx); handle_keymap_file_changes(user_keymap_file_rx, user_keymap_watcher, cx); let user_agent = format!( diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 60ede82b719297a553d3e2446e811e08712d1d5c..fd28c268e0a753cb41661aab5a9ac457b2518f89 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -27,7 +27,6 @@ use extension_host::ExtensionStore; use feature_flags::{FeatureFlagAppExt as _, PanicFeatureFlag}; use fs::Fs; use futures::FutureExt as _; -use futures::future::Either; use futures::{StreamExt, channel::mpsc, select_biased}; use git_ui::commit_view::CommitViewToolbar; use git_ui::git_panel::GitPanel; @@ -64,7 +63,7 @@ use rope::Rope; use search::project_search::ProjectSearchBar; use settings::{ BaseKeymap, DEFAULT_KEYMAP_PATH, InvalidSettingsError, KeybindSource, KeymapFile, - KeymapFileLoadResult, MigrationStatus, Settings, SettingsStore, VIM_KEYMAP_PATH, + KeymapFileLoadResult, MigrationStatus, Settings, SettingsFile, SettingsStore, VIM_KEYMAP_PATH, initial_local_debug_tasks_content, initial_project_settings_content, initial_tasks_content, update_settings_file, }; @@ -749,6 +748,7 @@ async fn initialize_agent_panel( if !cfg!(test) { workspace .register_action(agent_ui::AgentPanel::toggle_focus) + .register_action(agent_ui::AgentPanel::focus) .register_action(agent_ui::AgentPanel::toggle) .register_action(agent_ui::InlineAssistant::inline_assist); } @@ -1677,6 +1677,7 @@ fn notify_settings_errors(result: settings::SettingsParseResult, is_user: bool, let error = match result.parse_status { settings::ParseStatus::Failed { error } => Some(anyhow::format_err!(error)), settings::ParseStatus::Success => None, + settings::ParseStatus::Unchanged => return, }; let id = NotificationId::Named(format!("failed-to-parse-settings-{is_user}").into()); @@ -1740,67 +1741,25 @@ fn notify_settings_errors(result: settings::SettingsParseResult, is_user: bool, }; } -pub fn handle_settings_file_changes( - mut user_settings_file_rx: mpsc::UnboundedReceiver, - user_settings_watcher: gpui::Task<()>, - mut global_settings_file_rx: mpsc::UnboundedReceiver, - global_settings_watcher: gpui::Task<()>, - cx: &mut App, -) { +pub fn watch_settings_files(fs: Arc, cx: &mut App) { MigrationNotification::set_global(cx.new(|_| MigrationNotification), cx); - // Initial load of both settings files - let global_content = cx - .foreground_executor() - .block_on(global_settings_file_rx.next()) - .unwrap(); - let user_content = cx - .foreground_executor() - .block_on(user_settings_file_rx.next()) - .unwrap(); - - SettingsStore::update_global(cx, |store, cx| { - notify_settings_errors(store.set_user_settings(&user_content, cx), true, cx); - notify_settings_errors(store.set_global_settings(&global_content, cx), false, cx); - }); - - // Watch for changes in both files - cx.spawn(async move |cx| { - let _user_settings_watcher = user_settings_watcher; - let _global_settings_watcher = global_settings_watcher; - let mut settings_streams = futures::stream::select( - global_settings_file_rx.map(Either::Left), - user_settings_file_rx.map(Either::Right), - ); - - while let Some(content) = settings_streams.next().await { - let (content, is_user) = match content { - Either::Left(content) => (content, false), - Either::Right(content) => (content, true), - }; - - cx.update_global(|store: &mut SettingsStore, cx| { - let result = if is_user { - store.set_user_settings(&content, cx) - } else { - store.set_global_settings(&content, cx) - }; - let migrating_in_memory = - matches!(&result.migration_status, MigrationStatus::Succeeded); - notify_settings_errors(result, is_user, cx); - if let Some(notifier) = MigrationNotification::try_global(cx) { - notifier.update(cx, |_, cx| { - cx.emit(MigrationEvent::ContentChanged { - migration_type: MigrationType::Settings, - migrating_in_memory, - }); + SettingsStore::update_global(cx, move |store, cx| { + store.watch_settings_files(fs, cx, |settings_file, result, cx| { + let is_user = matches!(settings_file, SettingsFile::User); + let migrating_in_memory = + matches!(&result.migration_status, MigrationStatus::Succeeded); + notify_settings_errors(result, is_user, cx); + if let Some(notifier) = MigrationNotification::try_global(cx) { + notifier.update(cx, |_, cx| { + cx.emit(MigrationEvent::ContentChanged { + migration_type: MigrationType::Settings, + migrating_in_memory, }); - } - cx.refresh_windows(); - }); - } - }) - .detach(); + }); + } + }); + }); } pub fn handle_keymap_file_changes( @@ -4804,7 +4763,7 @@ mod tests { app_state .fs .save( - "/settings.json".as_ref(), + paths::settings_file(), &r#"{"base_keymap": "Atom"}"#.into(), Default::default(), ) @@ -4822,28 +4781,12 @@ mod tests { .unwrap(); executor.run_until_parked(); cx.update(|cx| { - let (settings_rx, settings_watcher) = watch_config_file( - &executor, - app_state.fs.clone(), - PathBuf::from("/settings.json"), - ); let (keymap_rx, keymap_watcher) = watch_config_file( &executor, app_state.fs.clone(), PathBuf::from("/keymap.json"), ); - let (global_settings_rx, global_settings_watcher) = watch_config_file( - &executor, - app_state.fs.clone(), - PathBuf::from("/global_settings.json"), - ); - handle_settings_file_changes( - settings_rx, - settings_watcher, - global_settings_rx, - global_settings_watcher, - cx, - ); + watch_settings_files(app_state.fs.clone(), cx); handle_keymap_file_changes(keymap_rx, keymap_watcher, cx); }); window @@ -4890,7 +4833,7 @@ mod tests { app_state .fs .save( - "/settings.json".as_ref(), + paths::settings_file(), &r#"{"base_keymap": "JetBrains"}"#.into(), Default::default(), ) @@ -4939,7 +4882,7 @@ mod tests { app_state .fs .save( - "/settings.json".as_ref(), + paths::settings_file(), &r#"{"base_keymap": "Atom"}"#.into(), Default::default(), ) @@ -4956,29 +4899,13 @@ mod tests { .unwrap(); cx.update(|cx| { - let (settings_rx, settings_watcher) = watch_config_file( - &executor, - app_state.fs.clone(), - PathBuf::from("/settings.json"), - ); let (keymap_rx, keymap_watcher) = watch_config_file( &executor, app_state.fs.clone(), PathBuf::from("/keymap.json"), ); - let (global_settings_rx, global_settings_watcher) = watch_config_file( - &executor, - app_state.fs.clone(), - PathBuf::from("/global_settings.json"), - ); - handle_settings_file_changes( - settings_rx, - settings_watcher, - global_settings_rx, - global_settings_watcher, - cx, - ); + watch_settings_files(app_state.fs.clone(), cx); handle_keymap_file_changes(keymap_rx, keymap_watcher, cx); }); @@ -5017,7 +4944,7 @@ mod tests { app_state .fs .save( - "/settings.json".as_ref(), + paths::settings_file(), &r#"{"base_keymap": "JetBrains"}"#.into(), Default::default(), ) diff --git a/crates/zed_actions/src/lib.rs b/crates/zed_actions/src/lib.rs index 8f0d3cae3b575f6d9937152275b223dbfc0c3ff2..404a2af27d81ec1b70f3e445c9f1f5eefca49530 100644 --- a/crates/zed_actions/src/lib.rs +++ b/crates/zed_actions/src/lib.rs @@ -72,6 +72,8 @@ actions!( OpenPerformanceProfiler, /// Opens the onboarding view. OpenOnboarding, + /// Shows the auto-update notification for testing. + ShowUpdateNotification, ] ); @@ -518,7 +520,8 @@ pub mod assistant { /// Toggles the agent panel. Toggle, #[action(deprecated_aliases = ["assistant::ToggleFocus"])] - ToggleFocus + ToggleFocus, + FocusAgent, ] );