diff options
Diffstat (limited to 'cli/lsp')
-rw-r--r-- | cli/lsp/capabilities.rs | 2 | ||||
-rw-r--r-- | cli/lsp/config.rs | 175 | ||||
-rw-r--r-- | cli/lsp/diagnostics.rs | 6 | ||||
-rw-r--r-- | cli/lsp/language_server.rs | 90 | ||||
-rw-r--r-- | cli/lsp/repl.rs | 1 | ||||
-rw-r--r-- | cli/lsp/tsc.rs | 5 |
6 files changed, 226 insertions, 53 deletions
diff --git a/cli/lsp/capabilities.rs b/cli/lsp/capabilities.rs index 87d2c31da..49adc961d 100644 --- a/cli/lsp/capabilities.rs +++ b/cli/lsp/capabilities.rs @@ -154,7 +154,7 @@ pub fn server_capabilities( workspace: Some(WorkspaceServerCapabilities { workspace_folders: Some(WorkspaceFoldersServerCapabilities { supported: Some(true), - change_notifications: None, + change_notifications: Some(OneOf::Left(true)), }), file_operations: None, }), diff --git a/cli/lsp/config.rs b/cli/lsp/config.rs index d35451e84..7b4294943 100644 --- a/cli/lsp/config.rs +++ b/cli/lsp/config.rs @@ -1,12 +1,14 @@ // Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. +use super::client::Client; +use super::logging::lsp_log; +use crate::fs_util; use deno_core::error::AnyError; use deno_core::serde::Deserialize; use deno_core::serde::Serialize; use deno_core::serde_json; use deno_core::serde_json::Value; use deno_core::ModuleSpecifier; -use lsp::WorkspaceFolder; use lspower::lsp; use std::collections::BTreeMap; use std::collections::HashMap; @@ -128,6 +130,10 @@ impl Default for ImportCompletionSettings { pub struct SpecifierSettings { /// A flag that indicates if Deno is enabled for this specifier or not. pub enable: bool, + /// A list of paths, using the workspace folder as a base that should be Deno + /// enabled. + #[serde(default)] + pub enable_paths: Vec<String>, /// Code lens specific settings for the resource. #[serde(default)] pub code_lens: CodeLensSpecifierSettings, @@ -141,6 +147,10 @@ pub struct WorkspaceSettings { #[serde(default)] pub enable: bool, + /// A list of paths, using the root_uri as a base that should be Deno enabled. + #[serde(default)] + pub enable_paths: Vec<String>, + /// An option that points to a path string of the path to utilise as the /// cache/DENO_DIR for the language server. pub cache: Option<String>, @@ -198,14 +208,27 @@ impl WorkspaceSettings { #[derive(Debug, Clone, Default)] pub struct ConfigSnapshot { pub client_capabilities: ClientCapabilities, + pub enabled_paths: HashMap<String, Vec<String>>, pub settings: Settings, - pub workspace_folders: Option<Vec<lsp::WorkspaceFolder>>, } impl ConfigSnapshot { + /// Determine if the provided specifier is enabled or not. pub fn specifier_enabled(&self, specifier: &ModuleSpecifier) -> bool { - if let Some(settings) = self.settings.specifiers.get(specifier) { - settings.1.enable + if !self.enabled_paths.is_empty() { + let specifier_str = specifier.to_string(); + for (workspace, enabled_paths) in self.enabled_paths.iter() { + if specifier_str.starts_with(workspace) { + return enabled_paths + .iter() + .any(|path| specifier_str.starts_with(path)); + } + } + } + if let Some((_, SpecifierSettings { enable, .. })) = + self.settings.specifiers.get(specifier) + { + *enable } else { self.settings.workspace.enable } @@ -228,14 +251,19 @@ pub struct Settings { #[derive(Debug)] pub struct Config { pub client_capabilities: ClientCapabilities, + enabled_paths: HashMap<String, Vec<String>>, + pub root_uri: Option<ModuleSpecifier>, settings: Settings, - pub workspace_folders: Option<Vec<WorkspaceFolder>>, + pub workspace_folders: Option<Vec<(ModuleSpecifier, lsp::WorkspaceFolder)>>, } impl Config { pub fn new() -> Self { Self { client_capabilities: ClientCapabilities::default(), + enabled_paths: Default::default(), + /// Root provided by the initialization parameters. + root_uri: None, settings: Default::default(), workspace_folders: None, } @@ -259,8 +287,8 @@ impl Config { pub fn snapshot(&self) -> Arc<ConfigSnapshot> { Arc::new(ConfigSnapshot { client_capabilities: self.client_capabilities.clone(), + enabled_paths: self.enabled_paths.clone(), settings: self.settings.clone(), - workspace_folders: self.workspace_folders.clone(), }) } @@ -269,6 +297,16 @@ impl Config { } pub fn specifier_enabled(&self, specifier: &ModuleSpecifier) -> bool { + if !self.enabled_paths.is_empty() { + let specifier_str = specifier.to_string(); + for (workspace, enabled_paths) in self.enabled_paths.iter() { + if specifier_str.starts_with(workspace) { + return enabled_paths + .iter() + .any(|path| specifier_str.starts_with(path)); + } + } + } self .settings .specifiers @@ -321,6 +359,66 @@ impl Config { } } + /// Given the configured workspaces or root URI and the their settings, + /// update and resolve any paths that should be enabled + pub async fn update_enabled_paths(&mut self, client: Client) -> bool { + if let Some(workspace_folders) = self.workspace_folders.clone() { + let mut touched = false; + for (workspace, folder) in workspace_folders { + if let Ok(settings) = client.specifier_configuration(&folder.uri).await + { + if self.update_enabled_paths_entry(&workspace, settings.enable_paths) + { + touched = true; + } + } + } + touched + } else if let Some(root_uri) = self.root_uri.clone() { + self.update_enabled_paths_entry( + &root_uri, + self.settings.workspace.enable_paths.clone(), + ) + } else { + false + } + } + + /// Update a specific entry in the enabled paths for a given workspace. + fn update_enabled_paths_entry( + &mut self, + workspace: &ModuleSpecifier, + enabled_paths: Vec<String>, + ) -> bool { + let workspace = fs_util::ensure_directory_specifier(workspace.clone()); + let key = workspace.to_string(); + let mut touched = false; + if !enabled_paths.is_empty() { + if let Ok(workspace_path) = fs_util::specifier_to_file_path(&workspace) { + let mut paths = Vec::new(); + for path in &enabled_paths { + let fs_path = workspace_path.join(path); + match ModuleSpecifier::from_file_path(fs_path) { + Ok(path_uri) => { + paths.push(path_uri.to_string()); + } + Err(_) => { + lsp_log!("Unable to resolve a file path for `deno.enablePath` from \"{}\" for workspace \"{}\".", path, workspace); + } + } + } + if !paths.is_empty() { + touched = true; + self.enabled_paths.insert(key, paths); + } + } + } else { + touched = true; + self.enabled_paths.remove(&key); + } + touched + } + pub fn get_specifiers_with_client_uris(&self) -> Vec<SpecifierWithClientUri> { self .settings @@ -330,7 +428,7 @@ impl Config { specifier: s.clone(), client_uri: u.clone(), }) - .collect::<Vec<_>>() + .collect() } pub fn set_specifier_settings( @@ -352,33 +450,9 @@ mod tests { use deno_core::resolve_url; use deno_core::serde_json::json; - #[derive(Debug, Default)] - struct MockLanguageServer; - - #[lspower::async_trait] - impl lspower::LanguageServer for MockLanguageServer { - async fn initialize( - &self, - _params: lspower::lsp::InitializeParams, - ) -> lspower::jsonrpc::Result<lsp::InitializeResult> { - Ok(lspower::lsp::InitializeResult { - capabilities: lspower::lsp::ServerCapabilities::default(), - server_info: None, - }) - } - - async fn shutdown(&self) -> lspower::jsonrpc::Result<()> { - Ok(()) - } - } - - fn setup() -> Config { - Config::new() - } - #[test] fn test_config_specifier_enabled() { - let mut config = setup(); + let mut config = Config::new(); let specifier = resolve_url("file:///a.ts").unwrap(); assert!(!config.specifier_enabled(&specifier)); config @@ -390,8 +464,42 @@ mod tests { } #[test] + fn test_config_snapshot_specifier_enabled() { + let mut config = Config::new(); + let specifier = resolve_url("file:///a.ts").unwrap(); + assert!(!config.specifier_enabled(&specifier)); + config + .set_workspace_settings(json!({ + "enable": true + })) + .expect("could not update"); + let config_snapshot = config.snapshot(); + assert!(config_snapshot.specifier_enabled(&specifier)); + } + + #[test] + fn test_config_specifier_enabled_path() { + let mut config = Config::new(); + let specifier_a = resolve_url("file:///project/worker/a.ts").unwrap(); + let specifier_b = resolve_url("file:///project/other/b.ts").unwrap(); + assert!(!config.specifier_enabled(&specifier_a)); + assert!(!config.specifier_enabled(&specifier_b)); + let mut enabled_paths = HashMap::new(); + enabled_paths.insert( + "file:///project/".to_string(), + vec!["file:///project/worker/".to_string()], + ); + config.enabled_paths = enabled_paths; + assert!(config.specifier_enabled(&specifier_a)); + assert!(!config.specifier_enabled(&specifier_b)); + let config_snapshot = config.snapshot(); + assert!(config_snapshot.specifier_enabled(&specifier_a)); + assert!(!config_snapshot.specifier_enabled(&specifier_b)); + } + + #[test] fn test_set_workspace_settings_defaults() { - let mut config = setup(); + let mut config = Config::new(); config .set_workspace_settings(json!({})) .expect("could not update"); @@ -399,6 +507,7 @@ mod tests { config.get_workspace_settings(), WorkspaceSettings { enable: false, + enable_paths: Vec::new(), cache: None, certificate_stores: None, config: None, diff --git a/cli/lsp/diagnostics.rs b/cli/lsp/diagnostics.rs index 8801af722..8a515ef3c 100644 --- a/cli/lsp/diagnostics.rs +++ b/cli/lsp/diagnostics.rs @@ -845,7 +845,8 @@ async fn generate_deps_diagnostics( break; } let mut diagnostics = Vec::new(); - if config.specifier_enabled(document.specifier()) { + let specifier = document.specifier(); + if config.specifier_enabled(specifier) { for (_, dependency) in document.dependencies() { diagnose_dependency( &mut diagnostics, @@ -866,7 +867,7 @@ async fn generate_deps_diagnostics( } } diagnostics_vec.push(( - document.specifier().clone(), + specifier.clone(), document.maybe_lsp_version(), diagnostics, )); @@ -985,6 +986,7 @@ let c: number = "a"; specifier.clone(), SpecifierSettings { enable: false, + enable_paths: Vec::new(), code_lens: Default::default(), }, ), diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs index f9dfce4d5..5db7011bb 100644 --- a/cli/lsp/language_server.rs +++ b/cli/lsp/language_server.rs @@ -117,8 +117,6 @@ pub(crate) struct Inner { maybe_import_map_uri: Option<Url>, /// A collection of measurements which instrument that performance of the LSP. performance: Arc<Performance>, - /// Root provided by the initialization parameters. - root_uri: Option<Url>, /// A memoized version of fixable diagnostic codes retrieved from TypeScript. ts_fixable_diagnostics: Vec<String>, /// An abstraction that handles interactions with TypeScript. @@ -173,7 +171,6 @@ impl Inner { maybe_import_map_uri: None, module_registries, module_registries_location, - root_uri: None, performance, ts_fixable_diagnostics: Default::default(), ts_server, @@ -306,10 +303,10 @@ impl Inner { let maybe_config = workspace_settings.config; if let Some(config_str) = &maybe_config { if !config_str.is_empty() { - lsp_log!("Setting TypeScript configuration from: \"{}\"", config_str); + lsp_log!("Setting Deno configuration from: \"{}\"", config_str); let config_url = if let Ok(url) = Url::from_file_path(config_str) { Ok(url) - } else if let Some(root_uri) = &self.root_uri { + } else if let Some(root_uri) = &self.config.root_uri { root_uri.join(config_str).map_err(|_| { anyhow!("Bad file path for configuration file: \"{}\"", config_str) }) @@ -331,7 +328,7 @@ impl Inner { // It is possible that root_uri is not set, for example when having a single // file open and not a workspace. In those situations we can't // automatically discover the configuration - if let Some(root_uri) = &self.root_uri { + if let Some(root_uri) = &self.config.root_uri { let root_path = fs_util::specifier_to_file_path(root_uri)?; let mut checked = std::collections::HashSet::new(); let maybe_config = @@ -392,7 +389,7 @@ impl Inner { assets: self.assets.snapshot(), cache_metadata: self.cache_metadata.clone(), documents: self.documents.clone(), - root_uri: self.root_uri.clone(), + root_uri: self.config.root_uri.clone(), }) } @@ -405,7 +402,7 @@ impl Inner { lsp_log!("Setting cache path from: \"{}\"", cache_str); let cache_url = if let Ok(url) = Url::from_file_path(cache_str) { Ok(url) - } else if let Some(root_uri) = &self.root_uri { + } else if let Some(root_uri) = &self.config.root_uri { let root_path = fs_util::specifier_to_file_path(root_uri)?; let cache_path = root_path.join(cache_str); Url::from_file_path(cache_path).map_err(|_| { @@ -434,6 +431,7 @@ impl Inner { let module_registries_location = dir.root.join(REGISTRIES_PATH); let workspace_settings = self.config.get_workspace_settings(); let maybe_root_path = self + .config .root_uri .as_ref() .and_then(|uri| fs_util::specifier_to_file_path(uri).ok()); @@ -477,7 +475,7 @@ impl Inner { Some(Url::parse(&import_map_str).map_err(|_| { anyhow!("Bad data url for import map: {}", import_map_str) })?) - } else if let Some(root_uri) = &self.root_uri { + } else if let Some(root_uri) = &self.config.root_uri { let root_path = fs_util::specifier_to_file_path(root_uri)?; let import_map_path = root_path.join(&import_map_str); Some(Url::from_file_path(import_map_path).map_err(|_| { @@ -554,6 +552,7 @@ impl Inner { let mark = self.performance.mark("update_registries", None::<()>); let workspace_settings = self.config.get_workspace_settings(); let maybe_root_path = self + .config .root_uri .as_ref() .and_then(|uri| fs_util::specifier_to_file_path(uri).ok()); @@ -687,7 +686,7 @@ impl Inner { { // sometimes this root uri may not have a trailing slash, so force it to - self.root_uri = params + self.config.root_uri = params .root_uri .map(|s| self.url_map.normalize_url(&s)) .map(fs_util::ensure_directory_specifier); @@ -698,6 +697,12 @@ impl Inner { LspError::internal_error() })?; } + self.config.workspace_folders = params.workspace_folders.map(|folders| { + folders + .into_iter() + .map(|folder| (self.url_map.normalize_url(&folder.uri), folder)) + .collect() + }); self.config.update_capabilities(¶ms.capabilities); } @@ -774,6 +779,7 @@ impl Inner { warn!("Client errored on capabilities.\n{}", err); } } + self.config.update_enabled_paths(self.client.clone()).await; lsp_log!("Server ready."); } @@ -952,6 +958,34 @@ impl Inner { self.performance.measure(mark); } + async fn did_change_workspace_folders( + &mut self, + params: DidChangeWorkspaceFoldersParams, + ) { + let mark = self + .performance + .mark("did_change_workspace_folders", Some(¶ms)); + let mut workspace_folders = params + .event + .added + .into_iter() + .map(|folder| (self.url_map.normalize_url(&folder.uri), folder)) + .collect::<Vec<(ModuleSpecifier, WorkspaceFolder)>>(); + if let Some(current_folders) = &self.config.workspace_folders { + for (specifier, folder) in current_folders { + if !params.event.removed.is_empty() + && params.event.removed.iter().any(|f| f.uri == folder.uri) + { + continue; + } + workspace_folders.push((specifier.clone(), folder.clone())); + } + } + + self.config.workspace_folders = Some(workspace_folders); + self.performance.measure(mark); + } + async fn document_symbol( &mut self, params: DocumentSymbolParams, @@ -1896,6 +1930,7 @@ impl Inner { })?; let maybe_root_path_owned = self + .config .root_uri .as_ref() .and_then(|uri| fs_util::specifier_to_file_path(uri).ok()); @@ -1944,6 +1979,7 @@ impl Inner { })?; let maybe_root_path_owned = self + .config .root_uri .as_ref() .and_then(|uri| fs_util::specifier_to_file_path(uri).ok()); @@ -1999,6 +2035,7 @@ impl Inner { let response = if let Some(one_or_many) = maybe_one_or_many { let maybe_root_path_owned = self + .config .root_uri .as_ref() .and_then(|uri| fs_util::specifier_to_file_path(uri).ok()); @@ -2390,7 +2427,7 @@ impl lspower::LanguageServer for LanguageServer { if document.is_diagnosable() { let specifiers = inner.documents.dependents(&specifier); inner.diagnostics_server.invalidate(&specifiers); - // don't send diagnotics yet if we don't have the specifier settings + // don't send diagnostics yet if we don't have the specifier settings if has_specifier_settings { inner.send_diagnostics_update(); } @@ -2467,7 +2504,8 @@ impl lspower::LanguageServer for LanguageServer { ) }; - // start retreiving all the specifiers' settings outside the lock on its own time + // start retrieving all the specifiers' settings outside the lock on its own + // time if let Some(specifiers) = specifiers { let language_server = self.clone(); let client = client.clone(); @@ -2495,6 +2533,15 @@ impl lspower::LanguageServer for LanguageServer { } } } + let mut ls = language_server.0.lock().await; + if ls.config.update_enabled_paths(client).await { + ls.diagnostics_server.invalidate_all(); + // this will be called in the inner did_change_configuration, but the + // problem then becomes, if there was a change, the snapshot used + // will be an out of date one, so we will call it again here if the + // workspace folders have been touched + ls.send_diagnostics_update(); + } }); } @@ -2533,6 +2580,25 @@ impl lspower::LanguageServer for LanguageServer { self.0.lock().await.did_change_watched_files(params).await } + async fn did_change_workspace_folders( + &self, + params: DidChangeWorkspaceFoldersParams, + ) { + let client = { + let mut inner = self.0.lock().await; + inner.did_change_workspace_folders(params).await; + inner.client.clone() + }; + let language_server = self.clone(); + tokio::spawn(async move { + let mut ls = language_server.0.lock().await; + if ls.config.update_enabled_paths(client).await { + ls.diagnostics_server.invalidate_all(); + ls.send_diagnostics_update(); + } + }); + } + async fn document_symbol( &self, params: DocumentSymbolParams, diff --git a/cli/lsp/repl.rs b/cli/lsp/repl.rs index a208b2266..1026c7bda 100644 --- a/cli/lsp/repl.rs +++ b/cli/lsp/repl.rs @@ -273,6 +273,7 @@ fn get_cwd_uri() -> Result<ModuleSpecifier, AnyError> { pub fn get_repl_workspace_settings() -> WorkspaceSettings { WorkspaceSettings { enable: true, + enable_paths: Vec::new(), config: None, certificate_stores: None, cache: None, diff --git a/cli/lsp/tsc.rs b/cli/lsp/tsc.rs index 4f3e88e7d..eaeef7a51 100644 --- a/cli/lsp/tsc.rs +++ b/cli/lsp/tsc.rs @@ -882,11 +882,6 @@ impl DocumentSpan { language_server: &language_server::Inner, ) -> Option<ModuleSpecifier> { let specifier = normalize_specifier(&self.file_name).ok()?; - log::info!( - "to_target file_name: {} specifier: {}", - self.file_name, - specifier - ); let asset_or_doc = language_server.get_maybe_cached_asset_or_document(&specifier)?; let line_index = asset_or_doc.line_index(); |