1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-01-18 11:53:59 -05:00

fix(lsp): avoid calling client while holding lock (#18197)

This commit is contained in:
David Sherret 2023-03-15 10:34:23 -04:00 committed by GitHub
parent 2ca1607027
commit 7070b8ed50
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 674 additions and 575 deletions

View file

@ -1,13 +1,11 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc; use std::sync::Arc;
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::futures::future;
use deno_core::serde_json; use deno_core::serde_json;
use deno_core::serde_json::Value; use deno_core::serde_json::Value;
use tower_lsp::lsp_types as lsp; use tower_lsp::lsp_types as lsp;
@ -45,24 +43,57 @@ impl Client {
Self(Arc::new(ReplClient)) Self(Arc::new(ReplClient))
} }
pub async fn publish_diagnostics( /// Gets additional methods that should only be called outside
&self, /// the LSP's lock to prevent deadlocking scenarios.
uri: lsp::Url, pub fn when_outside_lsp_lock(&self) -> OutsideLockClient {
diags: Vec<lsp::Diagnostic>, OutsideLockClient(self.0.clone())
version: Option<i32>,
) {
self.0.publish_diagnostics(uri, diags, version).await;
} }
pub async fn send_registry_state_notification( pub fn send_registry_state_notification(
&self, &self,
params: lsp_custom::RegistryStateNotificationParams, params: lsp_custom::RegistryStateNotificationParams,
) { ) {
self.0.send_registry_state_notification(params).await; // do on a task in case the caller currently is in the lsp lock
let client = self.0.clone();
tokio::task::spawn(async move {
client.send_registry_state_notification(params).await;
});
} }
pub fn send_test_notification(&self, params: TestingNotification) { pub fn send_test_notification(&self, params: TestingNotification) {
self.0.send_test_notification(params); // do on a task in case the caller currently is in the lsp lock
let client = self.0.clone();
tokio::task::spawn(async move {
client.send_test_notification(params).await;
});
}
pub fn show_message(
&self,
message_type: lsp::MessageType,
message: impl std::fmt::Display,
) {
// do on a task in case the caller currently is in the lsp lock
let client = self.0.clone();
let message = message.to_string();
tokio::task::spawn(async move {
client.show_message(message_type, message).await;
});
}
}
/// DANGER: The methods on this client should only be called outside
/// the LSP's lock. The reason is you never want to call into the client
/// while holding the lock because the client might call back into the
/// server and cause a deadlock.
pub struct OutsideLockClient(Arc<dyn ClientTrait>);
impl OutsideLockClient {
pub async fn register_capability(
&self,
registrations: Vec<lsp::Registration>,
) -> Result<(), AnyError> {
self.0.register_capability(registrations).await
} }
pub async fn specifier_configurations( pub async fn specifier_configurations(
@ -100,211 +131,185 @@ impl Client {
self.0.workspace_configuration().await self.0.workspace_configuration().await
} }
pub async fn show_message( pub async fn publish_diagnostics(
&self, &self,
message_type: lsp::MessageType, uri: lsp::Url,
message: impl std::fmt::Display, diags: Vec<lsp::Diagnostic>,
version: Option<i32>,
) { ) {
self self.0.publish_diagnostics(uri, diags, version).await;
.0
.show_message(message_type, format!("{message}"))
.await
}
pub async fn register_capability(
&self,
registrations: Vec<lsp::Registration>,
) -> Result<(), AnyError> {
self.0.register_capability(registrations).await
} }
} }
type AsyncReturn<T> = Pin<Box<dyn Future<Output = T> + 'static + Send>>; #[async_trait]
trait ClientTrait: Send + Sync { trait ClientTrait: Send + Sync {
fn publish_diagnostics( async fn publish_diagnostics(
&self, &self,
uri: lsp::Url, uri: lsp::Url,
diagnostics: Vec<lsp::Diagnostic>, diagnostics: Vec<lsp::Diagnostic>,
version: Option<i32>, version: Option<i32>,
) -> AsyncReturn<()>; );
fn send_registry_state_notification( async fn send_registry_state_notification(
&self, &self,
params: lsp_custom::RegistryStateNotificationParams, params: lsp_custom::RegistryStateNotificationParams,
) -> AsyncReturn<()>; );
fn send_test_notification(&self, params: TestingNotification); async fn send_test_notification(&self, params: TestingNotification);
fn specifier_configurations( async fn specifier_configurations(
&self, &self,
uris: Vec<lsp::Url>, uris: Vec<lsp::Url>,
) -> AsyncReturn<Result<Vec<Result<SpecifierSettings, AnyError>>, AnyError>>; ) -> Result<Vec<Result<SpecifierSettings, AnyError>>, AnyError>;
fn workspace_configuration(&self) -> AsyncReturn<Result<Value, AnyError>>; async fn workspace_configuration(&self) -> Result<Value, AnyError>;
fn show_message( async fn show_message(&self, message_type: lsp::MessageType, text: String);
&self, async fn register_capability(
message_type: lsp::MessageType,
text: String,
) -> AsyncReturn<()>;
fn register_capability(
&self, &self,
registrations: Vec<lsp::Registration>, registrations: Vec<lsp::Registration>,
) -> AsyncReturn<Result<(), AnyError>>; ) -> Result<(), AnyError>;
} }
#[derive(Clone)] #[derive(Clone)]
struct TowerClient(tower_lsp::Client); struct TowerClient(tower_lsp::Client);
#[async_trait]
impl ClientTrait for TowerClient { impl ClientTrait for TowerClient {
fn publish_diagnostics( async fn publish_diagnostics(
&self, &self,
uri: lsp::Url, uri: lsp::Url,
diagnostics: Vec<lsp::Diagnostic>, diagnostics: Vec<lsp::Diagnostic>,
version: Option<i32>, version: Option<i32>,
) -> AsyncReturn<()> { ) {
let client = self.0.clone(); self.0.publish_diagnostics(uri, diagnostics, version).await
Box::pin(async move {
client.publish_diagnostics(uri, diagnostics, version).await
})
} }
fn send_registry_state_notification( async fn send_registry_state_notification(
&self, &self,
params: lsp_custom::RegistryStateNotificationParams, params: lsp_custom::RegistryStateNotificationParams,
) -> AsyncReturn<()> { ) {
let client = self.0.clone(); self
Box::pin(async move { .0
client .send_notification::<lsp_custom::RegistryStateNotification>(params)
.send_notification::<lsp_custom::RegistryStateNotification>(params) .await
.await
})
} }
fn send_test_notification(&self, notification: TestingNotification) { async fn send_test_notification(&self, notification: TestingNotification) {
let client = self.0.clone(); match notification {
tokio::task::spawn(async move { TestingNotification::Module(params) => {
match notification { self
TestingNotification::Module(params) => { .0
client .send_notification::<testing_lsp_custom::TestModuleNotification>(
.send_notification::<testing_lsp_custom::TestModuleNotification>(
params,
)
.await
}
TestingNotification::DeleteModule(params) => client
.send_notification::<testing_lsp_custom::TestModuleDeleteNotification>(
params, params,
) )
.await, .await
TestingNotification::Progress(params) => client }
TestingNotification::DeleteModule(params) => self
.0
.send_notification::<testing_lsp_custom::TestModuleDeleteNotification>(
params,
)
.await,
TestingNotification::Progress(params) => {
self
.0
.send_notification::<testing_lsp_custom::TestRunProgressNotification>( .send_notification::<testing_lsp_custom::TestRunProgressNotification>(
params, params,
) )
.await, .await
} }
}); }
} }
fn specifier_configurations( async fn specifier_configurations(
&self, &self,
uris: Vec<lsp::Url>, uris: Vec<lsp::Url>,
) -> AsyncReturn<Result<Vec<Result<SpecifierSettings, AnyError>>, AnyError>> ) -> Result<Vec<Result<SpecifierSettings, AnyError>>, AnyError> {
{ let config_response = self
let client = self.0.clone(); .0
Box::pin(async move { .configuration(
let config_response = client uris
.configuration(
uris
.into_iter()
.map(|uri| ConfigurationItem {
scope_uri: Some(uri),
section: Some(SETTINGS_SECTION.to_string()),
})
.collect(),
)
.await?;
Ok(
config_response
.into_iter() .into_iter()
.map(|value| { .map(|uri| ConfigurationItem {
serde_json::from_value::<SpecifierSettings>(value).map_err(|err| { scope_uri: Some(uri),
anyhow!("Error converting specifier settings: {}", err) section: Some(SETTINGS_SECTION.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(),
)
} }
fn workspace_configuration(&self) -> AsyncReturn<Result<Value, AnyError>> { async fn workspace_configuration(&self) -> Result<Value, AnyError> {
let client = self.0.clone(); let config_response = self
Box::pin(async move { .0
let config_response = client .configuration(vec![ConfigurationItem {
.configuration(vec![ConfigurationItem { scope_uri: None,
scope_uri: None, section: Some(SETTINGS_SECTION.to_string()),
section: Some(SETTINGS_SECTION.to_string()), }])
}]) .await;
.await; match config_response {
match config_response { Ok(value_vec) => match value_vec.get(0).cloned() {
Ok(value_vec) => match value_vec.get(0).cloned() { Some(value) => Ok(value),
Some(value) => Ok(value), None => bail!("Missing response workspace configuration."),
None => bail!("Missing response workspace configuration."), },
}, Err(err) => {
Err(err) => { bail!("Error getting workspace configuration: {}", err)
bail!("Error getting workspace configuration: {}", err)
}
} }
}) }
} }
fn show_message( async fn show_message(
&self, &self,
message_type: lsp::MessageType, message_type: lsp::MessageType,
message: String, message: String,
) -> AsyncReturn<()> { ) {
let client = self.0.clone(); self.0.show_message(message_type, message).await
Box::pin(async move { client.show_message(message_type, message).await })
} }
fn register_capability( async fn register_capability(
&self, &self,
registrations: Vec<lsp::Registration>, registrations: Vec<lsp::Registration>,
) -> AsyncReturn<Result<(), AnyError>> { ) -> Result<(), AnyError> {
let client = self.0.clone(); self
Box::pin(async move { .0
client .register_capability(registrations)
.register_capability(registrations) .await
.await .map_err(|err| anyhow!("{}", err))
.map_err(|err| anyhow!("{}", err))
})
} }
} }
#[derive(Clone)] #[derive(Clone)]
struct ReplClient; struct ReplClient;
#[async_trait]
impl ClientTrait for ReplClient { impl ClientTrait for ReplClient {
fn publish_diagnostics( async fn publish_diagnostics(
&self, &self,
_uri: lsp::Url, _uri: lsp::Url,
_diagnostics: Vec<lsp::Diagnostic>, _diagnostics: Vec<lsp::Diagnostic>,
_version: Option<i32>, _version: Option<i32>,
) -> AsyncReturn<()> { ) {
Box::pin(future::ready(()))
} }
fn send_registry_state_notification( async fn send_registry_state_notification(
&self, &self,
_params: lsp_custom::RegistryStateNotificationParams, _params: lsp_custom::RegistryStateNotificationParams,
) -> AsyncReturn<()> { ) {
Box::pin(future::ready(()))
} }
fn send_test_notification(&self, _params: TestingNotification) {} async fn send_test_notification(&self, _params: TestingNotification) {}
fn specifier_configurations( async fn specifier_configurations(
&self, &self,
uris: Vec<lsp::Url>, uris: Vec<lsp::Url>,
) -> AsyncReturn<Result<Vec<Result<SpecifierSettings, AnyError>>, AnyError>> ) -> Result<Vec<Result<SpecifierSettings, AnyError>>, AnyError> {
{
// all specifiers are enabled for the REPL // all specifiers are enabled for the REPL
let settings = uris let settings = uris
.into_iter() .into_iter()
@ -315,27 +320,24 @@ impl ClientTrait for ReplClient {
}) })
}) })
.collect(); .collect();
Box::pin(future::ready(Ok(settings))) Ok(settings)
} }
fn workspace_configuration(&self) -> AsyncReturn<Result<Value, AnyError>> { async fn workspace_configuration(&self) -> Result<Value, AnyError> {
Box::pin(future::ready(Ok( Ok(serde_json::to_value(get_repl_workspace_settings()).unwrap())
serde_json::to_value(get_repl_workspace_settings()).unwrap(),
)))
} }
fn show_message( async fn show_message(
&self, &self,
_message_type: lsp::MessageType, _message_type: lsp::MessageType,
_message: String, _message: String,
) -> AsyncReturn<()> { ) {
Box::pin(future::ready(()))
} }
fn register_capability( async fn register_capability(
&self, &self,
_registrations: Vec<lsp::Registration>, _registrations: Vec<lsp::Registration>,
) -> AsyncReturn<Result<(), AnyError>> { ) -> Result<(), AnyError> {
Box::pin(future::ready(Ok(()))) Ok(())
} }
} }

View file

@ -48,7 +48,7 @@ pub struct CompletionItemData {
async fn check_auto_config_registry( async fn check_auto_config_registry(
url_str: &str, url_str: &str,
config: &ConfigSnapshot, config: &ConfigSnapshot,
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
@ -78,14 +78,12 @@ async fn check_auto_config_registry(
// incompatible. // incompatible.
// TODO(@kitsonk) clean up protocol when doing v2 of suggestions // TODO(@kitsonk) clean up protocol when doing v2 of suggestions
if suggestions { if suggestions {
client client.send_registry_state_notification(
.send_registry_state_notification( lsp_custom::RegistryStateNotificationParams {
lsp_custom::RegistryStateNotificationParams { origin,
origin, suggestions,
suggestions, },
}, );
)
.await;
} }
} }
} }
@ -139,7 +137,7 @@ pub async fn get_import_completions(
specifier: &ModuleSpecifier, specifier: &ModuleSpecifier,
position: &lsp::Position, position: &lsp::Position,
config: &ConfigSnapshot, config: &ConfigSnapshot,
client: Client, client: &Client,
module_registries: &ModuleRegistry, module_registries: &ModuleRegistry,
documents: &Documents, documents: &Documents,
maybe_import_map: Option<Arc<ImportMap>>, maybe_import_map: Option<Arc<ImportMap>>,

View file

@ -1,8 +1,6 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
use super::client::Client;
use super::logging::lsp_log; use super::logging::lsp_log;
use crate::util::path::ensure_directory_specifier;
use crate::util::path::specifier_to_file_path; use crate::util::path::specifier_to_file_path;
use deno_core::error::AnyError; use deno_core::error::AnyError;
use deno_core::serde::Deserialize; use deno_core::serde::Deserialize;
@ -10,6 +8,7 @@ use deno_core::serde::Serialize;
use deno_core::serde_json; use deno_core::serde_json;
use deno_core::serde_json::Value; use deno_core::serde_json::Value;
use deno_core::ModuleSpecifier; use deno_core::ModuleSpecifier;
use lsp::Url;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
@ -226,7 +225,7 @@ impl Default for ImportCompletionSettings {
/// Deno language server specific settings that can be applied uniquely to a /// Deno language server specific settings that can be applied uniquely to a
/// specifier. /// specifier.
#[derive(Debug, Default, Clone, Deserialize)] #[derive(Debug, Default, Clone, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct SpecifierSettings { pub struct SpecifierSettings {
/// A flag that indicates if Deno is enabled for this specifier or not. /// A flag that indicates if Deno is enabled for this specifier or not.
@ -388,7 +387,7 @@ impl WorkspaceSettings {
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct ConfigSnapshot { pub struct ConfigSnapshot {
pub client_capabilities: ClientCapabilities, pub client_capabilities: ClientCapabilities,
pub enabled_paths: HashMap<String, Vec<String>>, pub enabled_paths: HashMap<Url, Vec<Url>>,
pub settings: Settings, pub settings: Settings,
} }
@ -396,42 +395,33 @@ impl ConfigSnapshot {
/// Determine if the provided specifier is enabled or not. /// Determine if the provided specifier is enabled or not.
pub fn specifier_enabled(&self, specifier: &ModuleSpecifier) -> bool { pub fn specifier_enabled(&self, specifier: &ModuleSpecifier) -> bool {
if !self.enabled_paths.is_empty() { if !self.enabled_paths.is_empty() {
let specifier_str = specifier.to_string(); let specifier_str = specifier.as_str();
for (workspace, enabled_paths) in self.enabled_paths.iter() { for (workspace, enabled_paths) in self.enabled_paths.iter() {
if specifier_str.starts_with(workspace) { if specifier_str.starts_with(workspace.as_str()) {
return enabled_paths return enabled_paths
.iter() .iter()
.any(|path| specifier_str.starts_with(path)); .any(|path| specifier_str.starts_with(path.as_str()));
} }
} }
} }
if let Some((_, SpecifierSettings { enable, .. })) = if let Some(settings) = self.settings.specifiers.get(specifier) {
self.settings.specifiers.get(specifier) settings.enable
{
*enable
} else { } else {
self.settings.workspace.enable self.settings.workspace.enable
} }
} }
} }
#[derive(Debug, Clone)]
pub struct SpecifierWithClientUri {
pub specifier: ModuleSpecifier,
pub client_uri: ModuleSpecifier,
}
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]
pub struct Settings { pub struct Settings {
pub specifiers: pub specifiers: BTreeMap<ModuleSpecifier, SpecifierSettings>,
BTreeMap<ModuleSpecifier, (ModuleSpecifier, SpecifierSettings)>,
pub workspace: WorkspaceSettings, pub workspace: WorkspaceSettings,
} }
#[derive(Debug)] #[derive(Debug)]
pub struct Config { pub struct Config {
pub client_capabilities: ClientCapabilities, pub client_capabilities: ClientCapabilities,
enabled_paths: HashMap<String, Vec<String>>, enabled_paths: HashMap<Url, Vec<Url>>,
pub root_uri: Option<ModuleSpecifier>, pub root_uri: Option<ModuleSpecifier>,
settings: Settings, settings: Settings,
pub workspace_folders: Option<Vec<(ModuleSpecifier, lsp::WorkspaceFolder)>>, pub workspace_folders: Option<Vec<(ModuleSpecifier, lsp::WorkspaceFolder)>>,
@ -478,12 +468,12 @@ impl Config {
pub fn specifier_enabled(&self, specifier: &ModuleSpecifier) -> bool { pub fn specifier_enabled(&self, specifier: &ModuleSpecifier) -> bool {
if !self.enabled_paths.is_empty() { if !self.enabled_paths.is_empty() {
let specifier_str = specifier.to_string(); let specifier_str = specifier.as_str();
for (workspace, enabled_paths) in self.enabled_paths.iter() { for (workspace, enabled_paths) in self.enabled_paths.iter() {
if specifier_str.starts_with(workspace) { if specifier_str.starts_with(workspace.as_str()) {
return enabled_paths return enabled_paths
.iter() .iter()
.any(|path| specifier_str.starts_with(path)); .any(|path| specifier_str.starts_with(path.as_str()));
} }
} }
} }
@ -491,7 +481,7 @@ impl Config {
.settings .settings
.specifiers .specifiers
.get(specifier) .get(specifier)
.map(|(_, s)| s.enable) .map(|settings| settings.enable)
.unwrap_or_else(|| self.settings.workspace.enable) .unwrap_or_else(|| self.settings.workspace.enable)
} }
@ -500,7 +490,7 @@ impl Config {
.settings .settings
.specifiers .specifiers
.get(specifier) .get(specifier)
.map(|(_, s)| s.code_lens.test) .map(|settings| settings.code_lens.test)
.unwrap_or_else(|| self.settings.workspace.code_lens.test); .unwrap_or_else(|| self.settings.workspace.code_lens.test);
value value
} }
@ -554,13 +544,15 @@ impl Config {
/// Given the configured workspaces or root URI and the their settings, /// Given the configured workspaces or root URI and the their settings,
/// update and resolve any paths that should be enabled /// update and resolve any paths that should be enabled
pub async fn update_enabled_paths(&mut self, client: Client) -> bool { pub fn update_enabled_paths(&mut self) -> bool {
if let Some(workspace_folders) = self.workspace_folders.clone() { if let Some(workspace_folders) = self.workspace_folders.clone() {
let mut touched = false; let mut touched = false;
for (workspace, folder) in workspace_folders { for (workspace, _) in workspace_folders {
if let Ok(settings) = client.specifier_configuration(&folder.uri).await if let Some(settings) = self.settings.specifiers.get(&workspace) {
{ if self.update_enabled_paths_entry(
if self.update_enabled_paths_entry(workspace, settings.enable_paths) { workspace,
settings.enable_paths.clone(),
) {
touched = true; touched = true;
} }
} }
@ -582,8 +574,6 @@ impl Config {
workspace: ModuleSpecifier, workspace: ModuleSpecifier,
enabled_paths: Vec<String>, enabled_paths: Vec<String>,
) -> bool { ) -> bool {
let workspace = ensure_directory_specifier(workspace);
let key = workspace.to_string();
let mut touched = false; let mut touched = false;
if !enabled_paths.is_empty() { if !enabled_paths.is_empty() {
if let Ok(workspace_path) = specifier_to_file_path(&workspace) { if let Ok(workspace_path) = specifier_to_file_path(&workspace) {
@ -592,7 +582,7 @@ impl Config {
let fs_path = workspace_path.join(path); let fs_path = workspace_path.join(path);
match ModuleSpecifier::from_file_path(fs_path) { match ModuleSpecifier::from_file_path(fs_path) {
Ok(path_uri) => { Ok(path_uri) => {
paths.push(path_uri.to_string()); paths.push(path_uri);
} }
Err(_) => { Err(_) => {
lsp_log!("Unable to resolve a file path for `deno.enablePath` from \"{}\" for workspace \"{}\".", path, workspace); lsp_log!("Unable to resolve a file path for `deno.enablePath` from \"{}\" for workspace \"{}\".", path, workspace);
@ -601,38 +591,33 @@ impl Config {
} }
if !paths.is_empty() { if !paths.is_empty() {
touched = true; touched = true;
self.enabled_paths.insert(key, paths); self.enabled_paths.insert(workspace.clone(), paths);
} }
} }
} else { } else {
touched = true; touched = true;
self.enabled_paths.remove(&key); self.enabled_paths.remove(&workspace);
} }
touched touched
} }
pub fn get_specifiers_with_client_uris(&self) -> Vec<SpecifierWithClientUri> { pub fn get_specifiers(&self) -> Vec<ModuleSpecifier> {
self self.settings.specifiers.keys().cloned().collect()
.settings
.specifiers
.iter()
.map(|(s, (u, _))| SpecifierWithClientUri {
specifier: s.clone(),
client_uri: u.clone(),
})
.collect()
} }
pub fn set_specifier_settings( pub fn set_specifier_settings(
&mut self, &mut self,
specifier: ModuleSpecifier, specifier: ModuleSpecifier,
client_uri: ModuleSpecifier,
settings: SpecifierSettings, settings: SpecifierSettings,
) { ) -> bool {
self if let Some(existing) = self.settings.specifiers.get(&specifier) {
.settings if *existing == settings {
.specifiers return false;
.insert(specifier, (client_uri, settings)); }
}
self.settings.specifiers.insert(specifier, settings);
true
} }
} }
@ -678,8 +663,8 @@ mod tests {
assert!(!config.specifier_enabled(&specifier_b)); assert!(!config.specifier_enabled(&specifier_b));
let mut enabled_paths = HashMap::new(); let mut enabled_paths = HashMap::new();
enabled_paths.insert( enabled_paths.insert(
"file:///project/".to_string(), Url::parse("file:///project/").unwrap(),
vec!["file:///project/worker/".to_string()], vec![Url::parse("file:///project/worker/").unwrap()],
); );
config.enabled_paths = enabled_paths; config.enabled_paths = enabled_paths;
assert!(config.specifier_enabled(&specifier_a)); assert!(config.specifier_enabled(&specifier_a));

View file

@ -88,6 +88,7 @@ impl DiagnosticsPublisher {
self self
.client .client
.when_outside_lsp_lock()
.publish_diagnostics(specifier, version_diagnostics.clone(), version) .publish_diagnostics(specifier, version_diagnostics.clone(), version)
.await; .await;
} }
@ -1177,14 +1178,11 @@ let c: number = "a";
let mut disabled_config = mock_config(); let mut disabled_config = mock_config();
disabled_config.settings.specifiers.insert( disabled_config.settings.specifiers.insert(
specifier.clone(), specifier.clone(),
( SpecifierSettings {
specifier.clone(), enable: false,
SpecifierSettings { enable_paths: Vec::new(),
enable: false, code_lens: Default::default(),
enable_paths: Vec::new(), },
code_lens: Default::default(),
},
),
); );
let diagnostics = generate_lint_diagnostics( let diagnostics = generate_lint_diagnostics(

View file

@ -1184,12 +1184,8 @@ impl Documents {
hasher.write_str(&import_map.to_json()); hasher.write_str(&import_map.to_json());
hasher.write_str(import_map.base_url().as_str()); hasher.write_str(import_map.base_url().as_str());
} }
if let Some(jsx_config) = maybe_jsx_config { hasher.write_hashable(&maybe_jsx_config);
hasher.write_hashable(&jsx_config); hasher.write_hashable(&maybe_package_json_deps);
}
if let Some(deps) = maybe_package_json_deps {
hasher.write_hashable(&deps);
}
hasher.finish() hasher.finish()
} }

View file

@ -75,6 +75,7 @@ use crate::cache::HttpCache;
use crate::file_fetcher::FileFetcher; use crate::file_fetcher::FileFetcher;
use crate::graph_util; use crate::graph_util;
use crate::http_util::HttpClient; use crate::http_util::HttpClient;
use crate::lsp::urls::LspUrlKind;
use crate::npm::create_npm_fs_resolver; use crate::npm::create_npm_fs_resolver;
use crate::npm::NpmCache; use crate::npm::NpmCache;
use crate::npm::NpmPackageResolver; use crate::npm::NpmPackageResolver;
@ -84,7 +85,6 @@ use crate::proc_state::ProcState;
use crate::tools::fmt::format_file; use crate::tools::fmt::format_file;
use crate::tools::fmt::format_parsed_source; use crate::tools::fmt::format_parsed_source;
use crate::util::fs::remove_dir_all_if_exists; use crate::util::fs::remove_dir_all_if_exists;
use crate::util::path::ensure_directory_specifier;
use crate::util::path::specifier_to_file_path; use crate::util::path::specifier_to_file_path;
use crate::util::progress_bar::ProgressBar; use crate::util::progress_bar::ProgressBar;
use crate::util::progress_bar::ProgressBarStyle; use crate::util::progress_bar::ProgressBarStyle;
@ -214,8 +214,7 @@ impl LanguageServer {
.read() .read()
.await .await
.client .client
.show_message(MessageType::WARNING, err) .show_message(MessageType::WARNING, err);
.await;
return Err(LspError::internal_error()); return Err(LspError::internal_error());
} }
} }
@ -233,8 +232,7 @@ impl LanguageServer {
.read() .read()
.await .await
.client .client
.show_message(MessageType::WARNING, err) .show_message(MessageType::WARNING, err);
.await;
} }
// do npm resolution in a write—we should have everything // do npm resolution in a write—we should have everything
// cached by this point anyway // cached by this point anyway
@ -323,6 +321,92 @@ impl LanguageServer {
None => Err(LspError::invalid_params("Missing parameters")), None => Err(LspError::invalid_params("Missing parameters")),
} }
} }
pub async fn refresh_specifiers_from_client(&self) -> bool {
let (client, specifiers) =
{
let ls = self.0.read().await;
let specifiers =
if ls.config.client_capabilities.workspace_configuration {
let root_capacity = match &ls.config.workspace_folders {
Some(folder) => folder.len(),
None => 1,
};
let config_specifiers = ls.config.get_specifiers();
let mut specifiers =
HashMap::with_capacity(root_capacity + config_specifiers.len());
match &ls.config.workspace_folders {
Some(entry) => {
for (specifier, folder) in entry {
specifiers.insert(specifier.clone(), folder.uri.clone());
}
}
None => {
if let Some(root_uri) = &ls.config.root_uri {
specifiers.insert(
root_uri.clone(),
ls.url_map.normalize_specifier(root_uri).unwrap(),
);
}
}
}
specifiers.extend(ls.config.get_specifiers().iter().map(|s| {
(s.clone(), ls.url_map.normalize_specifier(s).unwrap())
}));
Some(specifiers.into_iter().collect::<Vec<_>>())
} 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(),
)
.await;
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.1))
{
match value {
Ok(specifier_settings) => {
if ls
.config
.set_specifier_settings(internal_uri, specifier_settings)
{
touched = true;
}
}
Err(err) => {
error!("{}", err);
}
}
}
}
if ls.config.update_enabled_paths() {
touched = true;
}
if touched {
ls.refresh_documents_config();
ls.diagnostics_server.invalidate_all();
ls.send_diagnostics_update();
}
}
touched
}
} }
fn create_lsp_structs( fn create_lsp_structs(
@ -923,7 +1007,7 @@ impl Inner {
tsconfig.merge(&unstable_libs); tsconfig.merge(&unstable_libs);
} }
if let Err(err) = self.merge_user_tsconfig(&mut tsconfig) { if let Err(err) = self.merge_user_tsconfig(&mut tsconfig) {
self.client.show_message(MessageType::WARNING, err).await; self.client.show_message(MessageType::WARNING, err);
} }
let _ok: bool = self let _ok: bool = self
.ts_server .ts_server
@ -978,8 +1062,7 @@ impl Inner {
// sometimes this root uri may not have a trailing slash, so force it to // sometimes this root uri may not have a trailing slash, so force it to
self.config.root_uri = params self.config.root_uri = params
.root_uri .root_uri
.map(|s| self.url_map.normalize_url(&s)) .map(|s| self.url_map.normalize_url(&s, LspUrlKind::Folder));
.map(ensure_directory_specifier);
if let Some(value) = params.initialization_options { if let Some(value) = params.initialization_options {
self.config.set_workspace_settings(value).map_err(|err| { self.config.set_workspace_settings(value).map_err(|err| {
@ -990,7 +1073,12 @@ impl Inner {
self.config.workspace_folders = params.workspace_folders.map(|folders| { self.config.workspace_folders = params.workspace_folders.map(|folders| {
folders folders
.into_iter() .into_iter()
.map(|folder| (self.url_map.normalize_url(&folder.uri), folder)) .map(|folder| {
(
self.url_map.normalize_url(&folder.uri, LspUrlKind::Folder),
folder,
)
})
.collect() .collect()
}); });
self.config.update_capabilities(&params.capabilities); self.config.update_capabilities(&params.capabilities);
@ -999,16 +1087,16 @@ impl Inner {
self.update_debug_flag(); self.update_debug_flag();
// Check to see if we need to change the cache path // Check to see if we need to change the cache path
if let Err(err) = self.update_cache() { if let Err(err) = self.update_cache() {
self.client.show_message(MessageType::WARNING, err).await; self.client.show_message(MessageType::WARNING, err);
} }
if let Err(err) = self.update_config_file() { if let Err(err) = self.update_config_file() {
self.client.show_message(MessageType::WARNING, err).await; self.client.show_message(MessageType::WARNING, err);
} }
if let Err(err) = self.update_package_json() { if let Err(err) = self.update_package_json() {
self.client.show_message(MessageType::WARNING, err).await; self.client.show_message(MessageType::WARNING, err);
} }
if let Err(err) = self.update_tsconfig().await { if let Err(err) = self.update_tsconfig().await {
self.client.show_message(MessageType::WARNING, err).await; self.client.show_message(MessageType::WARNING, err);
} }
if capabilities.code_action_provider.is_some() { if capabilities.code_action_provider.is_some() {
@ -1025,20 +1113,14 @@ impl Inner {
// Check to see if we need to setup the import map // Check to see if we need to setup the import map
if let Err(err) = self.update_import_map().await { if let Err(err) = self.update_import_map().await {
self.client.show_message(MessageType::WARNING, err).await; self.client.show_message(MessageType::WARNING, err);
} }
// Check to see if we need to setup any module registries // Check to see if we need to setup any module registries
if let Err(err) = self.update_registries().await { if let Err(err) = self.update_registries().await {
self.client.show_message(MessageType::WARNING, err).await; self.client.show_message(MessageType::WARNING, err);
} }
self.documents.update_config(
self.maybe_import_map.clone(),
self.maybe_config_file.as_ref(),
self.maybe_package_json.as_ref(),
self.npm_api.clone(),
self.npm_resolution.clone(),
);
// self.refresh_documents_config(); // todo(THIS PR): REMOVE
self.assets.intitialize(self.snapshot()).await; self.assets.intitialize(self.snapshot()).await;
self.performance.measure(mark); self.performance.measure(mark);
@ -1049,47 +1131,14 @@ impl Inner {
}) })
} }
async fn initialized(&mut self, _: InitializedParams) { fn refresh_documents_config(&mut self) {
if self self.documents.update_config(
.config self.maybe_import_map.clone(),
.client_capabilities self.maybe_config_file.as_ref(),
.workspace_did_change_watched_files self.maybe_package_json.as_ref(),
{ self.npm_api.clone(),
// we are going to watch all the JSON files in the workspace, and the self.npm_resolution.clone(),
// notification handler will pick up any of the changes of those files we );
// are interested in.
let watch_registration_options =
DidChangeWatchedFilesRegistrationOptions {
watchers: vec![FileSystemWatcher {
glob_pattern: "**/*.{json,jsonc}".to_string(),
kind: Some(WatchKind::Change),
}],
};
let registration = Registration {
id: "workspace/didChangeWatchedFiles".to_string(),
method: "workspace/didChangeWatchedFiles".to_string(),
register_options: Some(
serde_json::to_value(watch_registration_options).unwrap(),
),
};
if let Err(err) =
self.client.register_capability(vec![registration]).await
{
warn!("Client errored on capabilities.\n{:#}", err);
}
}
self.config.update_enabled_paths(self.client.clone()).await;
if self.config.client_capabilities.testing_api {
let test_server = testing::TestServer::new(
self.client.clone(),
self.performance.clone(),
self.config.root_uri.clone(),
);
self.maybe_testing_server = Some(test_server);
}
lsp_log!("Server ready.");
} }
async fn shutdown(&self) -> LspResult<()> { async fn shutdown(&self) -> LspResult<()> {
@ -1130,7 +1179,9 @@ impl Inner {
async fn did_change(&mut self, params: DidChangeTextDocumentParams) { async fn did_change(&mut self, params: DidChangeTextDocumentParams) {
let mark = self.performance.mark("did_change", Some(&params)); let mark = self.performance.mark("did_change", Some(&params));
let specifier = self.url_map.normalize_url(&params.text_document.uri); let specifier = self
.url_map
.normalize_url(&params.text_document.uri, LspUrlKind::File);
match self.documents.change( match self.documents.change(
&specifier, &specifier,
params.text_document.version, params.text_document.version,
@ -1166,7 +1217,9 @@ impl Inner {
// already managed by the language service // already managed by the language service
return; return;
} }
let specifier = self.url_map.normalize_url(&params.text_document.uri); let specifier = self
.url_map
.normalize_url(&params.text_document.uri, LspUrlKind::File);
if let Err(err) = self.documents.close(&specifier) { if let Err(err) = self.documents.close(&specifier) {
error!("{}", err); error!("{}", err);
@ -1206,31 +1259,25 @@ impl Inner {
self.update_debug_flag(); self.update_debug_flag();
if let Err(err) = self.update_cache() { if let Err(err) = self.update_cache() {
self.client.show_message(MessageType::WARNING, err).await; self.client.show_message(MessageType::WARNING, err);
} }
if let Err(err) = self.update_registries().await { if let Err(err) = self.update_registries().await {
self.client.show_message(MessageType::WARNING, err).await; self.client.show_message(MessageType::WARNING, err);
} }
if let Err(err) = self.update_config_file() { if let Err(err) = self.update_config_file() {
self.client.show_message(MessageType::WARNING, err).await; self.client.show_message(MessageType::WARNING, err);
} }
if let Err(err) = self.update_package_json() { if let Err(err) = self.update_package_json() {
self.client.show_message(MessageType::WARNING, err).await; self.client.show_message(MessageType::WARNING, err);
} }
if let Err(err) = self.update_import_map().await { if let Err(err) = self.update_import_map().await {
self.client.show_message(MessageType::WARNING, err).await; self.client.show_message(MessageType::WARNING, err);
} }
if let Err(err) = self.update_tsconfig().await { if let Err(err) = self.update_tsconfig().await {
self.client.show_message(MessageType::WARNING, err).await; self.client.show_message(MessageType::WARNING, err);
} }
self.documents.update_config( self.refresh_documents_config();
self.maybe_import_map.clone(),
self.maybe_config_file.as_ref(),
self.maybe_package_json.as_ref(),
self.npm_api.clone(),
self.npm_resolution.clone(),
);
self.send_diagnostics_update(); self.send_diagnostics_update();
self.send_testing_update(); self.send_testing_update();
@ -1247,17 +1294,17 @@ impl Inner {
let changes: HashSet<Url> = params let changes: HashSet<Url> = params
.changes .changes
.iter() .iter()
.map(|f| self.url_map.normalize_url(&f.uri)) .map(|f| self.url_map.normalize_url(&f.uri, LspUrlKind::File))
.collect(); .collect();
// if the current deno.json has changed, we need to reload it // if the current deno.json has changed, we need to reload it
if let Some(config_file) = &self.maybe_config_file { if let Some(config_file) = &self.maybe_config_file {
if changes.contains(&config_file.specifier) { if changes.contains(&config_file.specifier) {
if let Err(err) = self.update_config_file() { if let Err(err) = self.update_config_file() {
self.client.show_message(MessageType::WARNING, err).await; self.client.show_message(MessageType::WARNING, err);
} }
if let Err(err) = self.update_tsconfig().await { if let Err(err) = self.update_tsconfig().await {
self.client.show_message(MessageType::WARNING, err).await; self.client.show_message(MessageType::WARNING, err);
} }
touched = true; touched = true;
} }
@ -1266,7 +1313,7 @@ impl Inner {
// always update the package json if the deno config changes // always update the package json if the deno config changes
if touched || changes.contains(&package_json.specifier()) { if touched || changes.contains(&package_json.specifier()) {
if let Err(err) = self.update_package_json() { if let Err(err) = self.update_package_json() {
self.client.show_message(MessageType::WARNING, err).await; self.client.show_message(MessageType::WARNING, err);
} }
touched = true; touched = true;
} }
@ -1276,19 +1323,13 @@ impl Inner {
if let Some(import_map_uri) = &self.maybe_import_map_uri { if let Some(import_map_uri) = &self.maybe_import_map_uri {
if touched || changes.contains(import_map_uri) { if touched || changes.contains(import_map_uri) {
if let Err(err) = self.update_import_map().await { if let Err(err) = self.update_import_map().await {
self.client.show_message(MessageType::WARNING, err).await; self.client.show_message(MessageType::WARNING, err);
} }
touched = true; touched = true;
} }
} }
if touched { if touched {
self.documents.update_config( self.refresh_documents_config();
self.maybe_import_map.clone(),
self.maybe_config_file.as_ref(),
self.maybe_package_json.as_ref(),
self.npm_api.clone(),
self.npm_resolution.clone(),
);
self.refresh_npm_specifiers().await; self.refresh_npm_specifiers().await;
self.diagnostics_server.invalidate_all(); self.diagnostics_server.invalidate_all();
self.restart_ts_server().await; self.restart_ts_server().await;
@ -1298,18 +1339,20 @@ impl Inner {
self.performance.measure(mark); self.performance.measure(mark);
} }
async fn did_change_workspace_folders( fn did_change_workspace_folders(
&mut self, &mut self,
params: DidChangeWorkspaceFoldersParams, params: DidChangeWorkspaceFoldersParams,
) { ) {
let mark = self
.performance
.mark("did_change_workspace_folders", Some(&params));
let mut workspace_folders = params let mut workspace_folders = params
.event .event
.added .added
.into_iter() .into_iter()
.map(|folder| (self.url_map.normalize_url(&folder.uri), folder)) .map(|folder| {
(
self.url_map.normalize_url(&folder.uri, LspUrlKind::Folder),
folder,
)
})
.collect::<Vec<(ModuleSpecifier, WorkspaceFolder)>>(); .collect::<Vec<(ModuleSpecifier, WorkspaceFolder)>>();
if let Some(current_folders) = &self.config.workspace_folders { if let Some(current_folders) = &self.config.workspace_folders {
for (specifier, folder) in current_folders { for (specifier, folder) in current_folders {
@ -1323,14 +1366,15 @@ impl Inner {
} }
self.config.workspace_folders = Some(workspace_folders); self.config.workspace_folders = Some(workspace_folders);
self.performance.measure(mark);
} }
async fn document_symbol( async fn document_symbol(
&self, &self,
params: DocumentSymbolParams, params: DocumentSymbolParams,
) -> LspResult<Option<DocumentSymbolResponse>> { ) -> LspResult<Option<DocumentSymbolResponse>> {
let specifier = self.url_map.normalize_url(&params.text_document.uri); let specifier = self
.url_map
.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)
{ {
@ -1368,7 +1412,9 @@ impl Inner {
&self, &self,
params: DocumentFormattingParams, params: DocumentFormattingParams,
) -> LspResult<Option<Vec<TextEdit>>> { ) -> LspResult<Option<Vec<TextEdit>>> {
let specifier = self.url_map.normalize_url(&params.text_document.uri); let specifier = self
.url_map
.normalize_url(&params.text_document.uri, LspUrlKind::File);
let document = match self.documents.get(&specifier) { let document = match self.documents.get(&specifier) {
Some(doc) if doc.is_open() => doc, Some(doc) if doc.is_open() => doc,
_ => return Ok(None), _ => return Ok(None),
@ -1425,15 +1471,16 @@ impl Inner {
Ok(Some(text_edits)) Ok(Some(text_edits))
} }
} else { } else {
self.client.show_message(MessageType::WARNING, format!("Unable to format \"{specifier}\". Likely due to unrecoverable syntax errors in the file.")).await; self.client.show_message(MessageType::WARNING, format!("Unable to format \"{specifier}\". Likely due to unrecoverable syntax errors in the file."));
Ok(None) Ok(None)
} }
} }
async fn hover(&self, params: HoverParams) -> LspResult<Option<Hover>> { async fn hover(&self, params: HoverParams) -> LspResult<Option<Hover>> {
let specifier = self let specifier = self.url_map.normalize_url(
.url_map &params.text_document_position_params.text_document.uri,
.normalize_url(&params.text_document_position_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)
{ {
@ -1518,7 +1565,9 @@ impl Inner {
&self, &self,
params: CodeActionParams, params: CodeActionParams,
) -> LspResult<Option<CodeActionResponse>> { ) -> LspResult<Option<CodeActionResponse>> {
let specifier = self.url_map.normalize_url(&params.text_document.uri); let specifier = self
.url_map
.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)
{ {
@ -1778,7 +1827,9 @@ impl Inner {
&self, &self,
params: CodeLensParams, params: CodeLensParams,
) -> LspResult<Option<Vec<CodeLens>>> { ) -> LspResult<Option<Vec<CodeLens>>> {
let specifier = self.url_map.normalize_url(&params.text_document.uri); let specifier = self
.url_map
.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.get_workspace_settings().enabled_code_lens() || !(self.config.get_workspace_settings().enabled_code_lens()
@ -1838,9 +1889,10 @@ impl Inner {
&self, &self,
params: DocumentHighlightParams, params: DocumentHighlightParams,
) -> LspResult<Option<Vec<DocumentHighlight>>> { ) -> LspResult<Option<Vec<DocumentHighlight>>> {
let specifier = self let specifier = self.url_map.normalize_url(
.url_map &params.text_document_position_params.text_document.uri,
.normalize_url(&params.text_document_position_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)
{ {
@ -1882,9 +1934,10 @@ impl Inner {
&self, &self,
params: ReferenceParams, params: ReferenceParams,
) -> LspResult<Option<Vec<Location>>> { ) -> LspResult<Option<Vec<Location>>> {
let specifier = self let specifier = self.url_map.normalize_url(
.url_map &params.text_document_position.text_document.uri,
.normalize_url(&params.text_document_position.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)
{ {
@ -1938,9 +1991,10 @@ impl Inner {
&self, &self,
params: GotoDefinitionParams, params: GotoDefinitionParams,
) -> LspResult<Option<GotoDefinitionResponse>> { ) -> LspResult<Option<GotoDefinitionResponse>> {
let specifier = self let specifier = self.url_map.normalize_url(
.url_map &params.text_document_position_params.text_document.uri,
.normalize_url(&params.text_document_position_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)
{ {
@ -1977,9 +2031,10 @@ impl Inner {
&self, &self,
params: GotoTypeDefinitionParams, params: GotoTypeDefinitionParams,
) -> LspResult<Option<GotoTypeDefinitionResponse>> { ) -> LspResult<Option<GotoTypeDefinitionResponse>> {
let specifier = self let specifier = self.url_map.normalize_url(
.url_map &params.text_document_position_params.text_document.uri,
.normalize_url(&params.text_document_position_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)
{ {
@ -2024,9 +2079,10 @@ impl Inner {
&self, &self,
params: CompletionParams, params: CompletionParams,
) -> LspResult<Option<CompletionResponse>> { ) -> LspResult<Option<CompletionResponse>> {
let specifier = self let specifier = self.url_map.normalize_url(
.url_map &params.text_document_position.text_document.uri,
.normalize_url(&params.text_document_position.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)
{ {
@ -2043,7 +2099,7 @@ impl Inner {
&specifier, &specifier,
&params.text_document_position.position, &params.text_document_position.position,
&self.config.snapshot(), &self.config.snapshot(),
self.client.clone(), &self.client,
&self.module_registries, &self.module_registries,
&self.documents, &self.documents,
self.maybe_import_map.clone(), self.maybe_import_map.clone(),
@ -2183,9 +2239,10 @@ impl Inner {
&self, &self,
params: GotoImplementationParams, params: GotoImplementationParams,
) -> LspResult<Option<GotoImplementationResponse>> { ) -> LspResult<Option<GotoImplementationResponse>> {
let specifier = self let specifier = self.url_map.normalize_url(
.url_map &params.text_document_position_params.text_document.uri,
.normalize_url(&params.text_document_position_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)
{ {
@ -2229,7 +2286,9 @@ impl Inner {
&self, &self,
params: FoldingRangeParams, params: FoldingRangeParams,
) -> LspResult<Option<Vec<FoldingRange>>> { ) -> LspResult<Option<Vec<FoldingRange>>> {
let specifier = self.url_map.normalize_url(&params.text_document.uri); let specifier = self
.url_map
.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)
{ {
@ -2273,7 +2332,9 @@ impl Inner {
&self, &self,
params: CallHierarchyIncomingCallsParams, params: CallHierarchyIncomingCallsParams,
) -> LspResult<Option<Vec<CallHierarchyIncomingCall>>> { ) -> LspResult<Option<Vec<CallHierarchyIncomingCall>>> {
let specifier = self.url_map.normalize_url(&params.item.uri); let specifier = self
.url_map
.normalize_url(&params.item.uri, LspUrlKind::File);
if !self.is_diagnosable(&specifier) if !self.is_diagnosable(&specifier)
|| !self.config.specifier_enabled(&specifier) || !self.config.specifier_enabled(&specifier)
{ {
@ -2319,7 +2380,9 @@ impl Inner {
&self, &self,
params: CallHierarchyOutgoingCallsParams, params: CallHierarchyOutgoingCallsParams,
) -> LspResult<Option<Vec<CallHierarchyOutgoingCall>>> { ) -> LspResult<Option<Vec<CallHierarchyOutgoingCall>>> {
let specifier = self.url_map.normalize_url(&params.item.uri); let specifier = self
.url_map
.normalize_url(&params.item.uri, LspUrlKind::File);
if !self.is_diagnosable(&specifier) if !self.is_diagnosable(&specifier)
|| !self.config.specifier_enabled(&specifier) || !self.config.specifier_enabled(&specifier)
{ {
@ -2366,9 +2429,10 @@ impl Inner {
&self, &self,
params: CallHierarchyPrepareParams, params: CallHierarchyPrepareParams,
) -> LspResult<Option<Vec<CallHierarchyItem>>> { ) -> LspResult<Option<Vec<CallHierarchyItem>>> {
let specifier = self let specifier = self.url_map.normalize_url(
.url_map &params.text_document_position_params.text_document.uri,
.normalize_url(&params.text_document_position_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)
{ {
@ -2434,9 +2498,10 @@ impl Inner {
&self, &self,
params: RenameParams, params: RenameParams,
) -> LspResult<Option<WorkspaceEdit>> { ) -> LspResult<Option<WorkspaceEdit>> {
let specifier = self let specifier = self.url_map.normalize_url(
.url_map &params.text_document_position.text_document.uri,
.normalize_url(&params.text_document_position.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)
{ {
@ -2486,7 +2551,9 @@ impl Inner {
&self, &self,
params: SelectionRangeParams, params: SelectionRangeParams,
) -> LspResult<Option<Vec<SelectionRange>>> { ) -> LspResult<Option<Vec<SelectionRange>>> {
let specifier = self.url_map.normalize_url(&params.text_document.uri); let specifier = self
.url_map
.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)
{ {
@ -2524,7 +2591,9 @@ impl Inner {
&self, &self,
params: SemanticTokensParams, params: SemanticTokensParams,
) -> LspResult<Option<SemanticTokensResult>> { ) -> LspResult<Option<SemanticTokensResult>> {
let specifier = self.url_map.normalize_url(&params.text_document.uri); let specifier = self
.url_map
.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)
{ {
@ -2566,7 +2635,9 @@ impl Inner {
&self, &self,
params: SemanticTokensRangeParams, params: SemanticTokensRangeParams,
) -> LspResult<Option<SemanticTokensRangeResult>> { ) -> LspResult<Option<SemanticTokensRangeResult>> {
let specifier = self.url_map.normalize_url(&params.text_document.uri); let specifier = self
.url_map
.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)
{ {
@ -2609,9 +2680,10 @@ impl Inner {
&self, &self,
params: SignatureHelpParams, params: SignatureHelpParams,
) -> LspResult<Option<SignatureHelp>> { ) -> LspResult<Option<SignatureHelp>> {
let specifier = self let specifier = self.url_map.normalize_url(
.url_map &params.text_document_position_params.text_document.uri,
.normalize_url(&params.text_document_position_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)
{ {
@ -2728,8 +2800,61 @@ impl tower_lsp::LanguageServer for LanguageServer {
language_server.initialize(params).await language_server.initialize(params).await
} }
async fn initialized(&self, params: InitializedParams) { async fn initialized(&self, _: InitializedParams) {
self.0.write().await.initialized(params).await let mut maybe_registration = None;
let client = {
let mut ls = self.0.write().await;
if ls
.config
.client_capabilities
.workspace_did_change_watched_files
{
// we are going to watch all the JSON files in the workspace, and the
// notification handler will pick up any of the changes of those files we
// are interested in.
let watch_registration_options =
DidChangeWatchedFilesRegistrationOptions {
watchers: vec![FileSystemWatcher {
glob_pattern: "**/*.{json,jsonc}".to_string(),
kind: Some(WatchKind::Change),
}],
};
maybe_registration = Some(Registration {
id: "workspace/didChangeWatchedFiles".to_string(),
method: "workspace/didChangeWatchedFiles".to_string(),
register_options: Some(
serde_json::to_value(watch_registration_options).unwrap(),
),
});
}
if ls.config.client_capabilities.testing_api {
let test_server = testing::TestServer::new(
ls.client.clone(),
ls.performance.clone(),
ls.config.root_uri.clone(),
);
ls.maybe_testing_server = Some(test_server);
}
ls.client.clone()
};
if let Some(registration) = maybe_registration {
if let Err(err) = client
.when_outside_lsp_lock()
.register_capability(vec![registration])
.await
{
warn!("Client errored on capabilities.\n{:#}", err);
}
}
if !self.refresh_specifiers_from_client().await {
// force update config
self.0.write().await.refresh_documents_config();
}
lsp_log!("Server ready.");
} }
async fn shutdown(&self) -> LspResult<()> { async fn shutdown(&self) -> LspResult<()> {
@ -2744,11 +2869,12 @@ impl tower_lsp::LanguageServer for LanguageServer {
return; return;
} }
let (client, uri, specifier, had_specifier_settings) = { let (client, client_uri, specifier, had_specifier_settings) = {
let mut inner = self.0.write().await; let mut inner = self.0.write().await;
let client = inner.client.clone(); let client = inner.client.clone();
let uri = params.text_document.uri.clone(); let client_uri = params.text_document.uri.clone();
let specifier = inner.url_map.normalize_url(&uri); let specifier =
inner.url_map.normalize_url(&client_uri, LspUrlKind::File);
let document = inner.did_open(&specifier, params).await; let document = inner.did_open(&specifier, params).await;
let has_specifier_settings = let has_specifier_settings =
inner.config.has_specifier_settings(&specifier); inner.config.has_specifier_settings(&specifier);
@ -2762,39 +2888,38 @@ impl tower_lsp::LanguageServer for LanguageServer {
inner.send_testing_update(); inner.send_testing_update();
} }
} }
(client, uri, specifier, has_specifier_settings) (client, client_uri, specifier, has_specifier_settings)
}; };
// retrieve the specifier settings outside the lock if // retrieve the specifier settings outside the lock if
// they haven't been asked for yet on its own time // they haven't been asked for yet
if !had_specifier_settings { if !had_specifier_settings {
let language_server = self.clone(); let response = client
tokio::spawn(async move { .when_outside_lsp_lock()
let response = client.specifier_configuration(&uri).await; .specifier_configuration(&client_uri)
let mut inner = language_server.0.write().await; .await;
match response { let mut ls = self.0.write().await;
Ok(specifier_settings) => { match response {
// now update the config and send a diagnostics update Ok(specifier_settings) => {
inner.config.set_specifier_settings( ls.config
specifier.clone(), .set_specifier_settings(specifier.clone(), specifier_settings);
uri, ls.config.update_enabled_paths();
specifier_settings,
);
}
Err(err) => {
error!("{}", err);
}
} }
if inner Err(err) => {
.documents error!("{}", err);
.get(&specifier)
.map(|d| d.is_diagnosable())
.unwrap_or(false)
{
inner.send_diagnostics_update();
inner.send_testing_update();
} }
}); }
if ls
.documents
.get(&specifier)
.map(|d| d.is_diagnosable())
.unwrap_or(false)
{
ls.refresh_documents_config();
ls.send_diagnostics_update();
ls.send_testing_update();
}
} }
} }
@ -2815,66 +2940,18 @@ impl tower_lsp::LanguageServer for LanguageServer {
&self, &self,
params: DidChangeConfigurationParams, params: DidChangeConfigurationParams,
) { ) {
let (has_workspace_capability, client, specifiers, mark) = { let (mark, has_workspace_capability, client) = {
let inner = self.0.write().await; let inner = self.0.read().await;
let mark = inner
.performance
.mark("did_change_configuration", Some(&params));
let specifiers =
if inner.config.client_capabilities.workspace_configuration {
Some(inner.config.get_specifiers_with_client_uris())
} else {
None
};
( (
inner
.performance
.mark("did_change_configuration", Some(&params)),
inner.config.client_capabilities.workspace_configuration, inner.config.client_capabilities.workspace_configuration,
inner.client.clone(), inner.client.clone(),
specifiers,
mark,
) )
}; };
// start retrieving all the specifiers' settings outside the lock on its own self.refresh_specifiers_from_client().await;
// time
if let Some(specifiers) = specifiers {
let language_server = self.clone();
let client = client.clone();
tokio::spawn(async move {
if let Ok(configs) = client
.specifier_configurations(
specifiers.iter().map(|s| s.client_uri.clone()).collect(),
)
.await
{
let mut inner = language_server.0.write().await;
for (i, value) in configs.into_iter().enumerate() {
match value {
Ok(specifier_settings) => {
let entry = specifiers[i].clone();
inner.config.set_specifier_settings(
entry.specifier,
entry.client_uri,
specifier_settings,
);
}
Err(err) => {
error!("{}", err);
}
}
}
}
let mut ls = language_server.0.write().await;
if ls.config.update_enabled_paths(client).await {
ls.diagnostics_server.invalidate_all();
// this will be called in the inner did_change_configuration, but the
// problem then becomes, if there was a change, the snapshot used
// will be an out of date one, so we will call it again here if the
// workspace folders have been touched
ls.send_diagnostics_update();
}
});
}
// Get the configuration from the client outside of the lock // Get the configuration from the client outside of the lock
// in order to prevent potential deadlocking scenarios where // in order to prevent potential deadlocking scenarios where
@ -2884,7 +2961,10 @@ impl tower_lsp::LanguageServer for LanguageServer {
// received and acquiring the lock, but most likely there // received and acquiring the lock, but most likely there
// won't be any racing here. // won't be any racing here.
let client_workspace_config = if has_workspace_capability { let client_workspace_config = if has_workspace_capability {
let config_response = client.workspace_configuration().await; let config_response = client
.when_outside_lsp_lock()
.workspace_configuration()
.await;
match config_response { match config_response {
Ok(value) => Some(value), Ok(value) => Some(value),
Err(err) => { Err(err) => {
@ -2915,19 +2995,17 @@ impl tower_lsp::LanguageServer for LanguageServer {
&self, &self,
params: DidChangeWorkspaceFoldersParams, params: DidChangeWorkspaceFoldersParams,
) { ) {
let client = { let (performance, mark) = {
let mut inner = self.0.write().await; let mut ls = self.0.write().await;
inner.did_change_workspace_folders(params).await; let mark = ls
inner.client.clone() .performance
.mark("did_change_workspace_folders", Some(&params));
ls.did_change_workspace_folders(params);
(ls.performance.clone(), mark)
}; };
let language_server = self.clone();
tokio::spawn(async move { self.refresh_specifiers_from_client().await;
let mut ls = language_server.0.write().await; performance.measure(mark);
if ls.config.update_enabled_paths(client).await {
ls.diagnostics_server.invalidate_all();
ls.send_diagnostics_update();
}
});
} }
async fn document_symbol( async fn document_symbol(
@ -3106,7 +3184,9 @@ impl Inner {
&self, &self,
params: lsp_custom::CacheParams, params: lsp_custom::CacheParams,
) -> Result<Option<PrepareCacheResult>, AnyError> { ) -> Result<Option<PrepareCacheResult>, AnyError> {
let referrer = self.url_map.normalize_url(&params.referrer.uri); let referrer = self
.url_map
.normalize_url(&params.referrer.uri, LspUrlKind::File);
if !self.is_diagnosable(&referrer) { if !self.is_diagnosable(&referrer) {
return Ok(None); return Ok(None);
} }
@ -3116,7 +3196,7 @@ impl Inner {
params params
.uris .uris
.iter() .iter()
.map(|t| self.url_map.normalize_url(&t.uri)) .map(|t| self.url_map.normalize_url(&t.uri, LspUrlKind::File))
.collect() .collect()
} else { } else {
vec![referrer] vec![referrer]
@ -3190,7 +3270,9 @@ impl Inner {
&self, &self,
params: InlayHintParams, params: InlayHintParams,
) -> LspResult<Option<Vec<InlayHint>>> { ) -> LspResult<Option<Vec<InlayHint>>> {
let specifier = self.url_map.normalize_url(&params.text_document.uri); let specifier = self
.url_map
.normalize_url(&params.text_document.uri, LspUrlKind::File);
let workspace_settings = self.config.get_workspace_settings(); let workspace_settings = self.config.get_workspace_settings();
if !self.is_diagnosable(&specifier) if !self.is_diagnosable(&specifier)
|| !self.config.specifier_enabled(&specifier) || !self.config.specifier_enabled(&specifier)
@ -3251,7 +3333,9 @@ impl Inner {
let mark = self let mark = self
.performance .performance
.mark("virtual_text_document", Some(&params)); .mark("virtual_text_document", Some(&params));
let specifier = self.url_map.normalize_url(&params.text_document.uri); let specifier = self
.url_map
.normalize_url(&params.text_document.uri, LspUrlKind::File);
let contents = if specifier.as_str() == "deno:/status.md" { let contents = if specifier.as_str() == "deno:/status.md" {
let mut contents = String::new(); let mut contents = String::new();
let mut documents_specifiers = self let mut documents_specifiers = self

View file

@ -156,7 +156,7 @@ impl TestServer {
match run.exec(&client, maybe_root_uri.as_ref()).await { match run.exec(&client, maybe_root_uri.as_ref()).await {
Ok(_) => (), Ok(_) => (),
Err(err) => { Err(err) => {
client.show_message(lsp::MessageType::ERROR, err).await; client.show_message(lsp::MessageType::ERROR, err);
} }
} }
client.send_test_notification(TestingNotification::Progress( client.send_test_notification(TestingNotification::Progress(

View file

@ -80,8 +80,14 @@ impl LspUrlMapInner {
} }
} }
#[derive(Debug, Clone, Copy)]
pub enum LspUrlKind {
File,
Folder,
}
/// A bi-directional map of URLs sent to the LSP client and internal module /// A bi-directional map of URLs sent to the LSP client and internal module
/// specifiers. We need to map internal specifiers into `deno:` schema URLs /// specifiers. We need to map internal specifiers into `deno:` schema URLs
/// to allow the Deno language server to manage these as virtual documents. /// to allow the Deno language server to manage these as virtual documents.
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]
pub struct LspUrlMap(Arc<Mutex<LspUrlMapInner>>); pub struct LspUrlMap(Arc<Mutex<LspUrlMapInner>>);
@ -142,16 +148,26 @@ impl LspUrlMap {
/// converted into proper module specifiers, as well as handle situations /// converted into proper module specifiers, as well as handle situations
/// where the client encodes a file URL differently than Rust does by default /// where the client encodes a file URL differently than Rust does by default
/// causing issues with string matching of URLs. /// causing issues with string matching of URLs.
pub fn normalize_url(&self, url: &Url) -> ModuleSpecifier { ///
if let Some(specifier) = self.0.lock().get_specifier(url).cloned() { /// Note: Sometimes the url provided by the client may not have a trailing slash,
return specifier; /// so we need to force it to in the mapping and nee to explicitly state whether
/// this is a file or directory url.
pub fn normalize_url(&self, url: &Url, kind: LspUrlKind) -> ModuleSpecifier {
let mut inner = self.0.lock();
if let Some(specifier) = inner.get_specifier(url).cloned() {
specifier
} else {
let specifier = if let Ok(path) = url.to_file_path() {
match kind {
LspUrlKind::Folder => Url::from_directory_path(path).unwrap(),
LspUrlKind::File => Url::from_file_path(path).unwrap(),
}
} else {
url.clone()
};
inner.put(specifier.clone(), url.clone());
specifier
} }
if url.scheme() == "file" {
if let Ok(path) = url.to_file_path() {
return Url::from_file_path(path).unwrap();
}
}
url.clone()
} }
} }
@ -181,7 +197,7 @@ mod tests {
Url::parse("deno:/https/deno.land/x/pkg%401.0.0/mod.ts").unwrap(); Url::parse("deno:/https/deno.land/x/pkg%401.0.0/mod.ts").unwrap();
assert_eq!(actual_url, expected_url); assert_eq!(actual_url, expected_url);
let actual_specifier = map.normalize_url(&actual_url); let actual_specifier = map.normalize_url(&actual_url, LspUrlKind::File);
assert_eq!(actual_specifier, fixture); assert_eq!(actual_specifier, fixture);
} }
@ -196,7 +212,7 @@ mod tests {
let expected_url = Url::parse("deno:/https/cdn.skypack.dev/-/postcss%40v8.2.9-E4SktPp9c0AtxrJHp8iV/dist%3Des2020%2Cmode%3Dtypes/lib/postcss.d.ts").unwrap(); let expected_url = Url::parse("deno:/https/cdn.skypack.dev/-/postcss%40v8.2.9-E4SktPp9c0AtxrJHp8iV/dist%3Des2020%2Cmode%3Dtypes/lib/postcss.d.ts").unwrap();
assert_eq!(actual_url, expected_url); assert_eq!(actual_url, expected_url);
let actual_specifier = map.normalize_url(&actual_url); let actual_specifier = map.normalize_url(&actual_url, LspUrlKind::File);
assert_eq!(actual_specifier, fixture); assert_eq!(actual_specifier, fixture);
} }
@ -210,7 +226,7 @@ mod tests {
let expected_url = Url::parse("deno:/c21c7fc382b2b0553dc0864aa81a3acacfb7b3d1285ab5ae76da6abec213fb37/data_url.ts").unwrap(); let expected_url = Url::parse("deno:/c21c7fc382b2b0553dc0864aa81a3acacfb7b3d1285ab5ae76da6abec213fb37/data_url.ts").unwrap();
assert_eq!(actual_url, expected_url); assert_eq!(actual_url, expected_url);
let actual_specifier = map.normalize_url(&actual_url); let actual_specifier = map.normalize_url(&actual_url, LspUrlKind::File);
assert_eq!(actual_specifier, fixture); assert_eq!(actual_specifier, fixture);
} }
@ -222,7 +238,7 @@ mod tests {
"file:///c%3A/Users/deno/Desktop/file%20with%20spaces%20in%20name.txt", "file:///c%3A/Users/deno/Desktop/file%20with%20spaces%20in%20name.txt",
) )
.unwrap(); .unwrap();
let actual = map.normalize_url(&fixture); let actual = map.normalize_url(&fixture, LspUrlKind::File);
let expected = let expected =
Url::parse("file:///C:/Users/deno/Desktop/file with spaces in name.txt") Url::parse("file:///C:/Users/deno/Desktop/file with spaces in name.txt")
.unwrap(); .unwrap();
@ -237,7 +253,7 @@ mod tests {
"file:///Users/deno/Desktop/file%20with%20spaces%20in%20name.txt", "file:///Users/deno/Desktop/file%20with%20spaces%20in%20name.txt",
) )
.unwrap(); .unwrap();
let actual = map.normalize_url(&fixture); let actual = map.normalize_url(&fixture, LspUrlKind::File);
let expected = let expected =
Url::parse("file:///Users/deno/Desktop/file with spaces in name.txt") Url::parse("file:///Users/deno/Desktop/file with spaces in name.txt")
.unwrap(); .unwrap();

View file

@ -110,7 +110,7 @@ fn cafile_fetch() {
#[test] #[test]
fn cafile_compile() { fn cafile_compile() {
let context = TestContext::with_http_server(); let context = TestContext::with_http_server();
let temp_dir = context.deno_dir().path(); let temp_dir = context.temp_dir().path();
let output_exe = if cfg!(windows) { let output_exe = if cfg!(windows) {
temp_dir.join("cert.exe") temp_dir.join("cert.exe")
} else { } else {

View file

@ -26,7 +26,7 @@ fn lsp_startup_shutdown() {
#[test] #[test]
fn lsp_init_tsconfig() { fn lsp_init_tsconfig() {
let context = TestContextBuilder::new().build(); let context = TestContextBuilder::new().build();
let temp_dir = context.deno_dir(); let temp_dir = context.temp_dir();
temp_dir.write( temp_dir.write(
"lib.tsconfig.json", "lib.tsconfig.json",
@ -59,7 +59,7 @@ fn lsp_init_tsconfig() {
#[test] #[test]
fn lsp_tsconfig_types() { fn lsp_tsconfig_types() {
let context = TestContextBuilder::new().build(); let context = TestContextBuilder::new().build();
let temp_dir = context.deno_dir(); let temp_dir = context.temp_dir();
temp_dir.write( temp_dir.write(
"types.tsconfig.json", "types.tsconfig.json",
@ -124,7 +124,7 @@ fn lsp_tsconfig_bad_config_path() {
#[test] #[test]
fn lsp_triple_slash_types() { fn lsp_triple_slash_types() {
let context = TestContextBuilder::new().build(); let context = TestContextBuilder::new().build();
let temp_dir = context.deno_dir(); let temp_dir = context.temp_dir();
let a_dts = "// deno-lint-ignore-file no-var\ndeclare var a: string;"; let a_dts = "// deno-lint-ignore-file no-var\ndeclare var a: string;";
temp_dir.write("a.d.ts", a_dts); temp_dir.write("a.d.ts", a_dts);
let mut client = context.new_lsp_command().build(); let mut client = context.new_lsp_command().build();
@ -147,7 +147,7 @@ fn lsp_triple_slash_types() {
#[test] #[test]
fn lsp_import_map() { fn lsp_import_map() {
let context = TestContextBuilder::new().build(); let context = TestContextBuilder::new().build();
let temp_dir = context.deno_dir(); let temp_dir = context.temp_dir();
let import_map = r#"{ let import_map = r#"{
"imports": { "imports": {
"/~/": "./lib/" "/~/": "./lib/"
@ -231,7 +231,7 @@ fn lsp_import_map_data_url() {
#[test] #[test]
fn lsp_import_map_config_file() { fn lsp_import_map_config_file() {
let context = TestContextBuilder::new().build(); let context = TestContextBuilder::new().build();
let temp_dir = context.deno_dir(); let temp_dir = context.temp_dir();
temp_dir.write( temp_dir.write(
"deno.import_map.jsonc", "deno.import_map.jsonc",
r#"{ r#"{
@ -298,7 +298,7 @@ fn lsp_import_map_config_file() {
#[test] #[test]
fn lsp_import_map_embedded_in_config_file() { fn lsp_import_map_embedded_in_config_file() {
let context = TestContextBuilder::new().build(); let context = TestContextBuilder::new().build();
let temp_dir = context.deno_dir(); let temp_dir = context.temp_dir();
temp_dir.write( temp_dir.write(
"deno.embedded_import_map.jsonc", "deno.embedded_import_map.jsonc",
r#"{ r#"{
@ -359,7 +359,7 @@ fn lsp_import_map_embedded_in_config_file() {
#[test] #[test]
fn lsp_deno_task() { fn lsp_deno_task() {
let context = TestContextBuilder::new().build(); let context = TestContextBuilder::new().build();
let temp_dir = context.deno_dir(); let temp_dir = context.temp_dir();
temp_dir.write( temp_dir.write(
"deno.jsonc", "deno.jsonc",
r#"{ r#"{
@ -510,7 +510,7 @@ fn lsp_import_assertions() {
#[test] #[test]
fn lsp_import_map_import_completions() { fn lsp_import_map_import_completions() {
let context = TestContextBuilder::new().build(); let context = TestContextBuilder::new().build();
let temp_dir = context.deno_dir(); let temp_dir = context.temp_dir();
temp_dir.write( temp_dir.write(
"import-map.json", "import-map.json",
r#"{ r#"{
@ -932,26 +932,27 @@ fn lsp_workspace_enable_paths() {
// we aren't actually writing anything to the tempdir in this test, but we // we aren't actually writing anything to the tempdir in this test, but we
// just need a legitimate file path on the host system so that logic that // just need a legitimate file path on the host system so that logic that
// tries to convert to and from the fs paths works on all env // tries to convert to and from the fs paths works on all env
let temp_dir = context.deno_dir(); let temp_dir = context.temp_dir();
let root_specifier = temp_dir.uri(); let root_specifier = temp_dir.uri();
let mut client = context.new_lsp_command().build(); let mut client = context.new_lsp_command().build();
client.initialize(|builder| { client.initialize_with_config(
builder |builder| {
.set_enable_paths(vec!["./worker".to_string()]) builder
.set_root_uri(root_specifier.clone()) .set_enable_paths(vec!["./worker".to_string()])
.set_workspace_folders(vec![lsp::WorkspaceFolder { .set_root_uri(root_specifier.clone())
uri: root_specifier.clone(), .set_workspace_folders(vec![lsp::WorkspaceFolder {
name: "project".to_string(), uri: root_specifier.clone(),
}]) name: "project".to_string(),
.set_deno_enable(false); }])
}); .set_deno_enable(false);
},
client.handle_configuration_request(json!([{ json!([{
"enable": false, "enable": false,
"enablePaths": ["./worker"], "enablePaths": ["./worker"],
}])); }]),
);
client.did_open(json!({ client.did_open(json!({
"textDocument": { "textDocument": {
@ -1216,7 +1217,7 @@ fn lsp_hover_change_mbc() {
#[test] #[test]
fn lsp_hover_closed_document() { fn lsp_hover_closed_document() {
let context = TestContextBuilder::new().build(); let context = TestContextBuilder::new().build();
let temp_dir = context.deno_dir(); let temp_dir = context.temp_dir();
temp_dir.write("a.ts", r#"export const a = "a";"#); temp_dir.write("a.ts", r#"export const a = "a";"#);
temp_dir.write("b.ts", r#"export * from "./a.ts";"#); temp_dir.write("b.ts", r#"export * from "./a.ts";"#);
temp_dir.write("c.ts", "import { a } from \"./b.ts\";\nconsole.log(a);\n"); temp_dir.write("c.ts", "import { a } from \"./b.ts\";\nconsole.log(a);\n");
@ -5188,7 +5189,7 @@ fn lsp_auto_discover_registry() {
#[test] #[test]
fn lsp_cache_location() { fn lsp_cache_location() {
let context = TestContextBuilder::new().use_http_server().build(); let context = TestContextBuilder::new().use_http_server().build();
let temp_dir = context.deno_dir(); let temp_dir = context.temp_dir();
let mut client = context.new_lsp_command().build(); let mut client = context.new_lsp_command().build();
client.initialize(|builder| { client.initialize(|builder| {
builder.set_cache(".cache").add_test_server_suggestions(); builder.set_cache(".cache").add_test_server_suggestions();
@ -5668,7 +5669,7 @@ fn lsp_diagnostics_refresh_dependents() {
assert_eq!(client.queue_len(), 0); assert_eq!(client.queue_len(), 0);
} }
#[derive(Deserialize)] #[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct PerformanceAverage { pub struct PerformanceAverage {
pub name: String, pub name: String,
@ -5676,7 +5677,7 @@ pub struct PerformanceAverage {
pub average_duration: u32, pub average_duration: u32,
} }
#[derive(Deserialize)] #[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
struct PerformanceAverages { struct PerformanceAverages {
averages: Vec<PerformanceAverage>, averages: Vec<PerformanceAverage>,
@ -5707,7 +5708,30 @@ fn lsp_performance() {
"deno/performance", "deno/performance",
json!(null), json!(null),
); );
assert_eq!(res.averages.len(), 13); let mut averages = res
.averages
.iter()
.map(|a| a.name.as_str())
.collect::<Vec<_>>();
averages.sort();
assert_eq!(
averages,
vec![
"did_open",
"hover",
"initialize",
"op_load",
"request",
"testing_update",
"update_cache",
"update_diagnostics_deps",
"update_diagnostics_lint",
"update_diagnostics_ts",
"update_import_map",
"update_registries",
"update_tsconfig",
]
);
client.shutdown(); client.shutdown();
} }
@ -5826,7 +5850,7 @@ fn lsp_format_mbc() {
#[test] #[test]
fn lsp_format_exclude_with_config() { fn lsp_format_exclude_with_config() {
let context = TestContextBuilder::new().build(); let context = TestContextBuilder::new().build();
let temp_dir = context.deno_dir(); let temp_dir = context.temp_dir();
temp_dir.write( temp_dir.write(
"deno.fmt.jsonc", "deno.fmt.jsonc",
@ -5879,7 +5903,7 @@ fn lsp_format_exclude_with_config() {
#[test] #[test]
fn lsp_format_exclude_default_config() { fn lsp_format_exclude_default_config() {
let context = TestContextBuilder::new().build(); let context = TestContextBuilder::new().build();
let temp_dir = context.deno_dir(); let temp_dir = context.temp_dir();
temp_dir.write( temp_dir.write(
"deno.fmt.jsonc", "deno.fmt.jsonc",
@ -6071,7 +6095,7 @@ fn lsp_format_markdown() {
#[test] #[test]
fn lsp_format_with_config() { fn lsp_format_with_config() {
let context = TestContextBuilder::new().build(); let context = TestContextBuilder::new().build();
let temp_dir = context.deno_dir(); let temp_dir = context.temp_dir();
temp_dir.write( temp_dir.write(
"deno.fmt.jsonc", "deno.fmt.jsonc",
r#"{ r#"{
@ -6231,32 +6255,32 @@ fn lsp_configuration_did_change() {
"settings": {} "settings": {}
}), }),
); );
let (id, method, _) = client.read_request::<Value>(); let request = json!([{
assert_eq!(method, "workspace/configuration"); "enable": true,
client.write_response( "codeLens": {
id, "implementations": true,
json!([{ "references": true
"enable": true, },
"codeLens": { "importMap": null,
"implementations": true, "lint": true,
"references": true "suggest": {
}, "autoImports": true,
"importMap": null, "completeFunctionCalls": false,
"lint": true, "names": true,
"suggest": { "paths": true,
"autoImports": true, "imports": {
"completeFunctionCalls": false, "hosts": {
"names": true, "http://localhost:4545/": true
"paths": true,
"imports": {
"hosts": {
"http://localhost:4545/": true
}
} }
}, }
"unstable": false },
}]), "unstable": false
); }]);
// one for the workspace
client.handle_configuration_request(request.clone());
// one for the specifier
client.handle_configuration_request(request);
let list = client.get_completion_list( let list = client.get_completion_list(
"file:///a/file.ts", "file:///a/file.ts",
(0, 46), (0, 46),
@ -6635,7 +6659,7 @@ console.log(snake_case);
#[test] #[test]
fn lsp_lint_with_config() { fn lsp_lint_with_config() {
let context = TestContextBuilder::new().build(); let context = TestContextBuilder::new().build();
let temp_dir = context.deno_dir(); let temp_dir = context.temp_dir();
temp_dir.write( temp_dir.write(
"deno.lint.jsonc", "deno.lint.jsonc",
@ -6676,7 +6700,7 @@ fn lsp_lint_with_config() {
#[test] #[test]
fn lsp_lint_exclude_with_config() { fn lsp_lint_exclude_with_config() {
let context = TestContextBuilder::new().build(); let context = TestContextBuilder::new().build();
let temp_dir = context.deno_dir(); let temp_dir = context.temp_dir();
temp_dir.write( temp_dir.write(
"deno.lint.jsonc", "deno.lint.jsonc",
@ -6814,7 +6838,7 @@ struct TestRunResponseParams {
#[test] #[test]
fn lsp_testing_api() { fn lsp_testing_api() {
let context = TestContextBuilder::new().build(); let context = TestContextBuilder::new().build();
let temp_dir = context.deno_dir(); let temp_dir = context.temp_dir();
let contents = r#" let contents = r#"
Deno.test({ Deno.test({

View file

@ -2874,7 +2874,7 @@ fn package_json_no_node_modules_dir_created() {
.add_npm_env_vars() .add_npm_env_vars()
.use_temp_cwd() .use_temp_cwd()
.build(); .build();
let temp_dir = context.deno_dir(); let temp_dir = context.temp_dir();
temp_dir.write("deno.json", "{}"); temp_dir.write("deno.json", "{}");
temp_dir.write("package.json", "{}"); temp_dir.write("package.json", "{}");
@ -2892,7 +2892,7 @@ fn node_modules_dir_no_npm_specifiers_no_dir_created() {
.add_npm_env_vars() .add_npm_env_vars()
.use_temp_cwd() .use_temp_cwd()
.build(); .build();
let temp_dir = context.deno_dir(); let temp_dir = context.temp_dir();
temp_dir.write("deno.json", "{}"); temp_dir.write("deno.json", "{}");
temp_dir.write("main.ts", ""); temp_dir.write("main.ts", "");

View file

@ -69,18 +69,6 @@ pub fn specifier_to_file_path(
} }
} }
/// Ensures a specifier that will definitely be a directory has a trailing slash.
pub fn ensure_directory_specifier(
mut specifier: ModuleSpecifier,
) -> ModuleSpecifier {
let path = specifier.path();
if !path.ends_with('/') {
let new_path = format!("{path}/");
specifier.set_path(&new_path);
}
specifier
}
/// Gets the parent of this module specifier. /// Gets the parent of this module specifier.
pub fn specifier_parent(specifier: &ModuleSpecifier) -> ModuleSpecifier { pub fn specifier_parent(specifier: &ModuleSpecifier) -> ModuleSpecifier {
let mut specifier = specifier.clone(); let mut specifier = specifier.clone();
@ -264,21 +252,6 @@ mod test {
} }
} }
#[test]
fn test_ensure_directory_specifier() {
run_test("file:///", "file:///");
run_test("file:///test", "file:///test/");
run_test("file:///test/", "file:///test/");
run_test("file:///test/other", "file:///test/other/");
run_test("file:///test/other/", "file:///test/other/");
fn run_test(specifier: &str, expected: &str) {
let result =
ensure_directory_specifier(ModuleSpecifier::parse(specifier).unwrap());
assert_eq!(result.to_string(), expected);
}
}
#[test] #[test]
fn test_specifier_parent() { fn test_specifier_parent() {
run_test("file:///", "file:///"); run_test("file:///", "file:///");

View file

@ -148,6 +148,11 @@ impl LspStdoutReader {
self.pending_messages.0.lock().len() self.pending_messages.0.lock().len()
} }
pub fn output_pending_messages(&self) {
let messages = self.pending_messages.0.lock();
eprintln!("{:?}", messages);
}
pub fn had_message(&self, is_match: impl Fn(&LspMessage) -> bool) -> bool { pub fn had_message(&self, is_match: impl Fn(&LspMessage) -> bool) -> bool {
self.read_messages.iter().any(&is_match) self.read_messages.iter().any(&is_match)
|| self.pending_messages.0.lock().iter().any(&is_match) || self.pending_messages.0.lock().iter().any(&is_match)
@ -453,6 +458,9 @@ impl LspClientBuilder {
self self
} }
// not deprecated, this is just here so you don't accidentally
// commit code with this enabled
#[deprecated]
pub fn print_stderr(&mut self) -> &mut Self { pub fn print_stderr(&mut self) -> &mut Self {
self.print_stderr = true; self.print_stderr = true;
self self
@ -541,6 +549,7 @@ impl LspClient {
} }
pub fn queue_len(&self) -> usize { pub fn queue_len(&self) -> usize {
self.reader.output_pending_messages();
self.reader.pending_len() self.reader.pending_len()
} }
@ -551,12 +560,26 @@ impl LspClient {
pub fn initialize( pub fn initialize(
&mut self, &mut self,
do_build: impl Fn(&mut InitializeParamsBuilder), do_build: impl Fn(&mut InitializeParamsBuilder),
) {
self.initialize_with_config(
do_build,
json!([{
"enable": true
}]),
)
}
pub fn initialize_with_config(
&mut self,
do_build: impl Fn(&mut InitializeParamsBuilder),
config: Value,
) { ) {
let mut builder = InitializeParamsBuilder::new(); let mut builder = InitializeParamsBuilder::new();
builder.set_root_uri(self.context.deno_dir().uri()); builder.set_root_uri(self.context.deno_dir().uri());
do_build(&mut builder); do_build(&mut builder);
self.write_request("initialize", builder.build()); self.write_request("initialize", builder.build());
self.write_notification("initialized", json!({})); self.write_notification("initialized", json!({}));
self.handle_configuration_request(config);
} }
pub fn did_open(&mut self, params: Value) -> CollectedDiagnostics { pub fn did_open(&mut self, params: Value) -> CollectedDiagnostics {