1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-01-11 16:42:21 -05:00

perf(lsp): cleanup workspace settings scopes (#20937)

This commit is contained in:
Nayeem Rahman 2023-10-24 21:27:27 +01:00 committed by GitHub
parent 8f065a60e7
commit a7bd0cf7a8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 559 additions and 769 deletions

View file

@ -6,14 +6,13 @@ use async_trait::async_trait;
use deno_core::anyhow::anyhow; use deno_core::anyhow::anyhow;
use deno_core::anyhow::bail; use deno_core::anyhow::bail;
use deno_core::error::AnyError; use deno_core::error::AnyError;
use deno_core::serde_json; use deno_core::serde_json::json;
use deno_core::unsync::spawn; use deno_core::unsync::spawn;
use tower_lsp::lsp_types as lsp; use tower_lsp::lsp_types as lsp;
use tower_lsp::lsp_types::ConfigurationItem; use tower_lsp::lsp_types::ConfigurationItem;
use crate::lsp::repl::get_repl_workspace_settings; use crate::lsp::repl::get_repl_workspace_settings;
use super::config::SpecifierSettings;
use super::config::WorkspaceSettings; use super::config::WorkspaceSettings;
use super::config::SETTINGS_SECTION; use super::config::SETTINGS_SECTION;
use super::lsp_custom; use super::lsp_custom;
@ -125,46 +124,11 @@ impl OutsideLockClient {
self.0.register_capability(registrations).await self.0.register_capability(registrations).await
} }
pub async fn specifier_configurations(
&self,
specifiers: Vec<LspClientUrl>,
) -> Result<Vec<Result<SpecifierSettings, AnyError>>, AnyError> {
self
.0
.specifier_configurations(
specifiers.into_iter().map(|s| s.into_url()).collect(),
)
.await
}
pub async fn specifier_configuration(
&self,
specifier: &LspClientUrl,
) -> Result<SpecifierSettings, AnyError> {
let values = self
.0
.specifier_configurations(vec![specifier.as_url().clone()])
.await?;
if let Some(value) = values.into_iter().next() {
value.map_err(|err| {
anyhow!(
"Error converting specifier settings ({}): {}",
specifier,
err
)
})
} else {
bail!(
"Expected the client to return a configuration item for specifier: {}",
specifier
);
}
}
pub async fn workspace_configuration( pub async fn workspace_configuration(
&self, &self,
) -> Result<WorkspaceSettings, AnyError> { scopes: Vec<Option<lsp::Url>>,
self.0.workspace_configuration().await ) -> Result<Vec<WorkspaceSettings>, AnyError> {
self.0.workspace_configuration(scopes).await
} }
pub async fn publish_diagnostics( pub async fn publish_diagnostics(
@ -201,13 +165,10 @@ trait ClientTrait: Send + Sync {
&self, &self,
params: lsp_custom::DidChangeDenoConfigurationNotificationParams, params: lsp_custom::DidChangeDenoConfigurationNotificationParams,
); );
async fn specifier_configurations(
&self,
uris: Vec<lsp::Url>,
) -> Result<Vec<Result<SpecifierSettings, AnyError>>, AnyError>;
async fn workspace_configuration( async fn workspace_configuration(
&self, &self,
) -> Result<WorkspaceSettings, AnyError>; scopes: Vec<Option<lsp::Url>>,
) -> Result<Vec<WorkspaceSettings>, AnyError>;
async fn show_message(&self, message_type: lsp::MessageType, text: String); async fn show_message(&self, message_type: lsp::MessageType, text: String);
async fn register_capability( async fn register_capability(
&self, &self,
@ -288,67 +249,50 @@ impl ClientTrait for TowerClient {
.await .await
} }
async fn specifier_configurations( async fn workspace_configuration(
&self, &self,
uris: Vec<lsp::Url>, scopes: Vec<Option<lsp::Url>>,
) -> Result<Vec<Result<SpecifierSettings, AnyError>>, AnyError> { ) -> Result<Vec<WorkspaceSettings>, AnyError> {
let config_response = self let config_response = self
.0 .0
.configuration( .configuration(
uris scopes
.into_iter() .iter()
.map(|uri| ConfigurationItem { .flat_map(|scope_uri| {
scope_uri: Some(uri), vec![
section: Some(SETTINGS_SECTION.to_string()), ConfigurationItem {
scope_uri: scope_uri.clone(),
section: Some(SETTINGS_SECTION.to_string()),
},
ConfigurationItem {
scope_uri: scope_uri.clone(),
section: Some("javascript".to_string()),
},
ConfigurationItem {
scope_uri: scope_uri.clone(),
section: Some("typescript".to_string()),
},
]
}) })
.collect(), .collect(),
) )
.await?;
Ok(
config_response
.into_iter()
.map(|value| {
serde_json::from_value::<SpecifierSettings>(value).map_err(|err| {
anyhow!("Error converting specifier settings: {}", err)
})
})
.collect(),
)
}
async fn workspace_configuration(
&self,
) -> Result<WorkspaceSettings, AnyError> {
let config_response = self
.0
.configuration(vec![
ConfigurationItem {
scope_uri: None,
section: Some(SETTINGS_SECTION.to_string()),
},
ConfigurationItem {
scope_uri: None,
section: Some("javascript".to_string()),
},
ConfigurationItem {
scope_uri: None,
section: Some("typescript".to_string()),
},
])
.await; .await;
match config_response { match config_response {
Ok(configs) => { Ok(configs) => {
let mut configs = configs.into_iter(); let mut configs = configs.into_iter();
let deno = serde_json::to_value(configs.next()).unwrap(); let mut result = Vec::with_capacity(scopes.len());
let javascript = serde_json::to_value(configs.next()).unwrap(); for _ in 0..scopes.len() {
let typescript = serde_json::to_value(configs.next()).unwrap(); let deno = json!(configs.next());
Ok(WorkspaceSettings::from_raw_settings( let javascript = json!(configs.next());
deno, javascript, typescript, let typescript = json!(configs.next());
)) result.push(WorkspaceSettings::from_raw_settings(
deno, javascript, typescript,
));
}
Ok(result)
} }
Err(err) => { Err(err) => {
bail!("Error getting workspace configuration: {}", err) bail!("Error getting workspace configurations: {}", err)
} }
} }
} }
@ -406,27 +350,11 @@ impl ClientTrait for ReplClient {
) { ) {
} }
async fn specifier_configurations(
&self,
uris: Vec<lsp::Url>,
) -> Result<Vec<Result<SpecifierSettings, AnyError>>, AnyError> {
// all specifiers are enabled for the REPL
let settings = uris
.into_iter()
.map(|_| {
Ok(SpecifierSettings {
enable: Some(true),
..Default::default()
})
})
.collect();
Ok(settings)
}
async fn workspace_configuration( async fn workspace_configuration(
&self, &self,
) -> Result<WorkspaceSettings, AnyError> { scopes: Vec<Option<lsp::Url>>,
Ok(get_repl_workspace_settings()) ) -> Result<Vec<WorkspaceSettings>, AnyError> {
Ok(vec![get_repl_workspace_settings(); scopes.len()])
} }
async fn show_message( async fn show_message(

View file

@ -408,7 +408,7 @@ fn collect_test(
config: &Config, config: &Config,
) -> Result<Vec<lsp::CodeLens>, AnyError> { ) -> Result<Vec<lsp::CodeLens>, AnyError> {
if config.specifier_enabled_for_test(specifier) if config.specifier_enabled_for_test(specifier)
&& config.specifier_code_lens_test(specifier) && config.enabled_code_lens_test_for_specifier(specifier)
{ {
if let Some(parsed_source) = parsed_source { if let Some(parsed_source) = parsed_source {
let mut collector = let mut collector =

View file

@ -2,6 +2,7 @@
use super::client::Client; use super::client::Client;
use super::config::ConfigSnapshot; use super::config::ConfigSnapshot;
use super::config::WorkspaceSettings;
use super::documents::file_like_to_file_specifier; use super::documents::file_like_to_file_specifier;
use super::documents::Documents; use super::documents::Documents;
use super::documents::DocumentsFilter; use super::documents::DocumentsFilter;
@ -52,12 +53,12 @@ pub struct CompletionItemData {
/// a notification to the client. /// a notification to the client.
async fn check_auto_config_registry( async fn check_auto_config_registry(
url_str: &str, url_str: &str,
config: &ConfigSnapshot, workspace_settings: &WorkspaceSettings,
client: &Client, client: &Client,
module_registries: &ModuleRegistry, module_registries: &ModuleRegistry,
) { ) {
// check to see if auto discovery is enabled // check to see if auto discovery is enabled
if config.settings.workspace.suggest.imports.auto_discover { if workspace_settings.suggest.imports.auto_discover {
if let Ok(specifier) = resolve_url(url_str) { if let Ok(specifier) = resolve_url(url_str) {
let scheme = specifier.scheme(); let scheme = specifier.scheme();
let path = &specifier[Position::BeforePath..]; let path = &specifier[Position::BeforePath..];
@ -67,11 +68,14 @@ async fn check_auto_config_registry(
{ {
// check to see if this origin is already explicitly set // check to see if this origin is already explicitly set
let in_config = let in_config =
config.settings.workspace.suggest.imports.hosts.iter().any( workspace_settings
|(h, _)| { .suggest
.imports
.hosts
.iter()
.any(|(h, _)| {
resolve_url(h).map(|u| u.origin()) == Ok(specifier.origin()) resolve_url(h).map(|u| u.origin()) == Ok(specifier.origin())
}, });
);
// if it isn't in the configuration, we will check to see if it supports // if it isn't in the configuration, we will check to see if it supports
// suggestions and send a notification to the client. // suggestions and send a notification to the client.
if !in_config { if !in_config {
@ -176,7 +180,13 @@ pub async fn get_import_completions(
})) }))
} else if !text.is_empty() { } else if !text.is_empty() {
// completion of modules from a module registry or cache // completion of modules from a module registry or cache
check_auto_config_registry(&text, config, client, module_registries).await; check_auto_config_registry(
&text,
config.workspace_settings_for_specifier(specifier),
client,
module_registries,
)
.await;
let offset = if position.character > range.start.character { let offset = if position.character > range.start.character {
(position.character - range.start.character) as usize (position.character - range.start.character) as usize
} else { } else {

View file

@ -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)] #[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct DenoCompletionSettings { 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)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct TestingSettings { pub struct TestingSettings {
@ -712,56 +678,6 @@ impl WorkspaceSettings {
.unwrap_or_default(); .unwrap_or_default();
Self::from_raw_settings(deno, javascript, typescript) 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)] #[derive(Debug, Clone, Default)]
@ -773,6 +689,13 @@ pub struct ConfigSnapshot {
} }
impl 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. /// 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(
@ -803,8 +726,59 @@ impl ConfigSnapshot {
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]
pub struct Settings { pub struct Settings {
pub specifiers: BTreeMap<ModuleSpecifier, SpecifierSettings>, pub unscoped: WorkspaceSettings,
pub workspace: 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)] #[derive(Debug)]
@ -860,6 +834,93 @@ impl Config {
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> { pub fn root_uri(&self) -> Option<&Url> {
self.workspace_folders.get(0).map(|p| &p.0) 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> { pub fn snapshot(&self) -> Arc<ConfigSnapshot> {
Arc::new(ConfigSnapshot { Arc::new(ConfigSnapshot {
client_capabilities: self.client_capabilities.clone(), 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 { pub fn specifier_enabled(&self, specifier: &ModuleSpecifier) -> bool {
specifier_enabled( specifier_enabled(
specifier, specifier,
@ -1009,11 +1052,8 @@ impl Config {
lsp_log!("Unable to convert uri \"{}\" to path.", workspace_uri); lsp_log!("Unable to convert uri \"{}\" to path.", workspace_uri);
continue; continue;
}; };
let specifier_settings = self.settings.specifiers.get(workspace_uri); let settings = self.workspace_settings_for_specifier(workspace_uri);
let enable_paths = specifier_settings if let Some(enable_paths) = &settings.enable_paths {
.and_then(|s| s.enable_paths.as_ref())
.or(self.settings.workspace.enable_paths.as_ref());
if let Some(enable_paths) = enable_paths {
for path in enable_paths { for path in enable_paths {
paths.push(workspace_path.join(path)); 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 { for (workspace_uri, _) in &self.workspace_folders {
let Ok(workspace_path) = specifier_to_file_path(workspace_uri) else { let Ok(workspace_path) = specifier_to_file_path(workspace_uri) else {
lsp_log!("Unable to convert uri \"{}\" to path.", workspace_uri); lsp_log!("Unable to convert uri \"{}\" to path.", workspace_uri);
continue; continue;
}; };
let specifier_settings = self.settings.specifiers.get(workspace_uri); let settings = self.workspace_settings_for_specifier(workspace_uri);
let enable = specifier_settings if settings.enable.unwrap_or_else(|| self.has_config_file()) {
.and_then(|s| s.enable) for path in &settings.disable_paths {
.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 {
paths.push(workspace_path.join(path)); paths.push(workspace_path.join(path));
} }
} else { } else {
@ -1065,16 +1094,6 @@ impl Config {
paths 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( pub fn update_capabilities(
&mut self, &mut self,
capabilities: &lsp::ClientCapabilities, 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( fn specifier_enabled(
specifier: &Url, specifier: &Url,
config_file: Option<&ConfigFile>, config_file: Option<&ConfigFile>,
settings: &Settings, settings: &Settings,
workspace_folders: &Vec<(Url, lsp::WorkspaceFolder)>, workspace_folders: &[(Url, lsp::WorkspaceFolder)],
) -> bool { ) -> bool {
if let Some(cf) = config_file { if let Some(cf) = config_file {
if let Some(files) = cf.to_files_config().ok().flatten() { 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 { let Ok(path) = specifier_to_file_path(specifier) else {
// Non-file URLs are not disabled by these settings. // Non-file URLs are not disabled by these settings.
return true; return true;
}; };
for (workspace_uri, _) in workspace_folders { let (settings, mut folder_uri) = settings.get_for_specifier(specifier);
let Ok(workspace_path) = specifier_to_file_path(workspace_uri) else { folder_uri = folder_uri.or_else(|| workspace_folders.get(0).map(|f| &f.0));
lsp_log!("Unable to convert uri \"{}\" to path.", workspace_uri); let mut disable_paths = vec![];
continue; let mut enable_paths = None;
}; if let Some(folder_uri) = folder_uri {
if path.starts_with(&workspace_path) { if let Ok(folder_path) = specifier_to_file_path(folder_uri) {
let specifier_settings = settings.specifiers.get(workspace_uri); disable_paths = settings
let disable_paths = specifier_settings .disable_paths
.map(|s| &s.disable_paths)
.unwrap_or(&settings.workspace.disable_paths);
let resolved_disable_paths = disable_paths
.iter() .iter()
.map(|p| workspace_path.join(p)) .map(|p| folder_path.join(p))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let enable_paths = specifier_settings enable_paths = settings.enable_paths.as_ref().map(|enable_paths| {
.and_then(|s| s.enable_paths.as_ref()) enable_paths
.or(settings.workspace.enable_paths.as_ref()); .iter()
if let Some(enable_paths) = enable_paths { .map(|p| folder_path.join(p))
for enable_path in enable_paths { .collect::<Vec<_>>()
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));
}
} }
} }
root_enable 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))
}
} }
fn resolve_lockfile_from_config(config_file: &ConfigFile) -> Option<Lockfile> { fn resolve_lockfile_from_config(config_file: &ConfigFile) -> Option<Lockfile> {
@ -1285,6 +1267,7 @@ mod tests {
"enable": true "enable": true
})) }))
.unwrap(), .unwrap(),
None,
); );
assert!(config.specifier_enabled(&specifier)); assert!(config.specifier_enabled(&specifier));
} }
@ -1300,6 +1283,7 @@ mod tests {
"enable": true "enable": true
})) }))
.unwrap(), .unwrap(),
None,
); );
let config_snapshot = config.snapshot(); let config_snapshot = config.snapshot();
assert!(config_snapshot.specifier_enabled(&specifier)); assert!(config_snapshot.specifier_enabled(&specifier));
@ -1315,7 +1299,7 @@ mod tests {
assert!(!config.specifier_enabled(&specifier_b)); assert!(!config.specifier_enabled(&specifier_b));
let workspace_settings = let workspace_settings =
serde_json::from_str(r#"{ "enablePaths": ["worker"] }"#).unwrap(); 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_a));
assert!(!config.specifier_enabled(&specifier_b)); assert!(!config.specifier_enabled(&specifier_b));
let config_snapshot = config.snapshot(); let config_snapshot = config.snapshot();
@ -1327,10 +1311,10 @@ mod tests {
fn test_config_specifier_disabled_path() { fn test_config_specifier_disabled_path() {
let root_uri = resolve_url("file:///root/").unwrap(); let root_uri = resolve_url("file:///root/").unwrap();
let mut config = Config::new_with_root(root_uri.clone()); 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()]); 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("mod1.ts").unwrap()));
assert!(!config.specifier_enabled(&root_uri.join("mod2.ts").unwrap())); assert!(!config.specifier_enabled(&root_uri.join("mod2.ts").unwrap()));
@ -1340,7 +1324,8 @@ mod tests {
#[test] #[test]
fn test_set_workspace_settings_defaults() { fn test_set_workspace_settings_defaults() {
let mut config = Config::new(); 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!( assert_eq!(
config.workspace_settings().clone(), config.workspace_settings().clone(),
WorkspaceSettings { WorkspaceSettings {
@ -1472,6 +1457,7 @@ mod tests {
let mut config = Config::new(); let mut config = Config::new();
config.set_workspace_settings( config.set_workspace_settings(
serde_json::from_value(json!({ "cache": "" })).unwrap(), serde_json::from_value(json!({ "cache": "" })).unwrap(),
None,
); );
assert_eq!( assert_eq!(
config.workspace_settings().clone(), config.workspace_settings().clone(),
@ -1484,6 +1470,7 @@ mod tests {
let mut config = Config::new(); let mut config = Config::new();
config.set_workspace_settings( config.set_workspace_settings(
serde_json::from_value(json!({ "import_map": "" })).unwrap(), serde_json::from_value(json!({ "import_map": "" })).unwrap(),
None,
); );
assert_eq!( assert_eq!(
config.workspace_settings().clone(), config.workspace_settings().clone(),
@ -1496,6 +1483,7 @@ mod tests {
let mut config = Config::new(); let mut config = Config::new();
config.set_workspace_settings( config.set_workspace_settings(
serde_json::from_value(json!({ "tls_certificate": "" })).unwrap(), serde_json::from_value(json!({ "tls_certificate": "" })).unwrap(),
None,
); );
assert_eq!( assert_eq!(
config.workspace_settings().clone(), config.workspace_settings().clone(),
@ -1508,6 +1496,7 @@ mod tests {
let mut config = Config::new(); let mut config = Config::new();
config.set_workspace_settings( config.set_workspace_settings(
serde_json::from_value(json!({ "config": "" })).unwrap(), serde_json::from_value(json!({ "config": "" })).unwrap(),
None,
); );
assert_eq!( assert_eq!(
config.workspace_settings().clone(), config.workspace_settings().clone(),
@ -1541,30 +1530,39 @@ mod tests {
}, },
), ),
]; ];
config.set_specifier_settings( config.set_workspace_settings(
Url::parse("file:///root1/").unwrap(), Default::default(),
SpecifierSettings { Some(
enable_paths: Some(vec![ vec![
"sub_dir".to_string(), (
"sub_dir/other".to_string(), Url::parse("file:///root1/").unwrap(),
"test.ts".to_string(), WorkspaceSettings {
]), enable_paths: Some(vec![
..Default::default() "sub_dir".to_string(),
}, "sub_dir/other".to_string(),
); "test.ts".to_string(),
config.set_specifier_settings( ]),
Url::parse("file:///root2/").unwrap(), ..Default::default()
SpecifierSettings { },
enable_paths: Some(vec!["other.ts".to_string()]), ),
..Default::default() (
}, Url::parse("file:///root2/").unwrap(),
); WorkspaceSettings {
config.set_specifier_settings( enable_paths: Some(vec!["other.ts".to_string()]),
Url::parse("file:///root3/").unwrap(), ..Default::default()
SpecifierSettings { },
enable: Some(true), ),
..Default::default() (
}, Url::parse("file:///root3/").unwrap(),
WorkspaceSettings {
enable: Some(true),
..Default::default()
},
),
]
.into_iter()
.collect(),
),
); );
assert_eq!( assert_eq!(
@ -1583,7 +1581,7 @@ mod tests {
fn config_enable_via_config_file_detection() { fn config_enable_via_config_file_detection() {
let root_uri = resolve_url("file:///root/").unwrap(); let root_uri = resolve_url("file:///root/").unwrap();
let mut config = Config::new_with_root(root_uri.clone()); 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)); assert!(!config.specifier_enabled(&root_uri));
config.set_config_file( config.set_config_file(
@ -1597,7 +1595,7 @@ mod tests {
fn config_specifier_enabled_matches_by_path_component() { fn config_specifier_enabled_matches_by_path_component() {
let root_uri = resolve_url("file:///root/").unwrap(); let root_uri = resolve_url("file:///root/").unwrap();
let mut config = Config::new_with_root(root_uri.clone()); 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())); assert!(!config.specifier_enabled(&root_uri.join("mod.ts").unwrap()));
} }
@ -1605,11 +1603,11 @@ mod tests {
fn config_specifier_enabled_for_test() { fn config_specifier_enabled_for_test() {
let root_uri = resolve_url("file:///root/").unwrap(); let root_uri = resolve_url("file:///root/").unwrap();
let mut config = Config::new_with_root(root_uri.clone()); 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()]); 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!( assert!(
config.specifier_enabled_for_test(&root_uri.join("mod1.ts").unwrap()) config.specifier_enabled_for_test(&root_uri.join("mod1.ts").unwrap())
); );
@ -1619,7 +1617,7 @@ mod tests {
assert!( assert!(
!config.specifier_enabled_for_test(&root_uri.join("mod3.ts").unwrap()) !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( config.set_config_file(
ConfigFile::new( ConfigFile::new(
@ -1688,7 +1686,7 @@ mod tests {
fn config_snapshot_specifier_enabled_for_test() { fn config_snapshot_specifier_enabled_for_test() {
let root_uri = resolve_url("file:///root/").unwrap(); let root_uri = resolve_url("file:///root/").unwrap();
let mut config = Config::new_with_root(root_uri.clone()); 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( config.set_config_file(
ConfigFile::new( ConfigFile::new(
&json!({ &json!({

View file

@ -788,37 +788,37 @@ fn generate_lint_diagnostics(
let documents = snapshot let documents = snapshot
.documents .documents
.documents(DocumentsFilter::OpenDiagnosable); .documents(DocumentsFilter::OpenDiagnosable);
let workspace_settings = config.settings.workspace.clone();
let lint_rules = get_configured_rules(lint_options.rules.clone()); let lint_rules = get_configured_rules(lint_options.rules.clone());
let mut diagnostics_vec = Vec::new(); let mut diagnostics_vec = Vec::new();
if workspace_settings.lint { for document in documents {
for document in documents { let settings =
// exit early if cancelled config.workspace_settings_for_specifier(document.specifier());
if token.is_cancelled() { if !settings.lint {
break; continue;
}
// ignore any npm package files
if let Some(npm) = &snapshot.npm {
if npm.node_resolver.in_npm_package(document.specifier()) {
continue;
}
}
let version = document.maybe_lsp_version();
diagnostics_vec.push(DiagnosticRecord {
specifier: document.specifier().clone(),
versioned: VersionedDiagnostics {
version,
diagnostics: generate_document_lint_diagnostics(
config,
lint_options,
lint_rules.clone(),
&document,
),
},
});
} }
// exit early if cancelled
if token.is_cancelled() {
break;
}
// ignore any npm package files
if let Some(npm) = &snapshot.npm {
if npm.node_resolver.in_npm_package(document.specifier()) {
continue;
}
}
let version = document.maybe_lsp_version();
diagnostics_vec.push(DiagnosticRecord {
specifier: document.specifier().clone(),
versioned: VersionedDiagnostics {
version,
diagnostics: generate_document_lint_diagnostics(
config,
lint_options,
lint_rules.clone(),
&document,
),
},
});
} }
diagnostics_vec diagnostics_vec
} }
@ -1442,7 +1442,6 @@ mod tests {
use crate::cache::RealDenoCacheEnv; use crate::cache::RealDenoCacheEnv;
use crate::lsp::config::ConfigSnapshot; use crate::lsp::config::ConfigSnapshot;
use crate::lsp::config::Settings; use crate::lsp::config::Settings;
use crate::lsp::config::SpecifierSettings;
use crate::lsp::config::WorkspaceSettings; use crate::lsp::config::WorkspaceSettings;
use crate::lsp::documents::Documents; use crate::lsp::documents::Documents;
use crate::lsp::documents::LanguageId; use crate::lsp::documents::LanguageId;
@ -1497,7 +1496,7 @@ mod tests {
let root_uri = resolve_url("file:///").unwrap(); let root_uri = resolve_url("file:///").unwrap();
ConfigSnapshot { ConfigSnapshot {
settings: Settings { settings: Settings {
workspace: WorkspaceSettings { unscoped: WorkspaceSettings {
enable: Some(true), enable: Some(true),
lint: true, lint: true,
..Default::default() ..Default::default()
@ -1529,7 +1528,6 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn test_enabled_then_disabled_specifier() { async fn test_enabled_then_disabled_specifier() {
let temp_dir = TempDir::new(); let temp_dir = TempDir::new();
let specifier = ModuleSpecifier::parse("file:///a.ts").unwrap();
let (snapshot, cache_location) = setup( let (snapshot, cache_location) = setup(
&temp_dir, &temp_dir,
&[( &[(
@ -1578,15 +1576,10 @@ let c: number = "a";
// now test disabled specifier // now test disabled specifier
{ {
let mut disabled_config = mock_config(); let mut disabled_config = mock_config();
disabled_config.settings.specifiers.insert( disabled_config.settings.unscoped = WorkspaceSettings {
specifier.clone(), enable: Some(false),
SpecifierSettings { ..Default::default()
enable: Some(false), };
disable_paths: vec![],
enable_paths: None,
code_lens: Default::default(),
},
);
let diagnostics = generate_lint_diagnostics( let diagnostics = generate_lint_diagnostics(
&snapshot, &snapshot,

View file

@ -23,6 +23,7 @@ use import_map::ImportMap;
use indexmap::IndexSet; use indexmap::IndexSet;
use log::error; use log::error;
use serde_json::from_value; use serde_json::from_value;
use std::collections::BTreeMap;
use std::collections::HashMap; use std::collections::HashMap;
use std::collections::HashSet; use std::collections::HashSet;
use std::env; use std::env;
@ -80,7 +81,6 @@ use super::tsc::AssetsSnapshot;
use super::tsc::GetCompletionDetailsArgs; use super::tsc::GetCompletionDetailsArgs;
use super::tsc::TsServer; use super::tsc::TsServer;
use super::urls; use super::urls;
use super::urls::LspClientUrl;
use crate::args::get_root_cert_store; use crate::args::get_root_cert_store;
use crate::args::package_json; use crate::args::package_json;
use crate::args::resolve_import_map_from_specifier; use crate::args::resolve_import_map_from_specifier;
@ -403,68 +403,42 @@ impl LanguageServer {
} }
} }
pub async fn refresh_specifiers_from_client(&self) -> bool { pub async fn refresh_configuration(&self) {
let (client, specifiers) = { let (client, folders, capable) = {
let ls = self.0.read().await; let ls = self.0.read().await;
let specifiers = if ls.config.client_capabilities.workspace_configuration (
{ ls.client.clone(),
let root_capacity = std::cmp::max(ls.config.workspace_folders.len(), 1); ls.config.workspace_folders.clone(),
let config_specifiers = ls.config.get_specifiers(); ls.config.client_capabilities.workspace_configuration,
let mut specifiers = )
HashMap::with_capacity(root_capacity + config_specifiers.len());
for (specifier, folder) in &ls.config.workspace_folders {
specifiers
.insert(specifier.clone(), LspClientUrl::new(folder.uri.clone()));
}
specifiers.extend(
ls.config
.get_specifiers()
.iter()
.map(|s| (s.clone(), ls.url_map.normalize_specifier(s).unwrap())),
);
Some(specifiers.into_iter().collect::<Vec<_>>())
} else {
None
};
(ls.client.clone(), specifiers)
}; };
if capable {
let mut touched = false; let mut scopes = Vec::with_capacity(folders.len() + 1);
if let Some(specifiers) = specifiers { scopes.push(None);
let configs_result = client for (_, folder) in &folders {
scopes.push(Some(folder.uri.clone()));
}
let configs = client
.when_outside_lsp_lock() .when_outside_lsp_lock()
.specifier_configurations( .workspace_configuration(scopes)
specifiers
.iter()
.map(|(_, client_uri)| client_uri.clone())
.collect(),
)
.await; .await;
if let Ok(configs) = configs {
let mut ls = self.0.write().await; if configs.len() != folders.len() + 1 {
if let Ok(configs) = configs_result { lsp_warn!("Incorrect number of configurations received.");
for (value, internal_uri) in return;
configs.into_iter().zip(specifiers.into_iter().map(|s| s.0))
{
match value {
Ok(specifier_settings) => {
if ls
.config
.set_specifier_settings(internal_uri, specifier_settings)
{
touched = true;
}
}
Err(err) => {
error!("{}", err);
}
}
} }
let mut configs = configs.into_iter();
let unscoped = configs.next().unwrap();
let mut by_workspace_folder = BTreeMap::new();
for (folder_uri, _) in &folders {
by_workspace_folder
.insert(folder_uri.clone(), configs.next().unwrap());
}
let mut ls = self.0.write().await;
ls.config
.set_workspace_settings(unscoped, Some(by_workspace_folder));
} }
} }
touched
} }
} }
@ -1182,6 +1156,7 @@ impl Inner {
if let Some(options) = params.initialization_options { if let Some(options) = params.initialization_options {
self.config.set_workspace_settings( self.config.set_workspace_settings(
WorkspaceSettings::from_initialization_options(options), WorkspaceSettings::from_initialization_options(options),
None,
); );
} }
if let Some(folders) = params.workspace_folders { if let Some(folders) = params.workspace_folders {
@ -1391,27 +1366,22 @@ impl Inner {
async fn did_change_configuration( async fn did_change_configuration(
&mut self, &mut self,
client_workspace_config: Option<WorkspaceSettings>,
params: DidChangeConfigurationParams, params: DidChangeConfigurationParams,
) { ) {
let maybe_config = if !self.config.client_capabilities.workspace_configuration {
if self.config.client_capabilities.workspace_configuration { let config = params.settings.as_object().map(|settings| {
client_workspace_config let deno =
} else { serde_json::to_value(settings.get(SETTINGS_SECTION)).unwrap();
params.settings.as_object().map(|settings| { let javascript =
let deno = serde_json::to_value(settings.get("javascript")).unwrap();
serde_json::to_value(settings.get(SETTINGS_SECTION)).unwrap(); let typescript =
let javascript = serde_json::to_value(settings.get("typescript")).unwrap();
serde_json::to_value(settings.get("javascript")).unwrap(); WorkspaceSettings::from_raw_settings(deno, javascript, typescript)
let typescript = });
serde_json::to_value(settings.get("typescript")).unwrap(); if let Some(settings) = config {
WorkspaceSettings::from_raw_settings(deno, javascript, typescript) self.config.set_workspace_settings(settings, None);
}) }
}; };
if let Some(settings) = maybe_config {
self.config.set_workspace_settings(settings);
}
self.update_debug_flag(); self.update_debug_flag();
if let Err(err) = self.update_cache().await { if let Err(err) = self.update_cache().await {
@ -2148,8 +2118,7 @@ impl Inner {
.normalize_url(&params.text_document.uri, LspUrlKind::File); .normalize_url(&params.text_document.uri, LspUrlKind::File);
if !self.is_diagnosable(&specifier) if !self.is_diagnosable(&specifier)
|| !self.config.specifier_enabled(&specifier) || !self.config.specifier_enabled(&specifier)
|| !(self.config.workspace_settings().enabled_code_lens() || !self.config.enabled_code_lens_for_specifier(&specifier)
|| self.config.specifier_code_lens_test(&specifier))
{ {
return Ok(None); return Ok(None);
} }
@ -2385,10 +2354,8 @@ impl Inner {
&params.text_document_position.text_document.uri, &params.text_document_position.text_document.uri,
LspUrlKind::File, LspUrlKind::File,
); );
let language_settings = self let language_settings =
.config self.config.language_settings_for_specifier(&specifier);
.workspace_settings()
.language_settings_for_specifier(&specifier);
if !self.is_diagnosable(&specifier) if !self.is_diagnosable(&specifier)
|| !self.config.specifier_enabled(&specifier) || !self.config.specifier_enabled(&specifier)
|| !language_settings.map(|s| s.suggest.enabled).unwrap_or(true) || !language_settings.map(|s| s.suggest.enabled).unwrap_or(true)
@ -2457,7 +2424,6 @@ impl Inner {
line_index, line_index,
&self &self
.config .config
.workspace_settings()
.language_settings_for_specifier(&specifier) .language_settings_for_specifier(&specifier)
.cloned() .cloned()
.unwrap_or_default() .unwrap_or_default()
@ -2992,7 +2958,6 @@ impl Inner {
); );
let options = self let options = self
.config .config
.workspace_settings()
.language_settings_for_specifier(&old_specifier) .language_settings_for_specifier(&old_specifier)
.map(|s| s.update_imports_on_file_move.clone()) .map(|s| s.update_imports_on_file_move.clone())
.unwrap_or_default(); .unwrap_or_default();
@ -3188,7 +3153,7 @@ impl tower_lsp::LanguageServer for LanguageServer {
} }
} }
self.refresh_specifiers_from_client().await; self.refresh_configuration().await;
{ {
let mut ls = self.0.write().await; let mut ls = self.0.write().await;
@ -3212,58 +3177,17 @@ impl tower_lsp::LanguageServer for LanguageServer {
return; return;
} }
let (client, client_uri, specifier, should_get_specifier_settings) = { let mut inner = self.0.write().await;
let mut inner = self.0.write().await; let specifier = inner
let client = inner.client.clone(); .url_map
let client_uri = LspClientUrl::new(params.text_document.uri.clone()); .normalize_url(&params.text_document.uri, LspUrlKind::File);
let specifier = inner let document = inner.did_open(&specifier, params).await;
.url_map if document.is_diagnosable() {
.normalize_url(client_uri.as_url(), LspUrlKind::File); inner.refresh_npm_specifiers().await;
let document = inner.did_open(&specifier, params).await; let specifiers = inner.documents.dependents(&specifier);
let should_get_specifier_settings = inner.diagnostics_server.invalidate(&specifiers);
!inner.config.has_specifier_settings(&specifier) inner.send_diagnostics_update();
&& inner.config.client_capabilities.workspace_configuration; inner.send_testing_update();
if document.is_diagnosable() {
inner.refresh_npm_specifiers().await;
let specifiers = inner.documents.dependents(&specifier);
inner.diagnostics_server.invalidate(&specifiers);
// don't send diagnostics yet if we don't have the specifier settings
if !should_get_specifier_settings {
inner.send_diagnostics_update();
inner.send_testing_update();
}
}
(client, client_uri, specifier, should_get_specifier_settings)
};
// retrieve the specifier settings outside the lock if
// they haven't been asked for yet
if should_get_specifier_settings {
let response = client
.when_outside_lsp_lock()
.specifier_configuration(&client_uri)
.await;
let mut ls = self.0.write().await;
match response {
Ok(specifier_settings) => {
ls.config
.set_specifier_settings(specifier.clone(), specifier_settings);
}
Err(err) => {
error!("{}", err);
}
}
if ls
.documents
.get(&specifier)
.map(|d| d.is_diagnosable())
.unwrap_or(false)
{
ls.refresh_documents_config().await;
ls.send_diagnostics_update();
ls.send_testing_update();
}
} }
} }
@ -3277,7 +3201,10 @@ impl tower_lsp::LanguageServer for LanguageServer {
let mut inner = self.0.write().await; let mut inner = self.0.write().await;
let specifier = inner.url_map.normalize_url(uri, LspUrlKind::File); let specifier = inner.url_map.normalize_url(uri, LspUrlKind::File);
inner.documents.save(&specifier); inner.documents.save(&specifier);
if !inner.config.workspace_settings().cache_on_save if !inner
.config
.workspace_settings_for_specifier(&specifier)
.cache_on_save
|| !inner.config.specifier_enabled(&specifier) || !inner.config.specifier_enabled(&specifier)
|| !inner.diagnostics_state.has_no_cache_diagnostics(&specifier) || !inner.diagnostics_state.has_no_cache_diagnostics(&specifier)
{ {
@ -3310,47 +3237,17 @@ impl tower_lsp::LanguageServer for LanguageServer {
&self, &self,
params: DidChangeConfigurationParams, params: DidChangeConfigurationParams,
) { ) {
let (mark, has_workspace_capability, client) = { let mark = {
let inner = self.0.read().await; let inner = self.0.read().await;
( inner
inner .performance
.performance .mark("did_change_configuration", Some(&params))
.mark("did_change_configuration", Some(&params)),
inner.config.client_capabilities.workspace_configuration,
inner.client.clone(),
)
}; };
self.refresh_specifiers_from_client().await; self.refresh_configuration().await;
// Get the configuration from the client outside of the lock
// in order to prevent potential deadlocking scenarios where
// the server holds a lock and calls into the client, which
// calls into the server which deadlocks acquiring the lock.
// There is a gap here between when the configuration is
// received and acquiring the lock, but most likely there
// won't be any racing here.
let client_workspace_config = if has_workspace_capability {
let config_response = client
.when_outside_lsp_lock()
.workspace_configuration()
.await;
match config_response {
Ok(settings) => Some(settings),
Err(err) => {
error!("{}", err);
None
}
}
} else {
None
};
// now update the inner state
let mut inner = self.0.write().await; let mut inner = self.0.write().await;
inner inner.did_change_configuration(params).await;
.did_change_configuration(client_workspace_config, params)
.await;
inner.performance.measure(mark); inner.performance.measure(mark);
} }
@ -3374,7 +3271,8 @@ impl tower_lsp::LanguageServer for LanguageServer {
(ls.performance.clone(), mark) (ls.performance.clone(), mark)
}; };
if self.refresh_specifiers_from_client().await { self.refresh_configuration().await;
{
let mut ls = self.0.write().await; let mut ls = self.0.write().await;
ls.refresh_documents_config().await; ls.refresh_documents_config().await;
ls.diagnostics_server.invalidate_all(); ls.diagnostics_server.invalidate_all();
@ -3681,10 +3579,7 @@ impl Inner {
.normalize_url(&params.text_document.uri, LspUrlKind::File); .normalize_url(&params.text_document.uri, LspUrlKind::File);
if !self.is_diagnosable(&specifier) if !self.is_diagnosable(&specifier)
|| !self.config.specifier_enabled(&specifier) || !self.config.specifier_enabled(&specifier)
|| !self || !self.config.enabled_inlay_hints_for_specifier(&specifier)
.config
.workspace_settings()
.enabled_inlay_hints(&specifier)
{ {
return Ok(None); return Ok(None);
} }

View file

@ -4215,9 +4215,8 @@ impl UserPreferences {
use_label_details_in_completion_entries: Some(true), use_label_details_in_completion_entries: Some(true),
..Default::default() ..Default::default()
}; };
let Some(language_settings) = config let Some(language_settings) =
.workspace_settings() config.language_settings_for_specifier(specifier)
.language_settings_for_specifier(specifier)
else { else {
return base_preferences; return base_preferences;
}; };
@ -5312,7 +5311,7 @@ mod tests {
.variable_types .variable_types
.suppress_when_type_matches_name = true; .suppress_when_type_matches_name = true;
let mut config = config::Config::new(); let mut config = config::Config::new();
config.set_workspace_settings(settings); config.set_workspace_settings(settings, None);
let user_preferences = UserPreferences::from_config_for_specifier( let user_preferences = UserPreferences::from_config_for_specifier(
&config, &config,
&Default::default(), &Default::default(),

View file

@ -1024,23 +1024,23 @@ fn lsp_import_attributes() {
client.initialize(|builder| { client.initialize(|builder| {
builder.set_import_map("data:application/json;utf8,{\"imports\": { \"example\": \"https://deno.land/x/example/mod.ts\" }}"); builder.set_import_map("data:application/json;utf8,{\"imports\": { \"example\": \"https://deno.land/x/example/mod.ts\" }}");
}); });
client.change_configuration(json!({
client.did_open_with_config( "deno": {
json!({
"textDocument": {
"uri": "file:///a/test.json",
"languageId": "json",
"version": 1,
"text": "{\"a\":1}"
}
}),
&json!({ "deno": {
"enable": true, "enable": true,
"codeLens": { "codeLens": {
"test": true "test": true,
} },
} }), },
); }));
client.did_open(json!({
"textDocument": {
"uri": "file:///a/test.json",
"languageId": "json",
"version": 1,
"text": "{\"a\":1}",
},
}));
let diagnostics = client.did_open(json!({ let diagnostics = client.did_open(json!({
"textDocument": { "textDocument": {
@ -1380,17 +1380,15 @@ fn lsp_hover_disabled() {
client.initialize(|builder| { client.initialize(|builder| {
builder.set_deno_enable(false); builder.set_deno_enable(false);
}); });
client.did_open_with_config( client.change_configuration(json!({ "deno": { "enable": false } }));
json!({ client.did_open(json!({
"textDocument": { "textDocument": {
"uri": "file:///a/file.ts", "uri": "file:///a/file.ts",
"languageId": "typescript", "languageId": "typescript",
"version": 1, "version": 1,
"text": "console.log(Date.now());\n" "text": "console.log(Date.now());\n",
} },
}), }));
&json!({ "deno": { "enable": false } }),
);
let res = client.write_request( let res = client.write_request(
"textDocument/hover", "textDocument/hover",
@ -3794,24 +3792,22 @@ fn lsp_code_lens_test_disabled() {
"test": false "test": false
}))); })));
}); });
client client.change_configuration(json!({
.did_open_with_config( "deno": {
json!({ "enable": true,
"textDocument": { "codeLens": {
"uri": "file:///a/file.ts", "test": false,
"languageId": "typescript", },
"version": 1, },
"text": "const { test } = Deno;\nconst { test: test2 } = Deno;\nconst test3 = Deno.test;\n\nDeno.test(\"test a\", () => {});\nDeno.test({\n name: \"test b\",\n fn() {},\n});\ntest({\n name: \"test c\",\n fn() {},\n});\ntest(\"test d\", () => {});\ntest2({\n name: \"test e\",\n fn() {},\n});\ntest2(\"test f\", () => {});\ntest3({\n name: \"test g\",\n fn() {},\n});\ntest3(\"test h\", () => {});\n" }));
} client.did_open(json!({
}), "textDocument": {
// disable test code lens "uri": "file:///a/file.ts",
&json!({ "deno": { "languageId": "typescript",
"enable": true, "version": 1,
"codeLens": { "text": "const { test } = Deno;\nconst { test: test2 } = Deno;\nconst test3 = Deno.test;\n\nDeno.test(\"test a\", () => {});\nDeno.test({\n name: \"test b\",\n fn() {},\n});\ntest({\n name: \"test c\",\n fn() {},\n});\ntest(\"test d\", () => {});\ntest2({\n name: \"test e\",\n fn() {},\n});\ntest2(\"test f\", () => {});\ntest3({\n name: \"test g\",\n fn() {},\n});\ntest3(\"test h\", () => {});\n"
"test": false },
} }));
} }),
);
let res = client.write_request( let res = client.write_request(
"textDocument/codeLens", "textDocument/codeLens",
json!({ json!({
@ -3820,7 +3816,7 @@ fn lsp_code_lens_test_disabled() {
} }
}), }),
); );
assert_eq!(res, json!([])); assert_eq!(res, json!(null));
client.shutdown(); client.shutdown();
} }
@ -4898,22 +4894,12 @@ fn lsp_cache_on_save() {
); );
let mut client = context.new_lsp_command().build(); let mut client = context.new_lsp_command().build();
client.initialize_default(); client.initialize_default();
client.write_notification( client.change_configuration(json!({
"workspace/didChangeConfiguration",
json!({
"settings": {}
}),
);
let settings = json!({
"deno": { "deno": {
"enable": true, "enable": true,
"cacheOnSave": true, "cacheOnSave": true,
}, },
}); }));
// one for the workspace
client.handle_configuration_request(&settings);
// one for the specifier
client.handle_configuration_request(&settings);
let diagnostics = client.did_open(json!({ let diagnostics = client.did_open(json!({
"textDocument": { "textDocument": {
@ -5592,23 +5578,16 @@ fn lsp_quote_style_from_workspace_settings() {
); );
let mut client = context.new_lsp_command().build(); let mut client = context.new_lsp_command().build();
client.initialize_default(); client.initialize_default();
client.write_notification( client.change_configuration(json!({
"workspace/didChangeConfiguration", "deno": {
json!({ "enable": true,
"settings": {} },
}),
);
let settings = json!({
"typescript": { "typescript": {
"preferences": { "preferences": {
"quoteStyle": "single", "quoteStyle": "single",
}, },
}, },
}); }));
// one for the workspace
client.handle_configuration_request(&settings);
// one for the specifier
client.handle_configuration_request(&settings);
let code_action_params = json!({ let code_action_params = json!({
"textDocument": { "textDocument": {
@ -5792,7 +5771,7 @@ fn lsp_code_actions_deadlock() {
let large_file_text = let large_file_text =
fs::read_to_string(testdata_path().join("lsp").join("large_file.txt")) fs::read_to_string(testdata_path().join("lsp").join("large_file.txt"))
.unwrap(); .unwrap();
client.did_open_raw(json!({ client.did_open(json!({
"textDocument": { "textDocument": {
"uri": "file:///a/file.ts", "uri": "file:///a/file.ts",
"languageId": "javascript", "languageId": "javascript",
@ -5800,7 +5779,6 @@ fn lsp_code_actions_deadlock() {
"text": large_file_text, "text": large_file_text,
} }
})); }));
client.handle_configuration_request(&json!({ "deno": { "enable": true } }));
client.write_request( client.write_request(
"textDocument/semanticTokens/full", "textDocument/semanticTokens/full",
json!({ json!({
@ -5809,7 +5787,6 @@ fn lsp_code_actions_deadlock() {
} }
}), }),
); );
client.read_diagnostics();
client.write_notification( client.write_notification(
"textDocument/didChange", "textDocument/didChange",
json!({ json!({
@ -8901,17 +8878,11 @@ fn lsp_configuration_did_change() {
"text": "import * as a from \"http://localhost:4545/x/a@\"" "text": "import * as a from \"http://localhost:4545/x/a@\""
} }
})); }));
client.write_notification( client.change_configuration(json!({ "deno": {
"workspace/didChangeConfiguration",
json!({
"settings": {}
}),
);
let settings = json!({ "deno": {
"enable": true, "enable": true,
"codeLens": { "codeLens": {
"implementations": true, "implementations": true,
"references": true "references": true,
}, },
"importMap": null, "importMap": null,
"lint": true, "lint": true,
@ -8922,16 +8893,12 @@ fn lsp_configuration_did_change() {
"paths": true, "paths": true,
"imports": { "imports": {
"hosts": { "hosts": {
"http://localhost:4545/": true "http://localhost:4545/": true,
} },
} },
}, },
"unstable": false "unstable": false,
} }); } }));
// one for the workspace
client.handle_configuration_request(&settings);
// one for the specifier
client.handle_configuration_request(&settings);
let list = client.get_completion_list( let list = client.get_completion_list(
"file:///a/file.ts", "file:///a/file.ts",
@ -8997,13 +8964,7 @@ fn lsp_completions_complete_function_calls() {
"text": "[]." "text": "[]."
} }
})); }));
client.write_notification( client.change_configuration(json!({
"workspace/didChangeConfiguration",
json!({
"settings": {}
}),
);
let settings = json!({
"deno": { "deno": {
"enable": true, "enable": true,
}, },
@ -9012,11 +8973,7 @@ fn lsp_completions_complete_function_calls() {
"completeFunctionCalls": true, "completeFunctionCalls": true,
}, },
}, },
}); }));
// one for the workspace
client.handle_configuration_request(&settings);
// one for the specifier
client.handle_configuration_request(&settings);
let list = client.get_completion_list( let list = client.get_completion_list(
"file:///a/file.ts", "file:///a/file.ts",
@ -10099,22 +10056,12 @@ fn lsp_node_modules_dir() {
"{ \"nodeModulesDir\": true, \"lock\": false }\n", "{ \"nodeModulesDir\": true, \"lock\": false }\n",
); );
let refresh_config = |client: &mut LspClient| { let refresh_config = |client: &mut LspClient| {
client.write_notification( client.change_configuration(json!({ "deno": {
"workspace/didChangeConfiguration",
json!({
"settings": {
"enable": true,
"config": "./deno.json",
}
}),
);
let settings = json!({ "deno": {
"enable": true, "enable": true,
"config": "./deno.json", "config": "./deno.json",
"codeLens": { "codeLens": {
"implementations": true, "implementations": true,
"references": true "references": true,
}, },
"importMap": null, "importMap": null,
"lint": false, "lint": false,
@ -10123,14 +10070,10 @@ fn lsp_node_modules_dir() {
"completeFunctionCalls": false, "completeFunctionCalls": false,
"names": true, "names": true,
"paths": true, "paths": true,
"imports": {} "imports": {},
}, },
"unstable": false "unstable": false,
} }); } }));
// one for the workspace
client.handle_configuration_request(&settings);
// one for the specifier
client.handle_configuration_request(&settings);
}; };
refresh_config(&mut client); refresh_config(&mut client);
@ -10228,22 +10171,12 @@ fn lsp_vendor_dir() {
"{ \"vendor\": true, \"lock\": false }\n", "{ \"vendor\": true, \"lock\": false }\n",
); );
let refresh_config = |client: &mut LspClient| { let refresh_config = |client: &mut LspClient| {
client.write_notification( client.change_configuration(json!({ "deno": {
"workspace/didChangeConfiguration",
json!({
"settings": {
"enable": true,
"config": "./deno.json",
}
}),
);
let settings = json!({ "deno": {
"enable": true, "enable": true,
"config": "./deno.json", "config": "./deno.json",
"codeLens": { "codeLens": {
"implementations": true, "implementations": true,
"references": true "references": true,
}, },
"importMap": null, "importMap": null,
"lint": false, "lint": false,
@ -10252,14 +10185,10 @@ fn lsp_vendor_dir() {
"completeFunctionCalls": false, "completeFunctionCalls": false,
"names": true, "names": true,
"paths": true, "paths": true,
"imports": {} "imports": {},
}, },
"unstable": false "unstable": false,
} }); } }));
// one for the workspace
client.handle_configuration_request(&settings);
// one for the specifier
client.handle_configuration_request(&settings);
}; };
refresh_config(&mut client); refresh_config(&mut client);

View file

@ -210,7 +210,22 @@ pub struct InitializeParamsBuilder {
impl InitializeParamsBuilder { impl InitializeParamsBuilder {
#[allow(clippy::new_without_default)] #[allow(clippy::new_without_default)]
pub fn new() -> Self { pub fn new(config: Value) -> Self {
let mut config_as_options = json!({});
if let Some(object) = config.as_object() {
if let Some(deno) = object.get("deno") {
if let Some(deno) = deno.as_object() {
config_as_options = json!(deno.clone());
}
}
let config_as_options = config_as_options.as_object_mut().unwrap();
if let Some(typescript) = object.get("typescript") {
config_as_options.insert("typescript".to_string(), typescript.clone());
}
if let Some(javascript) = object.get("javascript") {
config_as_options.insert("javascript".to_string(), javascript.clone());
}
}
Self { Self {
params: InitializeParams { params: InitializeParams {
process_id: None, process_id: None,
@ -219,38 +234,7 @@ impl InitializeParamsBuilder {
version: Some("1.0.0".to_string()), version: Some("1.0.0".to_string()),
}), }),
root_uri: None, root_uri: None,
initialization_options: Some(json!({ initialization_options: Some(config_as_options),
"enableBuiltinCommands": true,
"enable": true,
"cache": null,
"certificateStores": null,
"codeLens": {
"implementations": true,
"references": true,
"test": true
},
"config": null,
"importMap": null,
"lint": true,
"suggest": {
"autoImports": true,
"completeFunctionCalls": false,
"names": true,
"paths": true,
"imports": {
"hosts": {}
}
},
"testing": {
"args": [
"--allow-all"
],
"enable": true
},
"tlsCertificate": null,
"unsafelyIgnoreCertificateErrors": null,
"unstable": false
})),
capabilities: ClientCapabilities { capabilities: ClientCapabilities {
text_document: Some(TextDocumentClientCapabilities { text_document: Some(TextDocumentClientCapabilities {
code_action: Some(CodeActionClientCapabilities { code_action: Some(CodeActionClientCapabilities {
@ -686,21 +670,70 @@ impl LspClient {
) { ) {
self.initialize_with_config( self.initialize_with_config(
do_build, do_build,
json!({"deno":{ json!({ "deno": {
"enable": true "enableBuiltinCommands": true,
}}), "enable": true,
"cache": null,
"certificateStores": null,
"codeLens": {
"implementations": true,
"references": true,
"test": true,
},
"config": null,
"importMap": null,
"lint": true,
"suggest": {
"autoImports": true,
"completeFunctionCalls": false,
"names": true,
"paths": true,
"imports": {
"hosts": {},
},
},
"testing": {
"args": [
"--allow-all"
],
"enable": true,
},
"tlsCertificate": null,
"unsafelyIgnoreCertificateErrors": null,
"unstable": false,
} }),
) )
} }
pub fn initialize_with_config( pub fn initialize_with_config(
&mut self, &mut self,
do_build: impl Fn(&mut InitializeParamsBuilder), do_build: impl Fn(&mut InitializeParamsBuilder),
config: Value, mut config: Value,
) { ) {
let mut builder = InitializeParamsBuilder::new(); let mut builder = InitializeParamsBuilder::new(config.clone());
builder.set_root_uri(self.context.temp_dir().uri()); builder.set_root_uri(self.context.temp_dir().uri());
do_build(&mut builder); do_build(&mut builder);
let params: InitializeParams = builder.build(); let params: InitializeParams = builder.build();
// `config` must be updated to account for the builder changes.
// TODO(nayeemrmn): Remove config-related methods from builder.
if let Some(options) = &params.initialization_options {
if let Some(options) = options.as_object() {
if let Some(config) = config.as_object_mut() {
let mut deno = options.clone();
let typescript = options.get("typescript");
let javascript = options.get("javascript");
deno.remove("typescript");
deno.remove("javascript");
config.insert("deno".to_string(), json!(deno));
if let Some(typescript) = typescript {
config.insert("typescript".to_string(), typescript.clone());
}
if let Some(javascript) = javascript {
config.insert("javascript".to_string(), javascript.clone());
}
}
}
}
self.supports_workspace_configuration = match &params.capabilities.workspace self.supports_workspace_configuration = match &params.capabilities.workspace
{ {
Some(workspace) => workspace.configuration == Some(true), Some(workspace) => workspace.configuration == Some(true),
@ -710,23 +743,12 @@ impl LspClient {
self.write_notification("initialized", json!({})); self.write_notification("initialized", json!({}));
self.config = config; self.config = config;
if self.supports_workspace_configuration { if self.supports_workspace_configuration {
self.handle_configuration_request(&self.config.clone()); self.handle_configuration_request();
} }
} }
pub fn did_open(&mut self, params: Value) -> CollectedDiagnostics { pub fn did_open(&mut self, params: Value) -> CollectedDiagnostics {
self.did_open_with_config(params, &self.config.clone())
}
pub fn did_open_with_config(
&mut self,
params: Value,
config: &Value,
) -> CollectedDiagnostics {
self.did_open_raw(params); self.did_open_raw(params);
if self.supports_workspace_configuration {
self.handle_configuration_request(config);
}
self.read_diagnostics() self.read_diagnostics()
} }
@ -734,17 +756,33 @@ impl LspClient {
self.write_notification("textDocument/didOpen", params); self.write_notification("textDocument/didOpen", params);
} }
pub fn handle_configuration_request(&mut self, settings: &Value) { pub fn change_configuration(&mut self, config: Value) {
self.config = config;
if self.supports_workspace_configuration {
self.write_notification(
"workspace/didChangeConfiguration",
json!({ "settings": {} }),
);
self.handle_configuration_request();
} else {
self.write_notification(
"workspace/didChangeConfiguration",
json!({ "settings": &self.config }),
);
}
}
pub fn handle_configuration_request(&mut self) {
let (id, method, args) = self.read_request::<Value>(); let (id, method, args) = self.read_request::<Value>();
assert_eq!(method, "workspace/configuration"); assert_eq!(method, "workspace/configuration");
let params = args.as_ref().unwrap().as_object().unwrap(); let params = args.as_ref().unwrap().as_object().unwrap();
let items = params.get("items").unwrap().as_array().unwrap(); let items = params.get("items").unwrap().as_array().unwrap();
let settings_object = settings.as_object().unwrap(); let config_object = self.config.as_object().unwrap();
let mut result = vec![]; let mut result = vec![];
for item in items { for item in items {
let item = item.as_object().unwrap(); let item = item.as_object().unwrap();
let section = item.get("section").unwrap().as_str().unwrap(); let section = item.get("section").unwrap().as_str().unwrap();
result.push(settings_object.get(section).cloned().unwrap_or_default()); result.push(config_object.get(section).cloned().unwrap_or_default());
} }
self.write_response(id, result); self.write_response(id, result);
} }