diff --git a/cli/lsp/config.rs b/cli/lsp/config.rs index 2c8d007035..8b31cc35fc 100644 --- a/cli/lsp/config.rs +++ b/cli/lsp/config.rs @@ -239,6 +239,10 @@ pub struct SpecifierSettings { /// A flag that indicates if Deno is enabled for this specifier or not. pub enable: Option, /// A list of paths, using the workspace folder as a base that should be Deno + /// disabled. + #[serde(default)] + pub disable_paths: Vec, + /// A list of paths, using the workspace folder as a base that should be Deno /// enabled. pub enable_paths: Option>, /// Code lens specific settings for the resource. @@ -285,6 +289,11 @@ pub struct WorkspaceSettings { /// A flag that indicates if Deno is enabled for the workspace. pub enable: Option, + /// A list of paths, using the root_uri as a base that should be Deno + /// disabled. + #[serde(default)] + pub disable_paths: Vec, + /// A list of paths, using the root_uri as a base that should be Deno enabled. pub enable_paths: Option>, @@ -353,6 +362,7 @@ impl Default for WorkspaceSettings { fn default() -> Self { WorkspaceSettings { enable: None, + disable_paths: vec![], enable_paths: None, cache: None, certificate_stores: None, @@ -643,34 +653,96 @@ impl Config { pub fn enabled_urls(&self) -> Vec { let mut urls = vec![]; for (workspace_uri, _) in &self.workspace_folders { + let Ok(workspace_path) = specifier_to_file_path(workspace_uri) else { + lsp_log!("Unable to convert uri \"{}\" to path.", workspace_uri); + continue; + }; let specifier_settings = self.settings.specifiers.get(workspace_uri); let enable = specifier_settings .and_then(|s| s.enable) .or(self.settings.workspace.enable) .unwrap_or(self.has_config_file()); + let disable_paths = specifier_settings + .map(|s| &s.disable_paths) + .unwrap_or(&self.settings.workspace.disable_paths); + let resolved_disable_paths = disable_paths + .iter() + .map(|p| workspace_path.join(p)) + .collect::>(); let enable_paths = specifier_settings .and_then(|s| s.enable_paths.as_ref()) .or(self.settings.workspace.enable_paths.as_ref()); if let Some(enable_paths) = enable_paths { - let Ok(scope_path) = specifier_to_file_path(workspace_uri) else { - lsp_log!("Unable to convert uri \"{}\" to path.", workspace_uri); - return vec![]; - }; for path in enable_paths { - let path = scope_path.join(path); + let path = workspace_path.join(path); let Ok(path_uri) = specifier_from_file_path(&path) else { lsp_log!("Unable to convert path \"{}\" to uri.", path.display()); continue; }; - urls.push(path_uri); + if !resolved_disable_paths.iter().any(|p| path.starts_with(p)) { + urls.push(path_uri); + } } - } else if enable { + } else if enable + && !resolved_disable_paths + .iter() + .any(|p| workspace_path.starts_with(p)) + { urls.push(workspace_uri.clone()); } } // sort for determinism urls.sort(); + urls.dedup(); + urls + } + + pub fn disabled_urls(&self) -> Vec { + let root_enable = self + .settings + .workspace + .enable + .unwrap_or(self.has_config_file()); + let mut urls = vec![]; + if let Some(cf) = self.maybe_config_file() { + if let Some(files) = cf.to_files_config().ok().flatten() { + for path in files.exclude { + let Ok(path_uri) = specifier_from_file_path(&path) else { + lsp_log!("Unable to convert path \"{}\" to uri.", path.display()); + continue; + }; + urls.push(path_uri); + } + } + } + for (workspace_uri, _) in &self.workspace_folders { + let Ok(workspace_path) = specifier_to_file_path(workspace_uri) else { + lsp_log!("Unable to convert uri \"{}\" to path.", workspace_uri); + continue; + }; + let specifier_settings = self.settings.specifiers.get(workspace_uri); + let enable = specifier_settings + .and_then(|s| s.enable) + .unwrap_or(root_enable); + if enable { + let disable_paths = specifier_settings + .map(|s| &s.disable_paths) + .unwrap_or(&self.settings.workspace.disable_paths); + for path in disable_paths { + let path = workspace_path.join(path); + let Ok(path_uri) = specifier_from_file_path(&path) else { + lsp_log!("Unable to convert path \"{}\" to uri.", path.display()); + continue; + }; + urls.push(path_uri); + } + } else { + urls.push(workspace_uri.clone()); + } + } + urls.sort(); + urls.dedup(); urls } @@ -778,9 +850,9 @@ fn specifier_enabled( let root_enable = settings.workspace.enable.unwrap_or(config_file.is_some()); if let Some(settings) = settings.specifiers.get(specifier) { - // TODO(nayeemrmn): We don't know from where to resolve `enable_paths` in - // this case. If it's detected, instead defer to workspace scopes. - if settings.enable_paths.is_none() { + // TODO(nayeemrmn): We don't know from where to resolve path lists in this + // case. If they're detected, instead defer to workspace scopes. + if settings.enable_paths.is_none() && settings.disable_paths.is_empty() { return settings.enable.unwrap_or(root_enable); } } @@ -795,13 +867,22 @@ fn specifier_enabled( }; if path.starts_with(&workspace_path) { let specifier_settings = settings.specifiers.get(workspace_uri); + let disable_paths = specifier_settings + .map(|s| &s.disable_paths) + .unwrap_or(&settings.workspace.disable_paths); + let resolved_disable_paths = disable_paths + .iter() + .map(|p| workspace_path.join(p)) + .collect::>(); let enable_paths = specifier_settings .and_then(|s| s.enable_paths.as_ref()) .or(settings.workspace.enable_paths.as_ref()); if let Some(enable_paths) = enable_paths { for enable_path in enable_paths { let enable_path = workspace_path.join(enable_path); - if path.starts_with(&enable_path) { + if path.starts_with(&enable_path) + && !resolved_disable_paths.iter().any(|p| path.starts_with(p)) + { return true; } } @@ -809,7 +890,8 @@ fn specifier_enabled( } else { return specifier_settings .and_then(|s| s.enable) - .unwrap_or(root_enable); + .unwrap_or(root_enable) + && !resolved_disable_paths.iter().any(|p| path.starts_with(p)); } } } @@ -919,6 +1001,20 @@ mod tests { assert!(!config_snapshot.specifier_enabled(&specifier_b)); } + #[test] + fn test_config_specifier_disabled_path() { + let root_uri = resolve_url("file:///root/").unwrap(); + let mut config = Config::new_with_root(root_uri.clone()); + config.settings.workspace.enable = Some(true); + config.settings.workspace.enable_paths = + Some(vec!["mod1.ts".to_string(), "mod2.ts".to_string()]); + config.settings.workspace.disable_paths = vec!["mod2.ts".to_string()]; + + assert!(config.specifier_enabled(&root_uri.join("mod1.ts").unwrap())); + assert!(!config.specifier_enabled(&root_uri.join("mod2.ts").unwrap())); + assert!(!config.specifier_enabled(&root_uri.join("mod3.ts").unwrap())); + } + #[test] fn test_set_workspace_settings_defaults() { let mut config = Config::new(); @@ -929,6 +1025,7 @@ mod tests { config.workspace_settings().clone(), WorkspaceSettings { enable: None, + disable_paths: vec![], enable_paths: None, cache: None, certificate_stores: None, @@ -1135,13 +1232,18 @@ mod tests { let mut config = Config::new_with_root(root_uri.clone()); config.settings.workspace.enable = Some(true); - config.settings.workspace.enable_paths = Some(vec!["mod1.ts".to_string()]); + config.settings.workspace.enable_paths = + Some(vec!["mod1.ts".to_string(), "mod2.ts".to_string()]); + config.settings.workspace.disable_paths = vec!["mod2.ts".to_string()]; assert!( config.specifier_enabled_for_test(&root_uri.join("mod1.ts").unwrap()) ); assert!( !config.specifier_enabled_for_test(&root_uri.join("mod2.ts").unwrap()) ); + assert!( + !config.specifier_enabled_for_test(&root_uri.join("mod3.ts").unwrap()) + ); config.settings.workspace.enable_paths = None; config.set_config_file( diff --git a/cli/lsp/diagnostics.rs b/cli/lsp/diagnostics.rs index 361dba1966..a04f490f70 100644 --- a/cli/lsp/diagnostics.rs +++ b/cli/lsp/diagnostics.rs @@ -1477,6 +1477,7 @@ let c: number = "a"; specifier.clone(), SpecifierSettings { enable: Some(false), + disable_paths: vec![], enable_paths: None, code_lens: Default::default(), }, diff --git a/cli/lsp/documents.rs b/cli/lsp/documents.rs index 388f9f2e56..e601147ffb 100644 --- a/cli/lsp/documents.rs +++ b/cli/lsp/documents.rs @@ -804,6 +804,7 @@ impl FileSystemDocuments { pub struct UpdateDocumentConfigOptions<'a> { pub enabled_urls: Vec, + pub disabled_urls: Vec, pub document_preload_limit: usize, pub maybe_import_map: Option>, pub maybe_config_file: Option<&'a ConfigFile>, @@ -1283,14 +1284,10 @@ impl Documents { .filter_map(|url| specifier_to_file_path(url).ok()) .collect(), options - .maybe_config_file - .and_then(|cf| { - cf.to_files_config() - .ok() - .flatten() - .map(|files| files.exclude) - }) - .unwrap_or_default(), + .disabled_urls + .iter() + .filter_map(|url| specifier_to_file_path(url).ok()) + .collect(), options.document_preload_limit, ); self.resolver_config_hash = new_resolver_config_hash; @@ -2032,6 +2029,7 @@ console.log(b, "hello deno"); documents.update_config(UpdateDocumentConfigOptions { enabled_urls: vec![], + disabled_urls: vec![], document_preload_limit: 1_000, maybe_import_map: Some(Arc::new(import_map)), maybe_config_file: None, @@ -2073,6 +2071,7 @@ console.log(b, "hello deno"); documents.update_config(UpdateDocumentConfigOptions { enabled_urls: vec![], + disabled_urls: vec![], document_preload_limit: 1_000, maybe_import_map: Some(Arc::new(import_map)), maybe_config_file: None, diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs index 26d03b76d7..385de0ba1a 100644 --- a/cli/lsp/language_server.rs +++ b/cli/lsp/language_server.rs @@ -1354,6 +1354,7 @@ impl Inner { async fn refresh_documents_config(&mut self) { self.documents.update_config(UpdateDocumentConfigOptions { enabled_urls: self.config.enabled_urls(), + disabled_urls: self.config.disabled_urls(), document_preload_limit: self .config .workspace_settings() diff --git a/cli/lsp/repl.rs b/cli/lsp/repl.rs index c21e2e3fba..c09a142d68 100644 --- a/cli/lsp/repl.rs +++ b/cli/lsp/repl.rs @@ -285,6 +285,7 @@ fn get_cwd_uri() -> Result { pub fn get_repl_workspace_settings() -> WorkspaceSettings { WorkspaceSettings { enable: Some(true), + disable_paths: vec![], enable_paths: None, config: None, certificate_stores: None, diff --git a/cli/tests/integration/lsp_tests.rs b/cli/tests/integration/lsp_tests.rs index 64c59ce7d9..6363a08f63 100644 --- a/cli/tests/integration/lsp_tests.rs +++ b/cli/tests/integration/lsp_tests.rs @@ -1298,13 +1298,14 @@ fn lsp_inlay_hints_not_enabled() { } #[test] -fn lsp_workspace_enable_paths() { +fn lsp_workspace_disable_enable_paths() { fn run_test(use_trailing_slash: bool) { let context = TestContextBuilder::new().use_temp_cwd().build(); let temp_dir = context.temp_dir(); temp_dir.create_dir_all("worker"); temp_dir.write("worker/shared.ts", "export const a = 1"); temp_dir.write("worker/other.ts", "import { a } from './shared.ts';\na;"); + temp_dir.write("worker/node.ts", "Buffer.alloc(1);"); let root_specifier = temp_dir.uri(); @@ -1312,6 +1313,7 @@ fn lsp_workspace_enable_paths() { client.initialize_with_config( |builder| { builder + .set_disable_paths(vec!["./worker/node.ts".to_string()]) .set_enable_paths(vec!["./worker".to_string()]) .set_root_uri(root_specifier.clone()) .set_workspace_folders(vec![lsp::WorkspaceFolder { @@ -1329,6 +1331,7 @@ fn lsp_workspace_enable_paths() { }, json!([{ "enable": false, + "disablePaths": ["./worker/node.ts"], "enablePaths": ["./worker"], }]), ); @@ -1395,6 +1398,17 @@ fn lsp_workspace_enable_paths() { ); assert_eq!(res, json!(null)); + let res = client.write_request( + "textDocument/hover", + json!({ + "textDocument": { + "uri": root_specifier.join("./worker/node.ts").unwrap(), + }, + "position": { "line": 0, "character": 0 } + }), + ); + assert_eq!(res, json!(null)); + let res = client.write_request( "textDocument/hover", json!({ diff --git a/test_util/src/lsp.rs b/test_util/src/lsp.rs index d276e3ed36..2af27e8d44 100644 --- a/test_util/src/lsp.rs +++ b/test_util/src/lsp.rs @@ -388,6 +388,12 @@ impl InitializeParamsBuilder { self } + pub fn set_disable_paths(&mut self, value: Vec) -> &mut Self { + let options = self.initialization_options_mut(); + options.insert("disablePaths".to_string(), value.into()); + self + } + pub fn set_enable_paths(&mut self, value: Vec) -> &mut Self { let options = self.initialization_options_mut(); options.insert("enablePaths".to_string(), value.into());