1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-01-03 04:48:52 -05:00

refactor(lsp): clean up "enablePaths" handling (#20388)

Previously we pre-computed enabled paths into `Config::enabled_paths`,
and had to keep updating it. Now we determine enabled paths directly
from `Config::settings` on demand as a single source of truth.

Removes `Config::root_uri`. If `InitializeParams::rootUri` is given, and
it doesn't correspond to a folder in
`InitializeParams::workspaceFolders`, prepend it to
`Config::workspace_folders` as a mocked folder.

Includes groundwork for
https://github.com/denoland/vscode_deno/issues/908. In a minor version
cycle or two we can fix that in vscode_deno, and it won't break for Deno
versions post this patch due to the corrected deserialization logic for
`enablePaths`.
This commit is contained in:
Nayeem Rahman 2023-09-08 00:50:34 +01:00 committed by GitHub
parent 9d6584c16f
commit 4a11603c76
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 264 additions and 245 deletions

View file

@ -239,8 +239,7 @@ pub struct SpecifierSettings {
pub enable: Option<bool>, pub enable: Option<bool>,
/// A list of paths, using the workspace folder as a base that should be Deno /// A list of paths, using the workspace folder as a base that should be Deno
/// enabled. /// enabled.
#[serde(default)] pub enable_paths: Option<Vec<String>>,
pub enable_paths: Vec<String>,
/// Code lens specific settings for the resource. /// Code lens specific settings for the resource.
#[serde(default)] #[serde(default)]
pub code_lens: CodeLensSpecifierSettings, pub code_lens: CodeLensSpecifierSettings,
@ -291,8 +290,7 @@ pub struct WorkspaceSettings {
pub enable: Option<bool>, pub enable: Option<bool>,
/// A list of paths, using the root_uri as a base that should be Deno enabled. /// A list of paths, using the root_uri as a base that should be Deno enabled.
#[serde(default)] pub enable_paths: Option<Vec<String>>,
pub enable_paths: Vec<String>,
/// An option that points to a path string of the path to utilise as the /// An option that points to a path string of the path to utilise as the
/// cache/DENO_DIR for the language server. /// cache/DENO_DIR for the language server.
@ -359,7 +357,7 @@ impl Default for WorkspaceSettings {
fn default() -> Self { fn default() -> Self {
WorkspaceSettings { WorkspaceSettings {
enable: None, enable: None,
enable_paths: vec![], enable_paths: None,
cache: None, cache: None,
certificate_stores: None, certificate_stores: None,
config: None, config: None,
@ -402,21 +400,21 @@ impl WorkspaceSettings {
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct ConfigSnapshot { pub struct ConfigSnapshot {
pub client_capabilities: ClientCapabilities, pub client_capabilities: ClientCapabilities,
pub enabled_paths: HashMap<Url, Vec<Url>>,
pub excluded_paths: Option<Vec<Url>>, pub excluded_paths: Option<Vec<Url>>,
pub has_config_file: bool, pub has_config_file: bool,
pub settings: Settings, pub settings: Settings,
pub workspace_folders: Vec<(ModuleSpecifier, lsp::WorkspaceFolder)>,
} }
impl ConfigSnapshot { impl ConfigSnapshot {
/// Determine if the provided specifier is enabled or not. /// Determine if the provided specifier is enabled or not.
pub fn specifier_enabled(&self, specifier: &ModuleSpecifier) -> bool { pub fn specifier_enabled(&self, specifier: &ModuleSpecifier) -> bool {
specifier_enabled( specifier_enabled(
&self.enabled_paths,
self.excluded_paths.as_ref(),
&self.settings,
self.has_config_file,
specifier, specifier,
&self.settings,
self.excluded_paths.as_ref(),
&self.workspace_folders,
self.has_config_file,
) )
} }
} }
@ -448,10 +446,8 @@ struct LspConfigFileInfo {
#[derive(Debug)] #[derive(Debug)]
pub struct Config { pub struct Config {
pub client_capabilities: ClientCapabilities, pub client_capabilities: ClientCapabilities,
enabled_paths: HashMap<Url, Vec<Url>>,
pub root_uri: Option<ModuleSpecifier>,
settings: Settings, settings: Settings,
pub workspace_folders: Option<Vec<(ModuleSpecifier, lsp::WorkspaceFolder)>>, pub workspace_folders: Vec<(ModuleSpecifier, lsp::WorkspaceFolder)>,
/// An optional configuration file which has been specified in the client /// An optional configuration file which has been specified in the client
/// options along with some data that is computed after the config file is set. /// options along with some data that is computed after the config file is set.
maybe_config_file_info: Option<LspConfigFileInfo>, maybe_config_file_info: Option<LspConfigFileInfo>,
@ -461,15 +457,32 @@ impl Config {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
client_capabilities: ClientCapabilities::default(), client_capabilities: ClientCapabilities::default(),
enabled_paths: Default::default(),
/// Root provided by the initialization parameters. /// Root provided by the initialization parameters.
root_uri: None,
settings: Default::default(), settings: Default::default(),
workspace_folders: None, workspace_folders: vec![],
maybe_config_file_info: None, maybe_config_file_info: None,
} }
} }
#[cfg(test)]
pub fn new_with_root(root_uri: Url) -> Self {
let mut config = Self::new();
let name = root_uri.path_segments().and_then(|s| s.last());
let name = name.unwrap_or_default().to_string();
config.workspace_folders = vec![(
root_uri.clone(),
lsp::WorkspaceFolder {
uri: root_uri,
name,
},
)];
config
}
pub fn root_uri(&self) -> Option<&Url> {
self.workspace_folders.get(0).map(|p| &p.0)
}
pub fn maybe_node_modules_dir_path(&self) -> Option<&PathBuf> { pub fn maybe_node_modules_dir_path(&self) -> Option<&PathBuf> {
self self
.maybe_config_file_info .maybe_config_file_info
@ -574,21 +587,24 @@ impl Config {
&mut self, &mut self,
value: Value, value: Value,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
let workspace_settings = serde_json::from_value(value)?; self.settings.workspace = serde_json::from_value(value)?;
self.settings.workspace = 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;
}
Ok(()) Ok(())
} }
pub fn snapshot(&self) -> Arc<ConfigSnapshot> { pub fn snapshot(&self) -> Arc<ConfigSnapshot> {
Arc::new(ConfigSnapshot { Arc::new(ConfigSnapshot {
client_capabilities: self.client_capabilities.clone(), client_capabilities: self.client_capabilities.clone(),
enabled_paths: self.enabled_paths.clone(),
excluded_paths: self excluded_paths: self
.maybe_config_file_info .maybe_config_file_info
.as_ref() .as_ref()
.map(|i| i.excluded_paths.clone()), .map(|i| i.excluded_paths.clone()),
has_config_file: self.has_config_file(), has_config_file: self.has_config_file(),
settings: self.settings.clone(), settings: self.settings.clone(),
workspace_folders: self.workspace_folders.clone(),
}) })
} }
@ -596,24 +612,16 @@ impl Config {
self.settings.specifiers.contains_key(specifier) self.settings.specifiers.contains_key(specifier)
} }
pub fn enabled(&self) -> bool {
self
.settings
.workspace
.enable
.unwrap_or_else(|| self.has_config_file())
}
pub fn specifier_enabled(&self, specifier: &ModuleSpecifier) -> bool { pub fn specifier_enabled(&self, specifier: &ModuleSpecifier) -> bool {
specifier_enabled( specifier_enabled(
&self.enabled_paths, specifier,
&self.settings,
self self
.maybe_config_file_info .maybe_config_file_info
.as_ref() .as_ref()
.map(|i| &i.excluded_paths), .map(|i| &i.excluded_paths),
&self.settings, &self.workspace_folders,
self.has_config_file(), self.has_config_file(),
specifier,
) )
} }
@ -623,24 +631,31 @@ impl Config {
/// WARNING: This may incorrectly have some directory urls as being /// WARNING: This may incorrectly have some directory urls as being
/// represented as file urls. /// represented as file urls.
pub fn enabled_urls(&self) -> Vec<Url> { pub fn enabled_urls(&self) -> Vec<Url> {
let mut urls: Vec<Url> = Vec::new(); let mut urls = vec![];
for (workspace_uri, _) in &self.workspace_folders {
if !self.enabled() && self.enabled_paths.is_empty() { let specifier_settings = self.settings.specifiers.get(workspace_uri);
// do not return any urls when disabled let enable = specifier_settings
return urls; .and_then(|s| s.enable)
} .or(self.settings.workspace.enable)
.unwrap_or(self.has_config_file());
for (workspace, enabled_paths) in &self.enabled_paths { let enable_paths = specifier_settings
if !enabled_paths.is_empty() { .and_then(|s| s.enable_paths.as_ref())
urls.extend(enabled_paths.iter().cloned()); .or(self.settings.workspace.enable_paths.as_ref());
} else { if let Some(enable_paths) = enable_paths {
urls.push(workspace.clone()); let Ok(scope_path) = specifier_to_file_path(workspace_uri) else {
} lsp_log!("Unable to convert uri \"{}\" to path.", workspace_uri);
} return vec![];
};
if urls.is_empty() { for path in enable_paths {
if let Some(root_dir) = &self.root_uri { let path = scope_path.join(path);
urls.push(root_dir.clone()) let Ok(path_uri) = Url::from_file_path(&path) else {
lsp_log!("Unable to convert path \"{}\" to uri.", path.display());
continue;
};
urls.push(path_uri);
}
} else if enable {
urls.push(workspace_uri.clone());
} }
} }
@ -712,64 +727,6 @@ impl Config {
} }
} }
/// Given the configured workspaces or root URI and the their settings,
/// update and resolve any paths that should be enabled
pub fn update_enabled_paths(&mut self) -> bool {
if let Some(workspace_folders) = self.workspace_folders.clone() {
let mut touched = false;
for (workspace, _) in workspace_folders {
let enabled_paths = match self.settings.specifiers.get(&workspace) {
Some(settings) => settings.enable_paths.clone(),
None => self.settings.workspace.enable_paths.clone(),
};
if self.update_enabled_paths_entry(workspace, enabled_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 mut touched = false;
if !enabled_paths.is_empty() {
if let Ok(workspace_path) = 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);
}
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(workspace.clone(), paths);
}
}
} else {
touched = true;
self.enabled_paths.remove(&workspace);
}
touched
}
pub fn get_specifiers(&self) -> Vec<ModuleSpecifier> { pub fn get_specifiers(&self) -> Vec<ModuleSpecifier> {
self.settings.specifiers.keys().cloned().collect() self.settings.specifiers.keys().cloned().collect()
} }
@ -777,8 +734,13 @@ impl Config {
pub fn set_specifier_settings( pub fn set_specifier_settings(
&mut self, &mut self,
specifier: ModuleSpecifier, specifier: ModuleSpecifier,
settings: SpecifierSettings, mut settings: SpecifierSettings,
) -> bool { ) -> 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 let Some(existing) = self.settings.specifiers.get(&specifier) {
if *existing == settings { if *existing == settings {
return false; return false;
@ -791,33 +753,63 @@ impl Config {
} }
fn specifier_enabled( fn specifier_enabled(
enabled_paths: &HashMap<Url, Vec<Url>>,
excluded_paths: Option<&Vec<Url>>,
settings: &Settings,
workspace_has_config_file: bool,
specifier: &Url, specifier: &Url,
settings: &Settings,
excluded_urls: Option<&Vec<Url>>,
workspace_folders: &Vec<(Url, lsp::WorkspaceFolder)>,
workspace_has_config_file: bool,
) -> bool { ) -> bool {
let specifier_str = specifier.as_str(); if let Some(excluded_urls) = excluded_urls {
for (workspace, enabled_paths) in enabled_paths.iter() { for excluded_path in excluded_urls {
if specifier_str.starts_with(workspace.as_str()) { if specifier.as_str().starts_with(excluded_path.as_str()) {
return enabled_paths
.iter()
.any(|path| specifier_str.starts_with(path.as_str()));
}
}
if let Some(excluded_paths) = excluded_paths {
for excluded_path in excluded_paths {
if specifier_str.starts_with(excluded_path.as_str()) {
return false; return false;
} }
} }
} }
settings
.specifiers let root_enable = settings
.get(specifier) .workspace
.and_then(|settings| settings.enable) .enable
.or(settings.workspace.enable) .unwrap_or(workspace_has_config_file);
.unwrap_or(workspace_has_config_file)
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() {
return settings.enable.unwrap_or(root_enable);
}
}
for (workspace_uri, _) in workspace_folders {
if specifier.as_str().starts_with(workspace_uri.as_str()) {
let specifier_settings = settings.specifiers.get(workspace_uri);
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 {
let Ok(scope_path) = specifier_to_file_path(workspace_uri) else {
lsp_log!("Unable to convert uri \"{}\" to path.", workspace_uri);
return false;
};
for path in enable_paths {
let path = scope_path.join(path);
let Ok(path_uri) = Url::from_file_path(&path) else {
lsp_log!("Unable to convert path \"{}\" to uri.", path.display());
continue;
};
if specifier.as_str().starts_with(path_uri.as_str()) {
return true;
}
}
return false;
} else {
return specifier_settings
.and_then(|s| s.enable)
.unwrap_or(root_enable);
}
}
}
root_enable
} }
fn resolve_lockfile_from_config(config_file: &ConfigFile) -> Option<Lockfile> { fn resolve_lockfile_from_config(config_file: &ConfigFile) -> Option<Lockfile> {
@ -878,7 +870,8 @@ mod tests {
#[test] #[test]
fn test_config_specifier_enabled() { fn test_config_specifier_enabled() {
let mut config = Config::new(); let root_uri = resolve_url("file:///").unwrap();
let mut config = Config::new_with_root(root_uri);
let specifier = resolve_url("file:///a.ts").unwrap(); let specifier = resolve_url("file:///a.ts").unwrap();
assert!(!config.specifier_enabled(&specifier)); assert!(!config.specifier_enabled(&specifier));
config config
@ -891,7 +884,8 @@ mod tests {
#[test] #[test]
fn test_config_snapshot_specifier_enabled() { fn test_config_snapshot_specifier_enabled() {
let mut config = Config::new(); let root_uri = resolve_url("file:///").unwrap();
let mut config = Config::new_with_root(root_uri);
let specifier = resolve_url("file:///a.ts").unwrap(); let specifier = resolve_url("file:///a.ts").unwrap();
assert!(!config.specifier_enabled(&specifier)); assert!(!config.specifier_enabled(&specifier));
config config
@ -905,17 +899,15 @@ mod tests {
#[test] #[test]
fn test_config_specifier_enabled_path() { fn test_config_specifier_enabled_path() {
let mut config = Config::new(); let root_uri = resolve_url("file:///project/").unwrap();
let mut config = Config::new_with_root(root_uri);
let specifier_a = resolve_url("file:///project/worker/a.ts").unwrap(); let specifier_a = resolve_url("file:///project/worker/a.ts").unwrap();
let specifier_b = resolve_url("file:///project/other/b.ts").unwrap(); let specifier_b = resolve_url("file:///project/other/b.ts").unwrap();
assert!(!config.specifier_enabled(&specifier_a)); assert!(!config.specifier_enabled(&specifier_a));
assert!(!config.specifier_enabled(&specifier_b)); assert!(!config.specifier_enabled(&specifier_b));
let mut enabled_paths = HashMap::new(); let workspace_settings =
enabled_paths.insert( serde_json::from_str(r#"{ "enablePaths": ["worker"] }"#).unwrap();
Url::parse("file:///project/").unwrap(), config.set_workspace_settings(workspace_settings).unwrap();
vec![Url::parse("file:///project/worker/").unwrap()],
);
config.enabled_paths = enabled_paths;
assert!(config.specifier_enabled(&specifier_a)); assert!(config.specifier_enabled(&specifier_a));
assert!(!config.specifier_enabled(&specifier_b)); assert!(!config.specifier_enabled(&specifier_b));
let config_snapshot = config.snapshot(); let config_snapshot = config.snapshot();
@ -933,7 +925,7 @@ mod tests {
config.workspace_settings().clone(), config.workspace_settings().clone(),
WorkspaceSettings { WorkspaceSettings {
enable: None, enable: None,
enable_paths: Vec::new(), enable_paths: None,
cache: None, cache: None,
certificate_stores: None, certificate_stores: None,
config: None, config: None,
@ -1038,11 +1030,10 @@ mod tests {
#[test] #[test]
fn config_enabled_urls() { fn config_enabled_urls() {
let mut config = Config::new(); let root_dir = resolve_url("file:///example/").unwrap();
let root_dir = Url::parse("file:///example/").unwrap(); let mut config = Config::new_with_root(root_dir.clone());
config.root_uri = Some(root_dir.clone());
config.settings.workspace.enable = Some(false); config.settings.workspace.enable = Some(false);
config.settings.workspace.enable_paths = Vec::new(); config.settings.workspace.enable_paths = None;
assert_eq!(config.enabled_urls(), vec![]); assert_eq!(config.enabled_urls(), vec![]);
config.settings.workspace.enable = Some(true); config.settings.workspace.enable = Some(true);
@ -1052,24 +1043,60 @@ mod tests {
let root_dir1 = Url::parse("file:///root1/").unwrap(); let root_dir1 = Url::parse("file:///root1/").unwrap();
let root_dir2 = Url::parse("file:///root2/").unwrap(); let root_dir2 = Url::parse("file:///root2/").unwrap();
let root_dir3 = Url::parse("file:///root3/").unwrap(); let root_dir3 = Url::parse("file:///root3/").unwrap();
config.enabled_paths = HashMap::from([ config.workspace_folders = vec![
( (
root_dir1.clone(), root_dir1.clone(),
vec![ lsp::WorkspaceFolder {
root_dir1.join("sub_dir/").unwrap(), uri: root_dir1.clone(),
root_dir1.join("sub_dir/other/").unwrap(), name: "1".to_string(),
root_dir1.join("test.ts").unwrap(), },
],
), ),
(root_dir2.clone(), vec![root_dir2.join("other.ts").unwrap()]), (
(root_dir3.clone(), vec![]), root_dir2.clone(),
]); lsp::WorkspaceFolder {
uri: root_dir2.clone(),
name: "2".to_string(),
},
),
(
root_dir3.clone(),
lsp::WorkspaceFolder {
uri: root_dir3.clone(),
name: "3".to_string(),
},
),
];
config.set_specifier_settings(
root_dir1.clone(),
SpecifierSettings {
enable_paths: Some(vec![
"sub_dir".to_string(),
"sub_dir/other".to_string(),
"test.ts".to_string(),
]),
..Default::default()
},
);
config.set_specifier_settings(
root_dir2.clone(),
SpecifierSettings {
enable_paths: Some(vec!["other.ts".to_string()]),
..Default::default()
},
);
config.set_specifier_settings(
root_dir3.clone(),
SpecifierSettings {
enable: Some(true),
..Default::default()
},
);
assert_eq!( assert_eq!(
config.enabled_urls(), config.enabled_urls(),
vec![ vec![
root_dir1.join("sub_dir/").unwrap(), root_dir1.join("sub_dir").unwrap(),
root_dir1.join("sub_dir/other/").unwrap(), root_dir1.join("sub_dir/other").unwrap(),
root_dir1.join("test.ts").unwrap(), root_dir1.join("test.ts").unwrap(),
root_dir2.join("other.ts").unwrap(), root_dir2.join("other.ts").unwrap(),
root_dir3 root_dir3
@ -1079,9 +1106,8 @@ mod tests {
#[test] #[test]
fn config_enable_via_config_file_detection() { fn config_enable_via_config_file_detection() {
let mut config = Config::new(); let root_uri = resolve_url("file:///root/").unwrap();
let root_uri = Url::parse("file:///root/").unwrap(); let mut config = Config::new_with_root(root_uri.clone());
config.root_uri = Some(root_uri.clone());
config.settings.workspace.enable = None; config.settings.workspace.enable = None;
assert_eq!(config.enabled_urls(), vec![]); assert_eq!(config.enabled_urls(), vec![]);

View file

@ -1388,6 +1388,7 @@ mod tests {
} }
fn mock_config() -> ConfigSnapshot { fn mock_config() -> ConfigSnapshot {
let root_uri = resolve_url("file:///").unwrap();
ConfigSnapshot { ConfigSnapshot {
settings: Settings { settings: Settings {
workspace: WorkspaceSettings { workspace: WorkspaceSettings {
@ -1397,6 +1398,13 @@ mod tests {
}, },
..Default::default() ..Default::default()
}, },
workspace_folders: vec![(
root_uri.clone(),
lsp::WorkspaceFolder {
uri: root_uri,
name: "".to_string(),
},
)],
..Default::default() ..Default::default()
} }
} }
@ -1468,7 +1476,7 @@ let c: number = "a";
specifier.clone(), specifier.clone(),
SpecifierSettings { SpecifierSettings {
enable: Some(false), enable: Some(false),
enable_paths: Vec::new(), enable_paths: None,
code_lens: Default::default(), code_lens: Default::default(),
}, },
); );

View file

@ -403,48 +403,33 @@ impl LanguageServer {
} }
pub async fn refresh_specifiers_from_client(&self) -> bool { pub async fn refresh_specifiers_from_client(&self) -> bool {
let (client, specifiers) = let (client, specifiers) = {
let ls = self.0.read().await;
let specifiers = if ls.config.client_capabilities.workspace_configuration
{ {
let ls = self.0.read().await; let root_capacity = std::cmp::max(ls.config.workspace_folders.len(), 1);
let specifiers = let config_specifiers = ls.config.get_specifiers();
if ls.config.client_capabilities.workspace_configuration { let mut specifiers =
let root_capacity = match &ls.config.workspace_folders { HashMap::with_capacity(root_capacity + config_specifiers.len());
Some(folder) => folder.len(), for (specifier, folder) in &ls.config.workspace_folders {
None => 1, specifiers
}; .insert(specifier.clone(), LspClientUrl::new(folder.uri.clone()));
let config_specifiers = ls.config.get_specifiers(); }
let mut specifiers = specifiers.extend(
HashMap::with_capacity(root_capacity + config_specifiers.len()); ls.config
match &ls.config.workspace_folders { .get_specifiers()
Some(entry) => { .iter()
for (specifier, folder) in entry { .map(|s| (s.clone(), ls.url_map.normalize_specifier(s).unwrap())),
specifiers.insert( );
specifier.clone(),
LspClientUrl::new(folder.uri.clone()),
);
}
}
None => {
if let Some(root_uri) = &ls.config.root_uri {
specifiers.insert(
root_uri.clone(),
ls.url_map.normalize_specifier(root_uri).unwrap(),
);
}
}
}
specifiers.extend(ls.config.get_specifiers().iter().map(|s| {
(s.clone(), ls.url_map.normalize_specifier(s).unwrap())
}));
Some(specifiers.into_iter().collect::<Vec<_>>()) Some(specifiers.into_iter().collect::<Vec<_>>())
} else { } else {
None None
};
(ls.client.clone(), specifiers)
}; };
(ls.client.clone(), specifiers)
};
let mut touched = false; let mut touched = false;
if let Some(specifiers) = specifiers { if let Some(specifiers) = specifiers {
let configs_result = client let configs_result = client
@ -477,10 +462,6 @@ impl LanguageServer {
} }
} }
} }
if ls.config.update_enabled_paths() {
touched = true;
}
} }
touched touched
} }
@ -701,7 +682,7 @@ impl Inner {
lsp_log!("Setting Deno 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) { let config_url = if let Ok(url) = Url::from_file_path(config_str) {
Ok(url) Ok(url)
} else if let Some(root_uri) = &self.config.root_uri { } else if let Some(root_uri) = self.config.root_uri() {
root_uri.join(config_str).map_err(|_| { root_uri.join(config_str).map_err(|_| {
anyhow!("Bad file path for configuration file: \"{}\"", config_str) anyhow!("Bad file path for configuration file: \"{}\"", config_str)
}) })
@ -723,7 +704,7 @@ impl Inner {
// It is possible that root_uri is not set, for example when having a single // 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 // file open and not a workspace. In those situations we can't
// automatically discover the configuration // automatically discover the configuration
if let Some(root_uri) = &self.config.root_uri { if let Some(root_uri) = self.config.root_uri() {
let root_path = specifier_to_file_path(root_uri)?; let root_path = specifier_to_file_path(root_uri)?;
let mut checked = std::collections::HashSet::new(); let mut checked = std::collections::HashSet::new();
let maybe_config = ConfigFile::discover_from(&root_path, &mut checked)?; let maybe_config = ConfigFile::discover_from(&root_path, &mut checked)?;
@ -747,7 +728,7 @@ impl Inner {
// It is possible that root_uri is not set, for example when having a single // 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 // file open and not a workspace. In those situations we can't
// automatically discover the configuration // automatically discover the configuration
if let Some(root_uri) = &self.config.root_uri { if let Some(root_uri) = self.config.root_uri() {
let root_path = specifier_to_file_path(root_uri)?; let root_path = specifier_to_file_path(root_uri)?;
let maybe_package_json = package_json::discover_from( let maybe_package_json = package_json::discover_from(
&root_path, &root_path,
@ -846,7 +827,7 @@ impl Inner {
lsp_log!("Setting global cache path from: \"{}\"", cache_str); lsp_log!("Setting global cache path from: \"{}\"", cache_str);
let cache_url = if let Ok(url) = Url::from_file_path(cache_str) { let cache_url = if let Ok(url) = Url::from_file_path(cache_str) {
Ok(url) Ok(url)
} else if let Some(root_uri) = &self.config.root_uri { } else if let Some(root_uri) = self.config.root_uri() {
let root_path = specifier_to_file_path(root_uri)?; let root_path = specifier_to_file_path(root_uri)?;
let cache_path = root_path.join(cache_str); let cache_path = root_path.join(cache_str);
Url::from_file_path(cache_path).map_err(|_| { Url::from_file_path(cache_path).map_err(|_| {
@ -892,8 +873,7 @@ impl Inner {
let workspace_settings = self.config.workspace_settings(); let workspace_settings = self.config.workspace_settings();
let maybe_root_path = self let maybe_root_path = self
.config .config
.root_uri .root_uri()
.as_ref()
.and_then(|uri| specifier_to_file_path(uri).ok()); .and_then(|uri| specifier_to_file_path(uri).ok());
let root_cert_store = get_root_cert_store( let root_cert_store = get_root_cert_store(
maybe_root_path, maybe_root_path,
@ -1074,7 +1054,7 @@ impl Inner {
anyhow!("Bad data url for import map: {}", import_map_str) anyhow!("Bad data url for import map: {}", import_map_str)
})?; })?;
Some(import_map_url) Some(import_map_url)
} else if let Some(root_uri) = &self.config.root_uri { } else if let Some(root_uri) = self.config.root_uri() {
let root_path = specifier_to_file_path(root_uri)?; let root_path = specifier_to_file_path(root_uri)?;
let import_map_path = root_path.join(&import_map_str); let import_map_path = root_path.join(&import_map_str);
let import_map_url = let import_map_url =
@ -1282,20 +1262,14 @@ impl Inner {
} }
{ {
// sometimes this root uri may not have a trailing slash, so force it to
self.config.root_uri = params
.root_uri
.map(|s| self.url_map.normalize_url(&s, LspUrlKind::Folder));
if let Some(value) = params.initialization_options { if let Some(value) = params.initialization_options {
self.config.set_workspace_settings(value).map_err(|err| { self.config.set_workspace_settings(value).map_err(|err| {
error!("Cannot set workspace settings: {}", err); error!("Cannot set workspace settings: {}", err);
LspError::internal_error() LspError::internal_error()
})?; })?;
self.config.update_enabled_paths();
} }
self.config.workspace_folders = params.workspace_folders.map(|folders| { if let Some(folders) = params.workspace_folders {
folders self.config.workspace_folders = folders
.into_iter() .into_iter()
.map(|folder| { .map(|folder| {
( (
@ -1303,8 +1277,31 @@ impl Inner {
folder, folder,
) )
}) })
.collect() .collect();
}); }
// rootUri is deprecated by the LSP spec. If it's specified, merge it into
// workspace_folders.
if let Some(root_uri) = params.root_uri {
if !self
.config
.workspace_folders
.iter()
.any(|(_, f)| f.uri == root_uri)
{
let name = root_uri.path_segments().and_then(|s| s.last());
let name = name.unwrap_or_default().to_string();
self.config.workspace_folders.insert(
0,
(
self.url_map.normalize_url(&root_uri, LspUrlKind::Folder),
WorkspaceFolder {
uri: root_uri,
name,
},
),
);
}
}
self.config.update_capabilities(&params.capabilities); self.config.update_capabilities(&params.capabilities);
} }
@ -1489,7 +1486,6 @@ impl Inner {
if let Err(err) = self.config.set_workspace_settings(value) { if let Err(err) = self.config.set_workspace_settings(value) {
error!("failed to update settings: {}", err); error!("failed to update settings: {}", err);
} }
self.config.update_enabled_paths();
} }
self.update_debug_flag(); self.update_debug_flag();
@ -1656,18 +1652,16 @@ impl Inner {
) )
}) })
.collect::<Vec<(ModuleSpecifier, WorkspaceFolder)>>(); .collect::<Vec<(ModuleSpecifier, WorkspaceFolder)>>();
if let Some(current_folders) = &self.config.workspace_folders { for (specifier, folder) in &self.config.workspace_folders {
for (specifier, folder) in current_folders { if !params.event.removed.is_empty()
if !params.event.removed.is_empty() && params.event.removed.iter().any(|f| f.uri == folder.uri)
&& params.event.removed.iter().any(|f| f.uri == folder.uri) {
{ continue;
continue;
}
workspace_folders.push((specifier.clone(), folder.clone()));
} }
workspace_folders.push((specifier.clone(), folder.clone()));
} }
self.config.workspace_folders = Some(workspace_folders); self.config.workspace_folders = workspace_folders;
} }
async fn document_symbol( async fn document_symbol(
@ -2627,8 +2621,7 @@ impl Inner {
let maybe_root_path_owned = self let maybe_root_path_owned = self
.config .config
.root_uri .root_uri()
.as_ref()
.and_then(|uri| specifier_to_file_path(uri).ok()); .and_then(|uri| specifier_to_file_path(uri).ok());
let mut resolved_items = Vec::<CallHierarchyIncomingCall>::new(); let mut resolved_items = Vec::<CallHierarchyIncomingCall>::new();
for item in incoming_calls.iter() { for item in incoming_calls.iter() {
@ -2671,8 +2664,7 @@ impl Inner {
let maybe_root_path_owned = self let maybe_root_path_owned = self
.config .config
.root_uri .root_uri()
.as_ref()
.and_then(|uri| specifier_to_file_path(uri).ok()); .and_then(|uri| specifier_to_file_path(uri).ok());
let mut resolved_items = Vec::<CallHierarchyOutgoingCall>::new(); let mut resolved_items = Vec::<CallHierarchyOutgoingCall>::new();
for item in outgoing_calls.iter() { for item in outgoing_calls.iter() {
@ -2720,8 +2712,7 @@ impl Inner {
let response = if let Some(one_or_many) = maybe_one_or_many { let response = if let Some(one_or_many) = maybe_one_or_many {
let maybe_root_path_owned = self let maybe_root_path_owned = self
.config .config
.root_uri .root_uri()
.as_ref()
.and_then(|uri| specifier_to_file_path(uri).ok()); .and_then(|uri| specifier_to_file_path(uri).ok());
let mut resolved_items = Vec::<CallHierarchyItem>::new(); let mut resolved_items = Vec::<CallHierarchyItem>::new();
match one_or_many { match one_or_many {
@ -3131,7 +3122,7 @@ impl tower_lsp::LanguageServer for LanguageServer {
let test_server = testing::TestServer::new( let test_server = testing::TestServer::new(
ls.client.clone(), ls.client.clone(),
ls.performance.clone(), ls.performance.clone(),
ls.config.root_uri.clone(), ls.config.root_uri().cloned(),
); );
ls.maybe_testing_server = Some(test_server); ls.maybe_testing_server = Some(test_server);
} }
@ -3208,7 +3199,6 @@ impl tower_lsp::LanguageServer for LanguageServer {
Ok(specifier_settings) => { Ok(specifier_settings) => {
ls.config ls.config
.set_specifier_settings(specifier.clone(), specifier_settings); .set_specifier_settings(specifier.clone(), specifier_settings);
ls.config.update_enabled_paths();
} }
Err(err) => { Err(err) => {
error!("{}", err); error!("{}", err);

View file

@ -285,7 +285,7 @@ fn get_cwd_uri() -> Result<ModuleSpecifier, AnyError> {
pub fn get_repl_workspace_settings() -> WorkspaceSettings { pub fn get_repl_workspace_settings() -> WorkspaceSettings {
WorkspaceSettings { WorkspaceSettings {
enable: Some(true), enable: Some(true),
enable_paths: Vec::new(), enable_paths: None,
config: None, config: None,
certificate_stores: None, certificate_stores: None,
cache: None, cache: None,

View file

@ -591,6 +591,7 @@ impl LspClientBuilder {
writer, writer,
deno_dir, deno_dir,
stderr_lines_rx, stderr_lines_rx,
config: json!("{}"),
supports_workspace_configuration: false, supports_workspace_configuration: false,
}) })
} }
@ -605,6 +606,7 @@ pub struct LspClient {
deno_dir: TempDir, deno_dir: TempDir,
context: TestContext, context: TestContext,
stderr_lines_rx: Option<mpsc::Receiver<String>>, stderr_lines_rx: Option<mpsc::Receiver<String>>,
config: serde_json::Value,
supports_workspace_configuration: bool, supports_workspace_configuration: bool,
} }
@ -699,21 +701,14 @@ impl LspClient {
}; };
self.write_request("initialize", params); self.write_request("initialize", params);
self.write_notification("initialized", json!({})); self.write_notification("initialized", json!({}));
self.config = config;
if self.supports_workspace_configuration { if self.supports_workspace_configuration {
self.handle_configuration_request(config); self.handle_configuration_request(self.config.clone());
} }
} }
pub fn did_open(&mut self, params: Value) -> CollectedDiagnostics { pub fn did_open(&mut self, params: Value) -> CollectedDiagnostics {
self.did_open_with_config( self.did_open_with_config(params, self.config.clone())
params,
json!([{
"enable": true,
"codeLens": {
"test": true
}
}]),
)
} }
pub fn did_open_with_config( pub fn did_open_with_config(