summaryrefslogtreecommitdiff
path: root/cli/lsp/config.rs
diff options
context:
space:
mode:
Diffstat (limited to 'cli/lsp/config.rs')
-rw-r--r--cli/lsp/config.rs466
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!({