1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-24 08:09:08 -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::bail;
use deno_core::error::AnyError;
use deno_core::serde_json;
use deno_core::serde_json::json;
use deno_core::unsync::spawn;
use tower_lsp::lsp_types as lsp;
use tower_lsp::lsp_types::ConfigurationItem;
use crate::lsp::repl::get_repl_workspace_settings;
use super::config::SpecifierSettings;
use super::config::WorkspaceSettings;
use super::config::SETTINGS_SECTION;
use super::lsp_custom;
@ -125,46 +124,11 @@ impl OutsideLockClient {
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(
&self,
) -> Result<WorkspaceSettings, AnyError> {
self.0.workspace_configuration().await
scopes: Vec<Option<lsp::Url>>,
) -> Result<Vec<WorkspaceSettings>, AnyError> {
self.0.workspace_configuration(scopes).await
}
pub async fn publish_diagnostics(
@ -201,13 +165,10 @@ trait ClientTrait: Send + Sync {
&self,
params: lsp_custom::DidChangeDenoConfigurationNotificationParams,
);
async fn specifier_configurations(
&self,
uris: Vec<lsp::Url>,
) -> Result<Vec<Result<SpecifierSettings, AnyError>>, AnyError>;
async fn workspace_configuration(
&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 register_capability(
&self,
@ -288,67 +249,50 @@ impl ClientTrait for TowerClient {
.await
}
async fn specifier_configurations(
async fn workspace_configuration(
&self,
uris: Vec<lsp::Url>,
) -> Result<Vec<Result<SpecifierSettings, AnyError>>, AnyError> {
scopes: Vec<Option<lsp::Url>>,
) -> Result<Vec<WorkspaceSettings>, AnyError> {
let config_response = self
.0
.configuration(
uris
.into_iter()
.map(|uri| ConfigurationItem {
scope_uri: Some(uri),
section: Some(SETTINGS_SECTION.to_string()),
})
.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![
scopes
.iter()
.flat_map(|scope_uri| {
vec![
ConfigurationItem {
scope_uri: None,
scope_uri: scope_uri.clone(),
section: Some(SETTINGS_SECTION.to_string()),
},
ConfigurationItem {
scope_uri: None,
scope_uri: scope_uri.clone(),
section: Some("javascript".to_string()),
},
ConfigurationItem {
scope_uri: None,
scope_uri: scope_uri.clone(),
section: Some("typescript".to_string()),
},
])
]
})
.collect(),
)
.await;
match config_response {
Ok(configs) => {
let mut configs = configs.into_iter();
let deno = serde_json::to_value(configs.next()).unwrap();
let javascript = serde_json::to_value(configs.next()).unwrap();
let typescript = serde_json::to_value(configs.next()).unwrap();
Ok(WorkspaceSettings::from_raw_settings(
let mut result = Vec::with_capacity(scopes.len());
for _ in 0..scopes.len() {
let deno = json!(configs.next());
let javascript = json!(configs.next());
let typescript = json!(configs.next());
result.push(WorkspaceSettings::from_raw_settings(
deno, javascript, typescript,
))
));
}
Ok(result)
}
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(
&self,
) -> Result<WorkspaceSettings, AnyError> {
Ok(get_repl_workspace_settings())
scopes: Vec<Option<lsp::Url>>,
) -> Result<Vec<WorkspaceSettings>, AnyError> {
Ok(vec![get_repl_workspace_settings(); scopes.len()])
}
async fn show_message(

View file

@ -408,7 +408,7 @@ fn collect_test(
config: &Config,
) -> Result<Vec<lsp::CodeLens>, AnyError> {
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 {
let mut collector =

View file

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

View file

@ -788,23 +788,24 @@ fn generate_lint_diagnostics(
let documents = snapshot
.documents
.documents(DocumentsFilter::OpenDiagnosable);
let workspace_settings = config.settings.workspace.clone();
let lint_rules = get_configured_rules(lint_options.rules.clone());
let mut diagnostics_vec = Vec::new();
if workspace_settings.lint {
for document in documents {
let settings =
config.workspace_settings_for_specifier(document.specifier());
if !settings.lint {
continue;
}
// 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(),
@ -819,7 +820,6 @@ fn generate_lint_diagnostics(
},
});
}
}
diagnostics_vec
}
@ -1442,7 +1442,6 @@ mod tests {
use crate::cache::RealDenoCacheEnv;
use crate::lsp::config::ConfigSnapshot;
use crate::lsp::config::Settings;
use crate::lsp::config::SpecifierSettings;
use crate::lsp::config::WorkspaceSettings;
use crate::lsp::documents::Documents;
use crate::lsp::documents::LanguageId;
@ -1497,7 +1496,7 @@ mod tests {
let root_uri = resolve_url("file:///").unwrap();
ConfigSnapshot {
settings: Settings {
workspace: WorkspaceSettings {
unscoped: WorkspaceSettings {
enable: Some(true),
lint: true,
..Default::default()
@ -1529,7 +1528,6 @@ mod tests {
#[tokio::test]
async fn test_enabled_then_disabled_specifier() {
let temp_dir = TempDir::new();
let specifier = ModuleSpecifier::parse("file:///a.ts").unwrap();
let (snapshot, cache_location) = setup(
&temp_dir,
&[(
@ -1578,15 +1576,10 @@ let c: number = "a";
// now test disabled specifier
{
let mut disabled_config = mock_config();
disabled_config.settings.specifiers.insert(
specifier.clone(),
SpecifierSettings {
disabled_config.settings.unscoped = WorkspaceSettings {
enable: Some(false),
disable_paths: vec![],
enable_paths: None,
code_lens: Default::default(),
},
);
..Default::default()
};
let diagnostics = generate_lint_diagnostics(
&snapshot,

View file

@ -23,6 +23,7 @@ use import_map::ImportMap;
use indexmap::IndexSet;
use log::error;
use serde_json::from_value;
use std::collections::BTreeMap;
use std::collections::HashMap;
use std::collections::HashSet;
use std::env;
@ -80,7 +81,6 @@ use super::tsc::AssetsSnapshot;
use super::tsc::GetCompletionDetailsArgs;
use super::tsc::TsServer;
use super::urls;
use super::urls::LspClientUrl;
use crate::args::get_root_cert_store;
use crate::args::package_json;
use crate::args::resolve_import_map_from_specifier;
@ -403,68 +403,42 @@ impl LanguageServer {
}
}
pub async fn refresh_specifiers_from_client(&self) -> bool {
let (client, specifiers) = {
pub async fn refresh_configuration(&self) {
let (client, folders, capable) = {
let ls = self.0.read().await;
let specifiers = if ls.config.client_capabilities.workspace_configuration
{
let root_capacity = std::cmp::max(ls.config.workspace_folders.len(), 1);
let config_specifiers = ls.config.get_specifiers();
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)
};
let mut touched = false;
if let Some(specifiers) = specifiers {
let configs_result = client
.when_outside_lsp_lock()
.specifier_configurations(
specifiers
.iter()
.map(|(_, client_uri)| client_uri.clone())
.collect(),
(
ls.client.clone(),
ls.config.workspace_folders.clone(),
ls.config.client_capabilities.workspace_configuration,
)
};
if capable {
let mut scopes = Vec::with_capacity(folders.len() + 1);
scopes.push(None);
for (_, folder) in &folders {
scopes.push(Some(folder.uri.clone()));
}
let configs = client
.when_outside_lsp_lock()
.workspace_configuration(scopes)
.await;
if let Ok(configs) = configs {
if configs.len() != folders.len() + 1 {
lsp_warn!("Incorrect number of configurations received.");
return;
}
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;
if let Ok(configs) = configs_result {
for (value, internal_uri) in
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;
ls.config
.set_workspace_settings(unscoped, Some(by_workspace_folder));
}
}
Err(err) => {
error!("{}", err);
}
}
}
}
}
touched
}
}
@ -1182,6 +1156,7 @@ impl Inner {
if let Some(options) = params.initialization_options {
self.config.set_workspace_settings(
WorkspaceSettings::from_initialization_options(options),
None,
);
}
if let Some(folders) = params.workspace_folders {
@ -1391,14 +1366,10 @@ impl Inner {
async fn did_change_configuration(
&mut self,
client_workspace_config: Option<WorkspaceSettings>,
params: DidChangeConfigurationParams,
) {
let maybe_config =
if self.config.client_capabilities.workspace_configuration {
client_workspace_config
} else {
params.settings.as_object().map(|settings| {
if !self.config.client_capabilities.workspace_configuration {
let config = params.settings.as_object().map(|settings| {
let deno =
serde_json::to_value(settings.get(SETTINGS_SECTION)).unwrap();
let javascript =
@ -1406,12 +1377,11 @@ impl Inner {
let typescript =
serde_json::to_value(settings.get("typescript")).unwrap();
WorkspaceSettings::from_raw_settings(deno, javascript, typescript)
})
};
if let Some(settings) = maybe_config {
self.config.set_workspace_settings(settings);
});
if let Some(settings) = config {
self.config.set_workspace_settings(settings, None);
}
};
self.update_debug_flag();
if let Err(err) = self.update_cache().await {
@ -2148,8 +2118,7 @@ impl Inner {
.normalize_url(&params.text_document.uri, LspUrlKind::File);
if !self.is_diagnosable(&specifier)
|| !self.config.specifier_enabled(&specifier)
|| !(self.config.workspace_settings().enabled_code_lens()
|| self.config.specifier_code_lens_test(&specifier))
|| !self.config.enabled_code_lens_for_specifier(&specifier)
{
return Ok(None);
}
@ -2385,10 +2354,8 @@ impl Inner {
&params.text_document_position.text_document.uri,
LspUrlKind::File,
);
let language_settings = self
.config
.workspace_settings()
.language_settings_for_specifier(&specifier);
let language_settings =
self.config.language_settings_for_specifier(&specifier);
if !self.is_diagnosable(&specifier)
|| !self.config.specifier_enabled(&specifier)
|| !language_settings.map(|s| s.suggest.enabled).unwrap_or(true)
@ -2457,7 +2424,6 @@ impl Inner {
line_index,
&self
.config
.workspace_settings()
.language_settings_for_specifier(&specifier)
.cloned()
.unwrap_or_default()
@ -2992,7 +2958,6 @@ impl Inner {
);
let options = self
.config
.workspace_settings()
.language_settings_for_specifier(&old_specifier)
.map(|s| s.update_imports_on_file_move.clone())
.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;
@ -3212,60 +3177,19 @@ impl tower_lsp::LanguageServer for LanguageServer {
return;
}
let (client, client_uri, specifier, should_get_specifier_settings) = {
let mut inner = self.0.write().await;
let client = inner.client.clone();
let client_uri = LspClientUrl::new(params.text_document.uri.clone());
let specifier = inner
.url_map
.normalize_url(client_uri.as_url(), LspUrlKind::File);
.normalize_url(&params.text_document.uri, LspUrlKind::File);
let document = inner.did_open(&specifier, params).await;
let should_get_specifier_settings =
!inner.config.has_specifier_settings(&specifier)
&& inner.config.client_capabilities.workspace_configuration;
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();
}
}
}
async fn did_change(&self, params: DidChangeTextDocumentParams) {
self.0.write().await.did_change(params).await
@ -3277,7 +3201,10 @@ impl tower_lsp::LanguageServer for LanguageServer {
let mut inner = self.0.write().await;
let specifier = inner.url_map.normalize_url(uri, LspUrlKind::File);
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.diagnostics_state.has_no_cache_diagnostics(&specifier)
{
@ -3310,47 +3237,17 @@ impl tower_lsp::LanguageServer for LanguageServer {
&self,
params: DidChangeConfigurationParams,
) {
let (mark, has_workspace_capability, client) = {
let mark = {
let inner = self.0.read().await;
(
inner
.performance
.mark("did_change_configuration", Some(&params)),
inner.config.client_capabilities.workspace_configuration,
inner.client.clone(),
)
.mark("did_change_configuration", Some(&params))
};
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;
inner
.did_change_configuration(client_workspace_config, params)
.await;
inner.did_change_configuration(params).await;
inner.performance.measure(mark);
}
@ -3374,7 +3271,8 @@ impl tower_lsp::LanguageServer for LanguageServer {
(ls.performance.clone(), mark)
};
if self.refresh_specifiers_from_client().await {
self.refresh_configuration().await;
{
let mut ls = self.0.write().await;
ls.refresh_documents_config().await;
ls.diagnostics_server.invalidate_all();
@ -3681,10 +3579,7 @@ impl Inner {
.normalize_url(&params.text_document.uri, LspUrlKind::File);
if !self.is_diagnosable(&specifier)
|| !self.config.specifier_enabled(&specifier)
|| !self
.config
.workspace_settings()
.enabled_inlay_hints(&specifier)
|| !self.config.enabled_inlay_hints_for_specifier(&specifier)
{
return Ok(None);
}

View file

@ -4215,9 +4215,8 @@ impl UserPreferences {
use_label_details_in_completion_entries: Some(true),
..Default::default()
};
let Some(language_settings) = config
.workspace_settings()
.language_settings_for_specifier(specifier)
let Some(language_settings) =
config.language_settings_for_specifier(specifier)
else {
return base_preferences;
};
@ -5312,7 +5311,7 @@ mod tests {
.variable_types
.suppress_when_type_matches_name = true;
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(
&config,
&Default::default(),

View file

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

View file

@ -210,7 +210,22 @@ pub struct InitializeParamsBuilder {
impl InitializeParamsBuilder {
#[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 {
params: InitializeParams {
process_id: None,
@ -219,38 +234,7 @@ impl InitializeParamsBuilder {
version: Some("1.0.0".to_string()),
}),
root_uri: None,
initialization_options: Some(json!({
"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
})),
initialization_options: Some(config_as_options),
capabilities: ClientCapabilities {
text_document: Some(TextDocumentClientCapabilities {
code_action: Some(CodeActionClientCapabilities {
@ -687,7 +671,36 @@ impl LspClient {
self.initialize_with_config(
do_build,
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,
} }),
)
}
@ -695,12 +708,32 @@ impl LspClient {
pub fn initialize_with_config(
&mut self,
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());
do_build(&mut builder);
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
{
Some(workspace) => workspace.configuration == Some(true),
@ -710,23 +743,12 @@ impl LspClient {
self.write_notification("initialized", json!({}));
self.config = config;
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 {
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);
if self.supports_workspace_configuration {
self.handle_configuration_request(config);
}
self.read_diagnostics()
}
@ -734,17 +756,33 @@ impl LspClient {
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>();
assert_eq!(method, "workspace/configuration");
let params = args.as_ref().unwrap().as_object().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![];
for item in items {
let item = item.as_object().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);
}