diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 46dd3b7d9e51aa06aa45b9cccb87533f2b90f58c..4adebabc5a03636ca81fbc3b04a277c2d6d03a66 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -1271,6 +1271,7 @@ impl Project { fs.clone(), worktree_store.clone(), task_store.clone(), + Some(remote_proto.clone()), cx, ) }); @@ -1521,7 +1522,13 @@ impl Project { })?; let settings_observer = cx.new(|cx| { - SettingsObserver::new_remote(fs.clone(), worktree_store.clone(), task_store.clone(), cx) + SettingsObserver::new_remote( + fs.clone(), + worktree_store.clone(), + task_store.clone(), + None, + cx, + ) })?; let git_store = cx.new(|cx| { diff --git a/crates/project/src/project_settings.rs b/crates/project/src/project_settings.rs index c98065116e00fd6c643a2c809cf6e8fb1c51532b..57969ec9938602b477293aa3033a31bc8b3deae1 100644 --- a/crates/project/src/project_settings.rs +++ b/crates/project/src/project_settings.rs @@ -4,7 +4,7 @@ use context_server::ContextServerCommand; use dap::adapters::DebugAdapterName; use fs::Fs; use futures::StreamExt as _; -use gpui::{App, AsyncApp, BorrowAppContext, Context, Entity, EventEmitter, Task}; +use gpui::{App, AsyncApp, BorrowAppContext, Context, Entity, EventEmitter, Subscription, Task}; use lsp::LanguageServerName; use paths::{ EDITORCONFIG_NAME, local_debug_file_relative_path, local_settings_file_relative_path, @@ -13,7 +13,7 @@ use paths::{ }; use rpc::{ AnyProtoClient, TypedEnvelope, - proto::{self, FromProto, ToProto}, + proto::{self, FromProto, REMOTE_SERVER_PROJECT_ID, ToProto}, }; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -658,6 +658,7 @@ pub struct SettingsObserver { worktree_store: Entity, project_id: u64, task_store: Entity, + _user_settings_watcher: Option, _global_task_config_watcher: Task<()>, _global_debug_config_watcher: Task<()>, } @@ -670,6 +671,7 @@ pub struct SettingsObserver { impl SettingsObserver { pub fn init(client: &AnyProtoClient) { client.add_entity_message_handler(Self::handle_update_worktree_settings); + client.add_entity_message_handler(Self::handle_update_user_settings); } pub fn new_local( @@ -686,7 +688,8 @@ impl SettingsObserver { task_store, mode: SettingsObserverMode::Local(fs.clone()), downstream_client: None, - project_id: 0, + _user_settings_watcher: None, + project_id: REMOTE_SERVER_PROJECT_ID, _global_task_config_watcher: Self::subscribe_to_global_task_file_changes( fs.clone(), paths::tasks_file().clone(), @@ -704,14 +707,38 @@ impl SettingsObserver { fs: Arc, worktree_store: Entity, task_store: Entity, + upstream_client: Option, cx: &mut Context, ) -> Self { + let mut user_settings_watcher = None; + if cx.try_global::().is_some() { + if let Some(upstream_client) = upstream_client { + let mut user_settings = None; + user_settings_watcher = Some(cx.observe_global::(move |_, cx| { + let new_settings = cx.global::().raw_user_settings(); + if Some(new_settings) != user_settings.as_ref() { + if let Some(new_settings_string) = serde_json::to_string(new_settings).ok() + { + user_settings = Some(new_settings.clone()); + upstream_client + .send(proto::UpdateUserSettings { + project_id: REMOTE_SERVER_PROJECT_ID, + contents: new_settings_string, + }) + .log_err(); + } + } + })); + } + }; + Self { worktree_store, task_store, mode: SettingsObserverMode::Remote, downstream_client: None, - project_id: 0, + project_id: REMOTE_SERVER_PROJECT_ID, + _user_settings_watcher: user_settings_watcher, _global_task_config_watcher: Self::subscribe_to_global_task_file_changes( fs.clone(), paths::tasks_file().clone(), @@ -803,6 +830,24 @@ impl SettingsObserver { Ok(()) } + async fn handle_update_user_settings( + _: Entity, + envelope: TypedEnvelope, + cx: AsyncApp, + ) -> anyhow::Result<()> { + let new_settings = serde_json::from_str::(&envelope.payload.contents) + .with_context(|| { + format!("deserializing {} user settings", envelope.payload.contents) + })?; + cx.update_global(|settings_store: &mut SettingsStore, cx| { + settings_store + .set_raw_user_settings(new_settings, cx) + .context("setting new user settings")?; + anyhow::Ok(()) + })??; + Ok(()) + } + fn on_worktree_store_event( &mut self, _: Entity, @@ -1089,7 +1134,7 @@ impl SettingsObserver { project_id: self.project_id, worktree_id: remote_worktree_id.to_proto(), path: directory.to_proto(), - content: file_content, + content: file_content.clone(), kind: Some(local_settings_kind_to_proto(kind).into()), }) .log_err(); diff --git a/crates/proto/proto/worktree.proto b/crates/proto/proto/worktree.proto index 67bd1925b509c6fc7727fa5cf6338e6cc00a4ae0..19a61cc4bc8d3b04103afe3a6c6b799ab92461e3 100644 --- a/crates/proto/proto/worktree.proto +++ b/crates/proto/proto/worktree.proto @@ -150,3 +150,8 @@ enum LocalSettingsKind { Editorconfig = 2; Debug = 3; } + +message UpdateUserSettings { + uint64 project_id = 1; + string contents = 2; +} diff --git a/crates/proto/proto/zed.proto b/crates/proto/proto/zed.proto index 2222bdec082759cb75ffcdb2c7a95435f36eba11..4133b4b5eea6f14e2c9359f7318f192a8566d809 100644 --- a/crates/proto/proto/zed.proto +++ b/crates/proto/proto/zed.proto @@ -397,7 +397,9 @@ message Envelope { LspQuery lsp_query = 365; LspQueryResponse lsp_query_response = 366; - ToggleLspLogs toggle_lsp_logs = 367; // current max + ToggleLspLogs toggle_lsp_logs = 367; + + UpdateUserSettings update_user_settings = 368; // current max } reserved 87 to 88; diff --git a/crates/proto/src/proto.rs b/crates/proto/src/proto.rs index 04495fb898b1d9bdbf229bb69e1e44b8afa6d1fb..8f4e836b20ae5bae43617e10391f75c3a069a82f 100644 --- a/crates/proto/src/proto.rs +++ b/crates/proto/src/proto.rs @@ -278,6 +278,7 @@ messages!( (UpdateUserChannels, Foreground), (UpdateWorktree, Foreground), (UpdateWorktreeSettings, Foreground), + (UpdateUserSettings, Background), (UpdateRepository, Foreground), (RemoveRepository, Foreground), (UsersResponse, Foreground), @@ -583,6 +584,7 @@ entity_messages!( UpdateRepository, RemoveRepository, UpdateWorktreeSettings, + UpdateUserSettings, LspExtExpandMacro, LspExtOpenDocs, LspExtRunnables, diff --git a/crates/remote_server/src/remote_editing_tests.rs b/crates/remote_server/src/remote_editing_tests.rs index 353857f5871551a20315f638aa3d9653b3ed2848..c0ccaf900d18ee176bab7193c2bfb65b8555318d 100644 --- a/crates/remote_server/src/remote_editing_tests.rs +++ b/crates/remote_server/src/remote_editing_tests.rs @@ -280,7 +280,8 @@ async fn test_remote_settings(cx: &mut TestAppContext, server_cx: &mut TestAppCo AllLanguageSettings::get_global(cx) .language(None, Some(&"Rust".into()), cx) .language_servers, - ["..."] // local settings are ignored + ["from-local-settings"], + "User language settings should be synchronized with the server settings" ) }); @@ -300,7 +301,8 @@ async fn test_remote_settings(cx: &mut TestAppContext, server_cx: &mut TestAppCo AllLanguageSettings::get_global(cx) .language(None, Some(&"Rust".into()), cx) .language_servers, - ["from-server-settings".to_string()] + ["from-server-settings".to_string()], + "Server language settings should take precedence over the user settings" ) }); diff --git a/crates/remote_server/src/unix.rs b/crates/remote_server/src/unix.rs index cb671a72d9beab0983536571e81fcd78f3df21c8..4aef536f0a45b5ea943f861da2be94ab7c2c21c4 100644 --- a/crates/remote_server/src/unix.rs +++ b/crates/remote_server/src/unix.rs @@ -918,29 +918,33 @@ fn initialize_settings( }); let (mut tx, rx) = watch::channel(None); + let mut node_settings = None; cx.observe_global::(move |cx| { - let settings = &ProjectSettings::get_global(cx).node; - log::info!("Got new node settings: {:?}", settings); - let options = NodeBinaryOptions { - allow_path_lookup: !settings.ignore_system_version, - // TODO: Implement this setting - allow_binary_download: true, - use_paths: settings.path.as_ref().map(|node_path| { - let node_path = PathBuf::from(shellexpand::tilde(node_path).as_ref()); - let npm_path = settings - .npm_path - .as_ref() - .map(|path| PathBuf::from(shellexpand::tilde(&path).as_ref())); - ( - node_path.clone(), - npm_path.unwrap_or_else(|| { - let base_path = PathBuf::new(); - node_path.parent().unwrap_or(&base_path).join("npm") - }), - ) - }), - }; - tx.send(Some(options)).log_err(); + let new_node_settings = &ProjectSettings::get_global(cx).node; + if Some(new_node_settings) != node_settings.as_ref() { + log::info!("Got new node settings: {new_node_settings:?}"); + let options = NodeBinaryOptions { + allow_path_lookup: !new_node_settings.ignore_system_version, + // TODO: Implement this setting + allow_binary_download: true, + use_paths: new_node_settings.path.as_ref().map(|node_path| { + let node_path = PathBuf::from(shellexpand::tilde(node_path).as_ref()); + let npm_path = new_node_settings + .npm_path + .as_ref() + .map(|path| PathBuf::from(shellexpand::tilde(&path).as_ref())); + ( + node_path.clone(), + npm_path.unwrap_or_else(|| { + let base_path = PathBuf::new(); + node_path.parent().unwrap_or(&base_path).join("npm") + }), + ) + }), + }; + node_settings = Some(new_node_settings.clone()); + tx.send(Some(options)).ok(); + } }) .detach(); diff --git a/crates/settings/src/settings_store.rs b/crates/settings/src/settings_store.rs index 60eb132ad8b4f6419f463f32b1874ea97be07ec1..72df08d14fb61536d147b4d1fb8b9a2466f5f0aa 100644 --- a/crates/settings/src/settings_store.rs +++ b/crates/settings/src/settings_store.rs @@ -467,6 +467,13 @@ impl SettingsStore { &self.raw_user_settings } + /// Replaces current settings with the values from the given JSON. + pub fn set_raw_user_settings(&mut self, new_settings: Value, cx: &mut App) -> Result<()> { + self.raw_user_settings = new_settings; + self.recompute_values(None, cx)?; + Ok(()) + } + /// Get the configured settings profile names. pub fn configured_settings_profiles(&self) -> impl Iterator { self.raw_user_settings @@ -525,20 +532,6 @@ impl SettingsStore { } } - pub async fn load_global_settings(fs: &Arc) -> Result { - match fs.load(paths::global_settings_file()).await { - result @ Ok(_) => result, - Err(err) => { - if let Some(e) = err.downcast_ref::() - && e.kind() == std::io::ErrorKind::NotFound - { - return Ok("{}".to_string()); - } - Err(err) - } - } - } - fn update_settings_file_inner( &self, fs: Arc,