diff --git a/cli/lsp/capabilities.rs b/cli/lsp/capabilities.rs index e93d3b7c20..5cdb1224d8 100644 --- a/cli/lsp/capabilities.rs +++ b/cli/lsp/capabilities.rs @@ -147,11 +147,11 @@ pub fn server_capabilities( moniker_provider: None, experimental: Some(json!({ "denoConfigTasks": true, - "testingApi":true, + "testingApi": true, + "didRefreshDenoConfigurationTreeNotifications": true, })), inlay_hint_provider: Some(OneOf::Left(true)), position_encoding: None, - // TODO(nayeemrmn): Support pull-based diagnostics. diagnostic_provider: None, inline_value_provider: None, inline_completion_provider: None, diff --git a/cli/lsp/client.rs b/cli/lsp/client.rs index b3f0d64fa6..65865d5b32 100644 --- a/cli/lsp/client.rs +++ b/cli/lsp/client.rs @@ -92,6 +92,19 @@ impl Client { }); } + pub fn send_did_refresh_deno_configuration_tree_notification( + &self, + params: lsp_custom::DidRefreshDenoConfigurationTreeNotificationParams, + ) { + // do on a task in case the caller currently is in the lsp lock + let client = self.0.clone(); + spawn(async move { + client + .send_did_refresh_deno_configuration_tree_notification(params) + .await; + }); + } + pub fn send_did_change_deno_configuration_notification( &self, params: lsp_custom::DidChangeDenoConfigurationNotificationParams, @@ -169,6 +182,10 @@ trait ClientTrait: Send + Sync { params: lsp_custom::DiagnosticBatchNotificationParams, ); async fn send_test_notification(&self, params: TestingNotification); + async fn send_did_refresh_deno_configuration_tree_notification( + &self, + params: lsp_custom::DidRefreshDenoConfigurationTreeNotificationParams, + ); async fn send_did_change_deno_configuration_notification( &self, params: lsp_custom::DidChangeDenoConfigurationNotificationParams, @@ -249,6 +266,18 @@ impl ClientTrait for TowerClient { } } + async fn send_did_refresh_deno_configuration_tree_notification( + &self, + params: lsp_custom::DidRefreshDenoConfigurationTreeNotificationParams, + ) { + self + .0 + .send_notification::( + params, + ) + .await + } + async fn send_did_change_deno_configuration_notification( &self, params: lsp_custom::DidChangeDenoConfigurationNotificationParams, @@ -366,6 +395,12 @@ impl ClientTrait for ReplClient { async fn send_test_notification(&self, _params: TestingNotification) {} + async fn send_did_refresh_deno_configuration_tree_notification( + &self, + _params: lsp_custom::DidRefreshDenoConfigurationTreeNotificationParams, + ) { + } + async fn send_did_change_deno_configuration_notification( &self, _params: lsp_custom::DidChangeDenoConfigurationNotificationParams, diff --git a/cli/lsp/config.rs b/cli/lsp/config.rs index 07fdd3c65a..74f3583d68 100644 --- a/cli/lsp/config.rs +++ b/cli/lsp/config.rs @@ -50,6 +50,8 @@ use std::sync::Arc; use tower_lsp::lsp_types as lsp; use super::logging::lsp_log; +use super::lsp_custom; +use super::urls::url_to_uri; use crate::args::discover_npmrc_from_workspace; use crate::args::has_flag_env_var; use crate::args::CliLockfile; @@ -1716,14 +1718,14 @@ impl ConfigTree { .unwrap_or_else(|| Arc::new(FmtConfig::new_with_base(PathBuf::from("/")))) } - /// Returns (scope_uri, type). + /// Returns (scope_url, type). pub fn watched_file_type( &self, specifier: &ModuleSpecifier, ) -> Option<(&ModuleSpecifier, ConfigWatchedFileType)> { - for (scope_uri, data) in self.scopes.iter() { + for (scope_url, data) in self.scopes.iter() { if let Some(typ) = data.watched_files.get(specifier) { - return Some((scope_uri, *typ)); + return Some((scope_url, *typ)); } } None @@ -1747,6 +1749,46 @@ impl ConfigTree { .any(|data| data.watched_files.contains_key(specifier)) } + pub fn to_did_refresh_params( + &self, + ) -> lsp_custom::DidRefreshDenoConfigurationTreeNotificationParams { + let data = self + .scopes + .values() + .filter_map(|data| { + let workspace_root_scope_uri = + Some(data.member_dir.workspace.root_dir()) + .filter(|s| *s != data.member_dir.dir_url()) + .and_then(|s| url_to_uri(s).ok()); + Some(lsp_custom::DenoConfigurationData { + scope_uri: url_to_uri(&data.scope).ok()?, + deno_json: data.maybe_deno_json().and_then(|c| { + if workspace_root_scope_uri.is_some() + && Some(&c.specifier) + == data + .member_dir + .workspace + .root_deno_json() + .map(|c| &c.specifier) + { + return None; + } + Some(lsp::TextDocumentIdentifier { + uri: url_to_uri(&c.specifier).ok()?, + }) + }), + package_json: data.maybe_pkg_json().and_then(|p| { + Some(lsp::TextDocumentIdentifier { + uri: url_to_uri(&p.specifier()).ok()?, + }) + }), + workspace_root_scope_uri, + }) + }) + .collect(); + lsp_custom::DidRefreshDenoConfigurationTreeNotificationParams { data } + } + pub async fn refresh( &mut self, settings: &Settings, diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs index 8269dc8515..908afa1657 100644 --- a/cli/lsp/language_server.rs +++ b/cli/lsp/language_server.rs @@ -963,6 +963,11 @@ impl Inner { .tree .refresh(&self.config.settings, &self.workspace_files, &file_fetcher) .await; + self + .client + .send_did_refresh_deno_configuration_tree_notification( + self.config.tree.to_did_refresh_params(), + ); for config_file in self.config.tree.config_files() { (|| { let compiler_options = config_file.to_compiler_options().ok()?.options; diff --git a/cli/lsp/lsp_custom.rs b/cli/lsp/lsp_custom.rs index 5f485db7ac..b570b6d0e2 100644 --- a/cli/lsp/lsp_custom.rs +++ b/cli/lsp/lsp_custom.rs @@ -46,6 +46,30 @@ pub struct DiagnosticBatchNotificationParams { pub messages_len: usize, } +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct DenoConfigurationData { + pub scope_uri: lsp::Uri, + pub workspace_root_scope_uri: Option, + pub deno_json: Option, + pub package_json: Option, +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct DidRefreshDenoConfigurationTreeNotificationParams { + pub data: Vec, +} + +pub enum DidRefreshDenoConfigurationTreeNotification {} + +impl lsp::notification::Notification + for DidRefreshDenoConfigurationTreeNotification +{ + type Params = DidRefreshDenoConfigurationTreeNotificationParams; + const METHOD: &'static str = "deno/didRefreshDenoConfigurationTree"; +} + #[derive(Debug, Eq, Hash, PartialEq, Copy, Clone, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub enum DenoConfigurationChangeType { @@ -88,13 +112,15 @@ pub struct DidChangeDenoConfigurationNotificationParams { pub changes: Vec, } +// TODO(nayeemrmn): This is being replaced by +// `DidRefreshDenoConfigurationTreeNotification` for Deno > v2.0.0. Remove it +// soon. pub enum DidChangeDenoConfigurationNotification {} impl lsp::notification::Notification for DidChangeDenoConfigurationNotification { type Params = DidChangeDenoConfigurationNotificationParams; - const METHOD: &'static str = "deno/didChangeDenoConfiguration"; } @@ -102,7 +128,6 @@ pub enum DidUpgradeCheckNotification {} impl lsp::notification::Notification for DidUpgradeCheckNotification { type Params = DidUpgradeCheckNotificationParams; - const METHOD: &'static str = "deno/didUpgradeCheck"; } @@ -125,6 +150,5 @@ pub enum DiagnosticBatchNotification {} impl lsp::notification::Notification for DiagnosticBatchNotification { type Params = DiagnosticBatchNotificationParams; - const METHOD: &'static str = "deno/internalTestDiagnosticBatch"; } diff --git a/tests/integration/lsp_tests.rs b/tests/integration/lsp_tests.rs index 0f2d437558..85e02041ed 100644 --- a/tests/integration/lsp_tests.rs +++ b/tests/integration/lsp_tests.rs @@ -1049,6 +1049,191 @@ fn lsp_workspace_enable_paths_no_workspace_configuration() { client.shutdown(); } +#[test] +fn lsp_did_refresh_deno_configuration_tree_notification() { + let context = TestContextBuilder::new().use_temp_cwd().build(); + let temp_dir = context.temp_dir(); + temp_dir.create_dir_all("workspace/member1"); + temp_dir.create_dir_all("workspace/member2"); + temp_dir.create_dir_all("non_workspace1"); + temp_dir.create_dir_all("non_workspace2"); + temp_dir.write( + "workspace/deno.json", + json!({ + "workspace": [ + "member1", + "member2", + ], + }) + .to_string(), + ); + temp_dir.write("workspace/member1/deno.json", json!({}).to_string()); + temp_dir.write("workspace/member1/package.json", json!({}).to_string()); + temp_dir.write("workspace/member2/package.json", json!({}).to_string()); + temp_dir.write("non_workspace1/deno.json", json!({}).to_string()); + let mut client = context.new_lsp_command().build(); + client.initialize_default(); + let res = client + .read_notification_with_method::( + "deno/didRefreshDenoConfigurationTree", + ) + .unwrap(); + assert_eq!( + res, + json!({ + "data": [ + { + "scopeUri": temp_dir.url().join("non_workspace1/").unwrap(), + "workspaceRootScopeUri": null, + "denoJson": { + "uri": temp_dir.url().join("non_workspace1/deno.json").unwrap(), + }, + "packageJson": null, + }, + { + "scopeUri": temp_dir.url().join("workspace/").unwrap(), + "workspaceRootScopeUri": null, + "denoJson": { + "uri": temp_dir.url().join("workspace/deno.json").unwrap(), + }, + "packageJson": null, + }, + { + "scopeUri": temp_dir.url().join("workspace/member1/").unwrap(), + "workspaceRootScopeUri": temp_dir.url().join("workspace/").unwrap(), + "denoJson": { + "uri": temp_dir.url().join("workspace/member1/deno.json").unwrap(), + }, + "packageJson": { + "uri": temp_dir.url().join("workspace/member1/package.json").unwrap(), + }, + }, + { + "scopeUri": temp_dir.url().join("workspace/member2/").unwrap(), + "workspaceRootScopeUri": temp_dir.url().join("workspace/").unwrap(), + "denoJson": null, + "packageJson": { + "uri": temp_dir.url().join("workspace/member2/package.json").unwrap(), + }, + }, + ], + }), + ); + temp_dir.write("non_workspace2/deno.json", json!({}).to_string()); + client.did_change_watched_files(json!({ + "changes": [{ + "uri": temp_dir.url().join("non_workspace2/deno.json").unwrap(), + "type": 1, + }], + })); + let res = client + .read_notification_with_method::( + "deno/didRefreshDenoConfigurationTree", + ) + .unwrap(); + assert_eq!( + res, + json!({ + "data": [ + { + "scopeUri": temp_dir.url().join("non_workspace1/").unwrap(), + "workspaceRootScopeUri": null, + "denoJson": { + "uri": temp_dir.url().join("non_workspace1/deno.json").unwrap(), + }, + "packageJson": null, + }, + { + "scopeUri": temp_dir.url().join("non_workspace2/").unwrap(), + "workspaceRootScopeUri": null, + "denoJson": { + "uri": temp_dir.url().join("non_workspace2/deno.json").unwrap(), + }, + "packageJson": null, + }, + { + "scopeUri": temp_dir.url().join("workspace/").unwrap(), + "workspaceRootScopeUri": null, + "denoJson": { + "uri": temp_dir.url().join("workspace/deno.json").unwrap(), + }, + "packageJson": null, + }, + { + "scopeUri": temp_dir.url().join("workspace/member1/").unwrap(), + "workspaceRootScopeUri": temp_dir.url().join("workspace/").unwrap(), + "denoJson": { + "uri": temp_dir.url().join("workspace/member1/deno.json").unwrap(), + }, + "packageJson": { + "uri": temp_dir.url().join("workspace/member1/package.json").unwrap(), + }, + }, + { + "scopeUri": temp_dir.url().join("workspace/member2/").unwrap(), + "workspaceRootScopeUri": temp_dir.url().join("workspace/").unwrap(), + "denoJson": null, + "packageJson": { + "uri": temp_dir.url().join("workspace/member2/package.json").unwrap(), + }, + }, + ], + }), + ); + client.change_configuration(json!({ + "deno": { + "disablePaths": ["non_workspace1"], + }, + })); + let res = client + .read_notification_with_method::( + "deno/didRefreshDenoConfigurationTree", + ) + .unwrap(); + assert_eq!( + res, + json!({ + "data": [ + { + "scopeUri": temp_dir.url().join("non_workspace2/").unwrap(), + "workspaceRootScopeUri": null, + "denoJson": { + "uri": temp_dir.url().join("non_workspace2/deno.json").unwrap(), + }, + "packageJson": null, + }, + { + "scopeUri": temp_dir.url().join("workspace/").unwrap(), + "workspaceRootScopeUri": null, + "denoJson": { + "uri": temp_dir.url().join("workspace/deno.json").unwrap(), + }, + "packageJson": null, + }, + { + "scopeUri": temp_dir.url().join("workspace/member1/").unwrap(), + "workspaceRootScopeUri": temp_dir.url().join("workspace/").unwrap(), + "denoJson": { + "uri": temp_dir.url().join("workspace/member1/deno.json").unwrap(), + }, + "packageJson": { + "uri": temp_dir.url().join("workspace/member1/package.json").unwrap(), + }, + }, + { + "scopeUri": temp_dir.url().join("workspace/member2/").unwrap(), + "workspaceRootScopeUri": temp_dir.url().join("workspace/").unwrap(), + "denoJson": null, + "packageJson": { + "uri": temp_dir.url().join("workspace/member2/package.json").unwrap(), + }, + }, + ], + }), + ); + client.shutdown(); +} + #[test] fn lsp_did_change_deno_configuration_notification() { let context = TestContextBuilder::new().use_temp_cwd().build(); @@ -9403,14 +9588,15 @@ fn lsp_auto_discover_registry() { "triggerCharacter": "@" }), ); - let (method, res) = client.read_notification(); - assert_eq!(method, "deno/registryState"); + let res = client + .read_notification_with_method::("deno/registryState") + .unwrap(); assert_eq!( res, - Some(json!({ + json!({ "origin": "http://localhost:4545", "suggestions": true, - })) + }), ); client.shutdown(); } @@ -10117,7 +10303,6 @@ fn lsp_diagnostics_refresh_dependents() { assert_eq!(json!(diagnostics.all()), json!([])); // no diagnostics now client.shutdown(); - assert_eq!(client.queue_len(), 0); } // Regression test for https://github.com/denoland/deno/issues/10897.