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, ] );