diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index e1e60a1ecf..59fca33fb6 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -44,7 +44,7 @@ use syntax_map::SyntaxSnapshot; use theme::{SyntaxTheme, Theme}; use tree_sitter::{self, Query}; use unicase::UniCase; -use util::{ResultExt, TryFutureExt as _, UnwrapFuture}; +use util::{merge_json_value_into, ResultExt, TryFutureExt as _, UnwrapFuture}; #[cfg(any(test, feature = "test-support"))] use futures::channel::mpsc; @@ -208,6 +208,13 @@ pub trait LspAdapter: 'static + Send + Sync { None } + fn workspace_configuration( + &self, + _: &mut MutableAppContext, + ) -> Option> { + None + } + async fn disk_based_diagnostic_sources(&self) -> Vec { Default::default() } @@ -541,6 +548,26 @@ impl LanguageRegistry { result } + pub fn workspace_configuration(&self, cx: &mut MutableAppContext) -> Task { + let mut language_configs = Vec::new(); + for language in self.available_languages.read().iter() { + if let Some(adapter) = language.lsp_adapter.as_ref() { + if let Some(language_config) = adapter.workspace_configuration(cx) { + language_configs.push(language_config); + } + } + } + + cx.background().spawn(async move { + let mut config = serde_json::json!({}); + let language_configs = futures::future::join_all(language_configs).await; + for language_config in language_configs { + merge_json_value_into(language_config, &mut config); + } + config + }) + } + pub fn add(&self, language: Arc) { if let Some(theme) = self.theme.read().clone() { language.set_theme(&theme.editor.syntax); diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index fb4e6b5b80..43c61079bf 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -64,7 +64,7 @@ use std::{ }; use terminals::Terminals; -use util::{debug_panic, defer, post_inc, ResultExt, TryFutureExt as _}; +use util::{debug_panic, defer, merge_json_value_into, post_inc, ResultExt, TryFutureExt as _}; pub use fs::*; pub use worktree::*; @@ -125,6 +125,7 @@ pub struct Project { buffers_being_formatted: HashSet, nonce: u128, _maintain_buffer_languages: Task<()>, + _maintain_workspace_config: Task<()>, terminals: Terminals, } @@ -428,6 +429,7 @@ impl Project { client_subscriptions: Vec::new(), _subscriptions: vec![cx.observe_global::(Self::on_settings_changed)], _maintain_buffer_languages: Self::maintain_buffer_languages(&languages, cx), + _maintain_workspace_config: Self::maintain_workspace_config(languages.clone(), cx), active_entry: None, languages, client, @@ -486,6 +488,7 @@ impl Project { active_entry: None, collaborators: Default::default(), _maintain_buffer_languages: Self::maintain_buffer_languages(&languages, cx), + _maintain_workspace_config: Self::maintain_workspace_config(languages.clone(), cx), languages, user_store: user_store.clone(), fs, @@ -1836,6 +1839,46 @@ impl Project { }) } + fn maintain_workspace_config( + languages: Arc, + cx: &mut ModelContext, + ) -> Task<()> { + let mut languages_changed = languages.subscribe(); + let (mut settings_changed_tx, mut settings_changed_rx) = watch::channel(); + let settings_observation = cx.observe_global::(move |_, _| { + *settings_changed_tx.borrow_mut() = (); + }); + cx.spawn_weak(|this, mut cx| async move { + loop { + futures::select_biased! { + _ = languages_changed.next().fuse() => {}, + _ = settings_changed_rx.next().fuse() => {} + } + + let workspace_config = cx.update(|cx| languages.workspace_configuration(cx)).await; + if let Some(this) = this.upgrade(&cx) { + this.read_with(&cx, |this, _| { + for server_state in this.language_servers.values() { + if let LanguageServerState::Running { server, .. } = server_state { + server + .notify::( + lsp::DidChangeConfigurationParams { + settings: workspace_config.clone(), + }, + ) + .ok(); + } + } + }) + } else { + break; + } + } + + drop(settings_observation); + }) + } + fn detect_language_for_buffer( &mut self, buffer: &ModelHandle, @@ -1875,24 +1918,6 @@ impl Project { } } - fn merge_json_value_into(source: serde_json::Value, target: &mut serde_json::Value) { - use serde_json::Value; - - match (source, target) { - (Value::Object(source), Value::Object(target)) => { - for (key, value) in source { - if let Some(target) = target.get_mut(&key) { - Self::merge_json_value_into(value, target); - } else { - target.insert(key.clone(), value); - } - } - } - - (source, target) => *target = source, - } - } - fn start_language_server( &mut self, worktree_id: WorktreeId, @@ -1920,17 +1945,16 @@ impl Project { let override_options = lsp.map(|s| s.initialization_options.clone()).flatten(); match (&mut initialization_options, override_options) { (Some(initialization_options), Some(override_options)) => { - Self::merge_json_value_into(override_options, initialization_options); + merge_json_value_into(override_options, initialization_options); } - (None, override_options) => initialization_options = override_options, - _ => {} } self.language_server_ids .entry(key.clone()) .or_insert_with(|| { + let languages = self.languages.clone(); let server_id = post_inc(&mut self.next_language_server_id); let language_server = self.languages.start_language_server( server_id, @@ -1977,23 +2001,24 @@ impl Project { language_server .on_request::({ - let settings = this.read_with(&cx, |this, _| { - this.language_server_settings.clone() - }); - move |params, _| { - let settings = settings.lock().clone(); + move |params, mut cx| { + let languages = languages.clone(); async move { + let workspace_config = cx + .update(|cx| languages.workspace_configuration(cx)) + .await; + Ok(params .items .into_iter() .map(|item| { if let Some(section) = &item.section { - settings + workspace_config .get(section) .cloned() .unwrap_or(serde_json::Value::Null) } else { - settings.clone() + workspace_config.clone() } }) .collect()) @@ -2539,21 +2564,6 @@ impl Project { } } - pub fn set_language_server_settings(&mut self, settings: serde_json::Value) { - for server_state in self.language_servers.values() { - if let LanguageServerState::Running { server, .. } = server_state { - server - .notify::( - lsp::DidChangeConfigurationParams { - settings: settings.clone(), - }, - ) - .ok(); - } - } - *self.language_server_settings.lock() = settings; - } - pub fn language_server_statuses( &self, ) -> impl DoubleEndedIterator { diff --git a/crates/util/Cargo.toml b/crates/util/Cargo.toml index e8c158b637..b13b8af956 100644 --- a/crates/util/Cargo.toml +++ b/crates/util/Cargo.toml @@ -9,7 +9,7 @@ path = "src/util.rs" doctest = false [features] -test-support = ["serde_json", "tempdir", "git2"] +test-support = ["tempdir", "git2"] [dependencies] anyhow = "1.0.38" @@ -19,11 +19,10 @@ log = { version = "0.4.16", features = ["kv_unstable_serde"] } lazy_static = "1.4.0" rand = { workspace = true } tempdir = { version = "0.3.7", optional = true } -serde_json = { version = "1.0", features = ["preserve_order"], optional = true } +serde_json = { version = "1.0", features = ["preserve_order"] } git2 = { version = "0.15", default-features = false, optional = true } dirs = "3.0" [dev-dependencies] tempdir = { version = "0.3.7" } -serde_json = { version = "1.0", features = ["preserve_order"] } git2 = { version = "0.15", default-features = false } diff --git a/crates/util/src/util.rs b/crates/util/src/util.rs index 3824312a4f..6a5ccb8bd5 100644 --- a/crates/util/src/util.rs +++ b/crates/util/src/util.rs @@ -83,6 +83,24 @@ where } } +pub fn merge_json_value_into(source: serde_json::Value, target: &mut serde_json::Value) { + use serde_json::Value; + + match (source, target) { + (Value::Object(source), Value::Object(target)) => { + for (key, value) in source { + if let Some(target) = target.get_mut(&key) { + merge_json_value_into(value, target); + } else { + target.insert(key.clone(), value); + } + } + } + + (source, target) => *target = source, + } +} + pub trait ResultExt { type Ok; diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index a99c80c001..5d5ffc4f95 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -2,6 +2,7 @@ use anyhow::Context; pub use language::*; use rust_embed::RustEmbed; use std::{borrow::Cow, str, sync::Arc}; +use theme::ThemeRegistry; mod c; mod elixir; @@ -31,7 +32,7 @@ mod yaml; #[exclude = "*.rs"] struct LanguageDir; -pub fn init(languages: Arc) { +pub fn init(languages: Arc, themes: Arc) { for (name, grammar, lsp_adapter) in [ ( "c", @@ -61,7 +62,10 @@ pub fn init(languages: Arc) { ( "json", tree_sitter_json::language(), - Some(Box::new(json::JsonLspAdapter)), + Some(Box::new(json::JsonLspAdapter::new( + languages.clone(), + themes.clone(), + ))), ), ( "markdown", diff --git a/crates/zed/src/languages/json.rs b/crates/zed/src/languages/json.rs index d7f87bee6c..d5c59d28d1 100644 --- a/crates/zed/src/languages/json.rs +++ b/crates/zed/src/languages/json.rs @@ -4,14 +4,32 @@ use async_compression::futures::bufread::GzipDecoder; use async_trait::async_trait; use client::http::HttpClient; use collections::HashMap; -use futures::{io::BufReader, StreamExt}; -use language::{LanguageServerName, LspAdapter}; +use futures::{future::BoxFuture, io::BufReader, FutureExt, StreamExt}; +use gpui::MutableAppContext; +use language::{LanguageRegistry, LanguageServerName, LspAdapter}; use serde_json::json; +use settings::{keymap_file_json_schema, settings_file_json_schema}; use smol::fs::{self, File}; -use std::{any::Any, env::consts, path::PathBuf, sync::Arc}; -use util::ResultExt; +use std::{ + any::Any, + env::consts, + future, + path::{Path, PathBuf}, + sync::Arc, +}; +use theme::ThemeRegistry; +use util::{paths, ResultExt, StaffMode}; -pub struct JsonLspAdapter; +pub struct JsonLspAdapter { + languages: Arc, + themes: Arc, +} + +impl JsonLspAdapter { + pub fn new(languages: Arc, themes: Arc) -> Self { + Self { languages, themes } + } +} #[async_trait] impl LspAdapter for JsonLspAdapter { @@ -102,7 +120,45 @@ impl LspAdapter for JsonLspAdapter { })) } + fn workspace_configuration( + &self, + cx: &mut MutableAppContext, + ) -> Option> { + let action_names = cx.all_action_names().collect::>(); + let theme_names = self + .themes + .list(**cx.default_global::()) + .map(|meta| meta.name) + .collect(); + let language_names = self.languages.language_names(); + Some( + future::ready(serde_json::json!({ + "json": { + "format": { + "enable": true, + }, + "schemas": [ + { + "fileMatch": [schema_file_match(&paths::SETTINGS)], + "schema": settings_file_json_schema(theme_names, &language_names), + }, + { + "fileMatch": [schema_file_match(&paths::KEYMAP)], + "schema": keymap_file_json_schema(&action_names), + } + ] + } + })) + .boxed(), + ) + } + async fn language_ids(&self) -> HashMap { [("JSON".into(), "jsonc".into())].into_iter().collect() } } + +fn schema_file_match(path: &Path) -> &Path { + path.strip_prefix(path.parent().unwrap().parent().unwrap()) + .unwrap() +} diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 8cf6879f7a..6b51155498 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -139,7 +139,7 @@ fn main() { languages.set_executor(cx.background().clone()); languages.set_language_server_download_dir(paths::LANGUAGES_DIR.clone()); let languages = Arc::new(languages); - languages::init(languages.clone()); + languages::init(languages.clone(), themes.clone()); let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http.clone(), cx)); cx.set_global(client.clone()); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 83a8038d77..b0239d234c 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -29,10 +29,10 @@ use project_panel::ProjectPanel; use search::{BufferSearchBar, ProjectSearchBar}; use serde::Deserialize; use serde_json::to_string_pretty; -use settings::{keymap_file_json_schema, settings_file_json_schema, Settings}; +use settings::Settings; use std::{borrow::Cow, env, path::Path, str, sync::Arc}; use terminal_view::terminal_button::{self, TerminalButton}; -use util::{channel::ReleaseChannel, paths, ResultExt, StaffMode}; +use util::{channel::ReleaseChannel, paths, ResultExt}; use uuid::Uuid; pub use workspace; use workspace::{sidebar::SidebarSide, AppState, Restart, Workspace}; @@ -296,34 +296,6 @@ pub fn initialize_workspace( cx.emit(workspace::Event::PaneAdded(workspace.active_pane().clone())); cx.emit(workspace::Event::PaneAdded(workspace.dock_pane().clone())); - let theme_names = app_state - .themes - .list(**cx.default_global::()) - .map(|meta| meta.name) - .collect(); - let language_names = app_state.languages.language_names(); - - workspace.project().update(cx, |project, cx| { - let action_names = cx.all_action_names().collect::>(); - project.set_language_server_settings(serde_json::json!({ - "json": { - "format": { - "enable": true, - }, - "schemas": [ - { - "fileMatch": [schema_file_match(&paths::SETTINGS)], - "schema": settings_file_json_schema(theme_names, &language_names), - }, - { - "fileMatch": [schema_file_match(&paths::KEYMAP)], - "schema": keymap_file_json_schema(&action_names), - } - ] - } - })); - }); - let collab_titlebar_item = cx.add_view(|cx| CollabTitlebarItem::new(&workspace_handle, &app_state.user_store, cx)); workspace.set_titlebar_item(collab_titlebar_item, cx); @@ -676,11 +648,6 @@ fn open_bundled_file( .detach(); } -fn schema_file_match(path: &Path) -> &Path { - path.strip_prefix(path.parent().unwrap().parent().unwrap()) - .unwrap() -} - #[cfg(test)] mod tests { use super::*; @@ -1882,7 +1849,8 @@ mod tests { let mut languages = LanguageRegistry::new(Task::ready(())); languages.set_executor(cx.background().clone()); let languages = Arc::new(languages); - languages::init(languages.clone()); + let themes = ThemeRegistry::new((), cx.font_cache().clone()); + languages::init(languages.clone(), themes); for name in languages.language_names() { languages.language_for_name(&name); }