diff options
Diffstat (limited to 'cli/lsp/config.rs')
-rw-r--r-- | cli/lsp/config.rs | 466 |
1 files changed, 232 insertions, 234 deletions
diff --git a/cli/lsp/config.rs b/cli/lsp/config.rs index 6a005e83b..ebcedb024 100644 --- a/cli/lsp/config.rs +++ b/cli/lsp/config.rs @@ -73,21 +73,6 @@ impl Default for CodeLensSettings { } } -#[derive(Debug, Clone, Deserialize, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct CodeLensSpecifierSettings { - /// Flag for providing test code lens on `Deno.test` statements. There is - /// also the `test_args` setting, but this is not used by the server. - #[serde(default = "is_true")] - pub test: bool, -} - -impl Default for CodeLensSpecifierSettings { - fn default() -> Self { - Self { test: true } - } -} - #[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct DenoCompletionSettings { @@ -277,25 +262,6 @@ impl Default for ImportCompletionSettings { } } -/// Deno language server specific settings that can be applied uniquely to a -/// specifier. -#[derive(Debug, Default, Clone, Deserialize, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct SpecifierSettings { - /// A flag that indicates if Deno is enabled for this specifier or not. - pub enable: Option<bool>, - /// A list of paths, using the workspace folder as a base that should be Deno - /// disabled. - #[serde(default)] - pub disable_paths: Vec<String>, - /// A list of paths, using the workspace folder as a base that should be Deno - /// enabled. - pub enable_paths: Option<Vec<String>>, - /// Code lens specific settings for the resource. - #[serde(default)] - pub code_lens: CodeLensSpecifierSettings, -} - #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct TestingSettings { @@ -712,56 +678,6 @@ impl WorkspaceSettings { .unwrap_or_default(); Self::from_raw_settings(deno, javascript, typescript) } - - /// Determine if any code lenses are enabled at all. This allows short - /// circuiting when there are no code lenses enabled. - pub fn enabled_code_lens(&self) -> bool { - self.code_lens.implementations || self.code_lens.references - } - - // TODO(nayeemrmn): Factor in out-of-band media type here. - pub fn language_settings_for_specifier( - &self, - specifier: &ModuleSpecifier, - ) -> Option<&LanguageWorkspaceSettings> { - if specifier.scheme() == "deno-notebook-cell" { - return Some(&self.typescript); - } - match MediaType::from_specifier(specifier) { - MediaType::JavaScript - | MediaType::Jsx - | MediaType::Mjs - | MediaType::Cjs => Some(&self.javascript), - MediaType::TypeScript - | MediaType::Mts - | MediaType::Cts - | MediaType::Dts - | MediaType::Dmts - | MediaType::Dcts - | MediaType::Tsx => Some(&self.typescript), - MediaType::Json - | MediaType::Wasm - | MediaType::TsBuildInfo - | MediaType::SourceMap - | MediaType::Unknown => None, - } - } - - /// Determine if any inlay hints are enabled. This allows short circuiting - /// when there are no inlay hints enabled. - pub fn enabled_inlay_hints(&self, specifier: &ModuleSpecifier) -> bool { - let Some(settings) = self.language_settings_for_specifier(specifier) else { - return false; - }; - !matches!( - settings.inlay_hints.parameter_names.enabled, - InlayHintsParamNamesEnabled::None - ) || settings.inlay_hints.parameter_types.enabled - || settings.inlay_hints.variable_types.enabled - || settings.inlay_hints.property_declaration_types.enabled - || settings.inlay_hints.function_like_return_types.enabled - || settings.inlay_hints.enum_member_values.enabled - } } #[derive(Debug, Clone, Default)] @@ -773,6 +689,13 @@ pub struct ConfigSnapshot { } impl ConfigSnapshot { + pub fn workspace_settings_for_specifier( + &self, + specifier: &ModuleSpecifier, + ) -> &WorkspaceSettings { + self.settings.get_for_specifier(specifier).0 + } + /// Determine if the provided specifier is enabled or not. pub fn specifier_enabled(&self, specifier: &ModuleSpecifier) -> bool { specifier_enabled( @@ -803,8 +726,59 @@ impl ConfigSnapshot { #[derive(Debug, Default, Clone)] pub struct Settings { - pub specifiers: BTreeMap<ModuleSpecifier, SpecifierSettings>, - pub workspace: WorkspaceSettings, + pub unscoped: WorkspaceSettings, + pub by_workspace_folder: Option<BTreeMap<ModuleSpecifier, WorkspaceSettings>>, +} + +impl Settings { + pub fn get_unscoped(&self) -> &WorkspaceSettings { + &self.unscoped + } + + pub fn get_for_specifier( + &self, + specifier: &ModuleSpecifier, + ) -> (&WorkspaceSettings, Option<&ModuleSpecifier>) { + let Ok(path) = specifier_to_file_path(specifier) else { + return (&self.unscoped, None); + }; + if let Some(by_workspace_folder) = &self.by_workspace_folder { + for (folder_uri, settings) in by_workspace_folder.iter().rev() { + let Ok(folder_path) = specifier_to_file_path(folder_uri) else { + continue; + }; + if path.starts_with(folder_path) { + return (settings, Some(folder_uri)); + } + } + } + (&self.unscoped, None) + } + + pub fn set_unscoped(&mut self, mut settings: WorkspaceSettings) { + // See https://github.com/denoland/vscode_deno/issues/908. + if settings.enable_paths == Some(vec![]) { + settings.enable_paths = None; + } + self.unscoped = settings; + } + + pub fn set_for_workspace_folders( + &mut self, + mut by_workspace_folder: Option< + BTreeMap<ModuleSpecifier, WorkspaceSettings>, + >, + ) { + if let Some(by_workspace_folder) = &mut by_workspace_folder { + for settings in by_workspace_folder.values_mut() { + // See https://github.com/denoland/vscode_deno/issues/908. + if settings.enable_paths == Some(vec![]) { + settings.enable_paths = None; + } + } + } + self.by_workspace_folder = by_workspace_folder; + } } #[derive(Debug)] @@ -860,6 +834,93 @@ impl Config { config } + pub fn set_workspace_settings( + &mut self, + unscoped: WorkspaceSettings, + by_workspace_folder: Option<BTreeMap<ModuleSpecifier, WorkspaceSettings>>, + ) { + self.settings.set_unscoped(unscoped); + self.settings.set_for_workspace_folders(by_workspace_folder); + } + + pub fn workspace_settings(&self) -> &WorkspaceSettings { + self.settings.get_unscoped() + } + + pub fn workspace_settings_for_specifier( + &self, + specifier: &ModuleSpecifier, + ) -> &WorkspaceSettings { + self.settings.get_for_specifier(specifier).0 + } + + pub fn language_settings_for_specifier( + &self, + specifier: &ModuleSpecifier, + ) -> Option<&LanguageWorkspaceSettings> { + let workspace_settings = self.workspace_settings_for_specifier(specifier); + if specifier.scheme() == "deno-notebook-cell" { + return Some(&workspace_settings.typescript); + } + match MediaType::from_specifier(specifier) { + MediaType::JavaScript + | MediaType::Jsx + | MediaType::Mjs + | MediaType::Cjs => Some(&workspace_settings.javascript), + MediaType::TypeScript + | MediaType::Mts + | MediaType::Cts + | MediaType::Dts + | MediaType::Dmts + | MediaType::Dcts + | MediaType::Tsx => Some(&workspace_settings.typescript), + MediaType::Json + | MediaType::Wasm + | MediaType::TsBuildInfo + | MediaType::SourceMap + | MediaType::Unknown => None, + } + } + + /// Determine if any inlay hints are enabled. This allows short circuiting + /// when there are no inlay hints enabled. + pub fn enabled_inlay_hints_for_specifier( + &self, + specifier: &ModuleSpecifier, + ) -> bool { + let Some(settings) = self.language_settings_for_specifier(specifier) else { + return false; + }; + !matches!( + settings.inlay_hints.parameter_names.enabled, + InlayHintsParamNamesEnabled::None + ) || settings.inlay_hints.parameter_types.enabled + || settings.inlay_hints.variable_types.enabled + || settings.inlay_hints.property_declaration_types.enabled + || settings.inlay_hints.function_like_return_types.enabled + || settings.inlay_hints.enum_member_values.enabled + } + + /// Determine if any code lenses are enabled at all. This allows short + /// circuiting when there are no code lenses enabled. + pub fn enabled_code_lens_for_specifier( + &self, + specifier: &ModuleSpecifier, + ) -> bool { + let settings = self.workspace_settings_for_specifier(specifier); + settings.code_lens.implementations + || settings.code_lens.references + || settings.code_lens.test + } + + pub fn enabled_code_lens_test_for_specifier( + &self, + specifier: &ModuleSpecifier, + ) -> bool { + let settings = self.workspace_settings_for_specifier(specifier); + settings.code_lens.test + } + pub fn root_uri(&self) -> Option<&Url> { self.workspace_folders.get(0).map(|p| &p.0) } @@ -949,20 +1010,6 @@ impl Config { }); } - pub fn workspace_settings(&self) -> &WorkspaceSettings { - &self.settings.workspace - } - - /// Set the workspace settings directly, which occurs during initialization - /// and when the client does not support workspace configuration requests - pub fn set_workspace_settings(&mut self, settings: WorkspaceSettings) { - self.settings.workspace = settings; - // See https://github.com/denoland/vscode_deno/issues/908. - if self.settings.workspace.enable_paths == Some(vec![]) { - self.settings.workspace.enable_paths = None; - } - } - pub fn snapshot(&self) -> Arc<ConfigSnapshot> { Arc::new(ConfigSnapshot { client_capabilities: self.client_capabilities.clone(), @@ -972,10 +1019,6 @@ impl Config { }) } - pub fn has_specifier_settings(&self, specifier: &ModuleSpecifier) -> bool { - self.settings.specifiers.contains_key(specifier) - } - pub fn specifier_enabled(&self, specifier: &ModuleSpecifier) -> bool { specifier_enabled( specifier, @@ -1009,11 +1052,8 @@ impl Config { lsp_log!("Unable to convert uri \"{}\" to path.", workspace_uri); continue; }; - let specifier_settings = self.settings.specifiers.get(workspace_uri); - 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 settings = self.workspace_settings_for_specifier(workspace_uri); + if let Some(enable_paths) = &settings.enable_paths { for path in enable_paths { paths.push(workspace_path.join(path)); } @@ -1035,25 +1075,14 @@ impl Config { } } } - let root_enable = self - .settings - .workspace - .enable - .unwrap_or(self.has_config_file()); 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 settings = self.workspace_settings_for_specifier(workspace_uri); + if settings.enable.unwrap_or_else(|| self.has_config_file()) { + for path in &settings.disable_paths { paths.push(workspace_path.join(path)); } } else { @@ -1065,16 +1094,6 @@ impl Config { paths } - pub fn specifier_code_lens_test(&self, specifier: &ModuleSpecifier) -> bool { - let value = self - .settings - .specifiers - .get(specifier) - .map(|settings| settings.code_lens.test) - .unwrap_or_else(|| self.settings.workspace.code_lens.test); - value - } - pub fn update_capabilities( &mut self, capabilities: &lsp::ClientCapabilities, @@ -1127,37 +1146,13 @@ impl Config { }; } } - - pub fn get_specifiers(&self) -> Vec<ModuleSpecifier> { - self.settings.specifiers.keys().cloned().collect() - } - - pub fn set_specifier_settings( - &mut self, - specifier: ModuleSpecifier, - mut settings: SpecifierSettings, - ) -> bool { - // See https://github.com/denoland/vscode_deno/issues/908. - if settings.enable_paths == Some(vec![]) { - settings.enable_paths = None; - } - - if let Some(existing) = self.settings.specifiers.get(&specifier) { - if *existing == settings { - return false; - } - } - - self.settings.specifiers.insert(specifier, settings); - true - } } fn specifier_enabled( specifier: &Url, config_file: Option<&ConfigFile>, settings: &Settings, - workspace_folders: &Vec<(Url, lsp::WorkspaceFolder)>, + workspace_folders: &[(Url, lsp::WorkspaceFolder)], ) -> bool { if let Some(cf) = config_file { if let Some(files) = cf.to_files_config().ok().flatten() { @@ -1166,55 +1161,42 @@ 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 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); - } - } let Ok(path) = specifier_to_file_path(specifier) else { // Non-file URLs are not disabled by these settings. return true; }; - for (workspace_uri, _) in workspace_folders { - let Ok(workspace_path) = specifier_to_file_path(workspace_uri) else { - lsp_log!("Unable to convert uri \"{}\" to path.", workspace_uri); - continue; - }; - 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 + let (settings, mut folder_uri) = settings.get_for_specifier(specifier); + folder_uri = folder_uri.or_else(|| workspace_folders.get(0).map(|f| &f.0)); + let mut disable_paths = vec![]; + let mut enable_paths = None; + if let Some(folder_uri) = folder_uri { + if let Ok(folder_path) = specifier_to_file_path(folder_uri) { + disable_paths = settings + .disable_paths .iter() - .map(|p| workspace_path.join(p)) + .map(|p| folder_path.join(p)) .collect::<Vec<_>>(); - 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) - && !resolved_disable_paths.iter().any(|p| path.starts_with(p)) - { - return true; - } - } - return false; - } else { - return specifier_settings - .and_then(|s| s.enable) - .unwrap_or(root_enable) - && !resolved_disable_paths.iter().any(|p| path.starts_with(p)); + enable_paths = settings.enable_paths.as_ref().map(|enable_paths| { + enable_paths + .iter() + .map(|p| folder_path.join(p)) + .collect::<Vec<_>>() + }); + } + } + if let Some(enable_paths) = &enable_paths { + for enable_path in enable_paths { + if path.starts_with(enable_path) + && !disable_paths.iter().any(|p| path.starts_with(p)) + { + return true; } } + false + } else { + settings.enable.unwrap_or_else(|| config_file.is_some()) + && !disable_paths.iter().any(|p| path.starts_with(p)) } - root_enable } fn resolve_lockfile_from_config(config_file: &ConfigFile) -> Option<Lockfile> { @@ -1285,6 +1267,7 @@ mod tests { "enable": true })) .unwrap(), + None, ); assert!(config.specifier_enabled(&specifier)); } @@ -1300,6 +1283,7 @@ mod tests { "enable": true })) .unwrap(), + None, ); let config_snapshot = config.snapshot(); assert!(config_snapshot.specifier_enabled(&specifier)); @@ -1315,7 +1299,7 @@ mod tests { assert!(!config.specifier_enabled(&specifier_b)); let workspace_settings = serde_json::from_str(r#"{ "enablePaths": ["worker"] }"#).unwrap(); - config.set_workspace_settings(workspace_settings); + config.set_workspace_settings(workspace_settings, None); assert!(config.specifier_enabled(&specifier_a)); assert!(!config.specifier_enabled(&specifier_b)); let config_snapshot = config.snapshot(); @@ -1327,10 +1311,10 @@ mod tests { 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 = + config.settings.unscoped.enable = Some(true); + config.settings.unscoped.enable_paths = Some(vec!["mod1.ts".to_string(), "mod2.ts".to_string()]); - config.settings.workspace.disable_paths = vec!["mod2.ts".to_string()]; + config.settings.unscoped.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())); @@ -1340,7 +1324,8 @@ mod tests { #[test] fn test_set_workspace_settings_defaults() { let mut config = Config::new(); - config.set_workspace_settings(serde_json::from_value(json!({})).unwrap()); + config + .set_workspace_settings(serde_json::from_value(json!({})).unwrap(), None); assert_eq!( config.workspace_settings().clone(), WorkspaceSettings { @@ -1472,6 +1457,7 @@ mod tests { let mut config = Config::new(); config.set_workspace_settings( serde_json::from_value(json!({ "cache": "" })).unwrap(), + None, ); assert_eq!( config.workspace_settings().clone(), @@ -1484,6 +1470,7 @@ mod tests { let mut config = Config::new(); config.set_workspace_settings( serde_json::from_value(json!({ "import_map": "" })).unwrap(), + None, ); assert_eq!( config.workspace_settings().clone(), @@ -1496,6 +1483,7 @@ mod tests { let mut config = Config::new(); config.set_workspace_settings( serde_json::from_value(json!({ "tls_certificate": "" })).unwrap(), + None, ); assert_eq!( config.workspace_settings().clone(), @@ -1508,6 +1496,7 @@ mod tests { let mut config = Config::new(); config.set_workspace_settings( serde_json::from_value(json!({ "config": "" })).unwrap(), + None, ); assert_eq!( config.workspace_settings().clone(), @@ -1541,30 +1530,39 @@ mod tests { }, ), ]; - config.set_specifier_settings( - Url::parse("file:///root1/").unwrap(), - SpecifierSettings { - enable_paths: Some(vec![ - "sub_dir".to_string(), - "sub_dir/other".to_string(), - "test.ts".to_string(), - ]), - ..Default::default() - }, - ); - config.set_specifier_settings( - Url::parse("file:///root2/").unwrap(), - SpecifierSettings { - enable_paths: Some(vec!["other.ts".to_string()]), - ..Default::default() - }, - ); - config.set_specifier_settings( - Url::parse("file:///root3/").unwrap(), - SpecifierSettings { - enable: Some(true), - ..Default::default() - }, + config.set_workspace_settings( + Default::default(), + Some( + vec![ + ( + Url::parse("file:///root1/").unwrap(), + WorkspaceSettings { + enable_paths: Some(vec![ + "sub_dir".to_string(), + "sub_dir/other".to_string(), + "test.ts".to_string(), + ]), + ..Default::default() + }, + ), + ( + Url::parse("file:///root2/").unwrap(), + WorkspaceSettings { + enable_paths: Some(vec!["other.ts".to_string()]), + ..Default::default() + }, + ), + ( + Url::parse("file:///root3/").unwrap(), + WorkspaceSettings { + enable: Some(true), + ..Default::default() + }, + ), + ] + .into_iter() + .collect(), + ), ); assert_eq!( @@ -1583,7 +1581,7 @@ mod tests { fn config_enable_via_config_file_detection() { let root_uri = resolve_url("file:///root/").unwrap(); let mut config = Config::new_with_root(root_uri.clone()); - config.settings.workspace.enable = None; + config.settings.unscoped.enable = None; assert!(!config.specifier_enabled(&root_uri)); config.set_config_file( @@ -1597,7 +1595,7 @@ mod tests { fn config_specifier_enabled_matches_by_path_component() { let root_uri = resolve_url("file:///root/").unwrap(); let mut config = Config::new_with_root(root_uri.clone()); - config.settings.workspace.enable_paths = Some(vec!["mo".to_string()]); + config.settings.unscoped.enable_paths = Some(vec!["mo".to_string()]); assert!(!config.specifier_enabled(&root_uri.join("mod.ts").unwrap())); } @@ -1605,11 +1603,11 @@ mod tests { fn config_specifier_enabled_for_test() { 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.unscoped.enable = Some(true); - config.settings.workspace.enable_paths = + config.settings.unscoped.enable_paths = Some(vec!["mod1.ts".to_string(), "mod2.ts".to_string()]); - config.settings.workspace.disable_paths = vec!["mod2.ts".to_string()]; + config.settings.unscoped.disable_paths = vec!["mod2.ts".to_string()]; assert!( config.specifier_enabled_for_test(&root_uri.join("mod1.ts").unwrap()) ); @@ -1619,7 +1617,7 @@ mod tests { assert!( !config.specifier_enabled_for_test(&root_uri.join("mod3.ts").unwrap()) ); - config.settings.workspace.enable_paths = None; + config.settings.unscoped.enable_paths = None; config.set_config_file( ConfigFile::new( @@ -1688,7 +1686,7 @@ mod tests { fn config_snapshot_specifier_enabled_for_test() { 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.unscoped.enable = Some(true); config.set_config_file( ConfigFile::new( &json!({ |