mirror of
https://github.com/denoland/deno.git
synced 2025-01-07 06:46:59 -05:00
fix(lsp): avoid calling client while holding lock (#18197)
This commit is contained in:
parent
a8013e7a0f
commit
d84da1520f
13 changed files with 674 additions and 575 deletions
|
@ -1,13 +1,11 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use deno_core::anyhow::anyhow;
|
||||
use deno_core::anyhow::bail;
|
||||
use deno_core::error::AnyError;
|
||||
use deno_core::futures::future;
|
||||
use deno_core::serde_json;
|
||||
use deno_core::serde_json::Value;
|
||||
use tower_lsp::lsp_types as lsp;
|
||||
|
@ -45,24 +43,57 @@ impl Client {
|
|||
Self(Arc::new(ReplClient))
|
||||
}
|
||||
|
||||
pub async fn publish_diagnostics(
|
||||
&self,
|
||||
uri: lsp::Url,
|
||||
diags: Vec<lsp::Diagnostic>,
|
||||
version: Option<i32>,
|
||||
) {
|
||||
self.0.publish_diagnostics(uri, diags, version).await;
|
||||
/// Gets additional methods that should only be called outside
|
||||
/// the LSP's lock to prevent deadlocking scenarios.
|
||||
pub fn when_outside_lsp_lock(&self) -> OutsideLockClient {
|
||||
OutsideLockClient(self.0.clone())
|
||||
}
|
||||
|
||||
pub async fn send_registry_state_notification(
|
||||
pub fn send_registry_state_notification(
|
||||
&self,
|
||||
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) {
|
||||
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(
|
||||
|
@ -100,211 +131,185 @@ impl Client {
|
|||
self.0.workspace_configuration().await
|
||||
}
|
||||
|
||||
pub async fn show_message(
|
||||
pub async fn publish_diagnostics(
|
||||
&self,
|
||||
message_type: lsp::MessageType,
|
||||
message: impl std::fmt::Display,
|
||||
uri: lsp::Url,
|
||||
diags: Vec<lsp::Diagnostic>,
|
||||
version: Option<i32>,
|
||||
) {
|
||||
self
|
||||
.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
|
||||
self.0.publish_diagnostics(uri, diags, version).await;
|
||||
}
|
||||
}
|
||||
|
||||
type AsyncReturn<T> = Pin<Box<dyn Future<Output = T> + 'static + Send>>;
|
||||
|
||||
#[async_trait]
|
||||
trait ClientTrait: Send + Sync {
|
||||
fn publish_diagnostics(
|
||||
async fn publish_diagnostics(
|
||||
&self,
|
||||
uri: lsp::Url,
|
||||
diagnostics: Vec<lsp::Diagnostic>,
|
||||
version: Option<i32>,
|
||||
) -> AsyncReturn<()>;
|
||||
fn send_registry_state_notification(
|
||||
);
|
||||
async fn send_registry_state_notification(
|
||||
&self,
|
||||
params: lsp_custom::RegistryStateNotificationParams,
|
||||
) -> AsyncReturn<()>;
|
||||
fn send_test_notification(&self, params: TestingNotification);
|
||||
fn specifier_configurations(
|
||||
);
|
||||
async fn send_test_notification(&self, params: TestingNotification);
|
||||
async fn specifier_configurations(
|
||||
&self,
|
||||
uris: Vec<lsp::Url>,
|
||||
) -> AsyncReturn<Result<Vec<Result<SpecifierSettings, AnyError>>, AnyError>>;
|
||||
fn workspace_configuration(&self) -> AsyncReturn<Result<Value, AnyError>>;
|
||||
fn show_message(
|
||||
&self,
|
||||
message_type: lsp::MessageType,
|
||||
text: String,
|
||||
) -> AsyncReturn<()>;
|
||||
fn register_capability(
|
||||
) -> Result<Vec<Result<SpecifierSettings, AnyError>>, AnyError>;
|
||||
async fn workspace_configuration(&self) -> Result<Value, AnyError>;
|
||||
async fn show_message(&self, message_type: lsp::MessageType, text: String);
|
||||
async fn register_capability(
|
||||
&self,
|
||||
registrations: Vec<lsp::Registration>,
|
||||
) -> AsyncReturn<Result<(), AnyError>>;
|
||||
) -> Result<(), AnyError>;
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct TowerClient(tower_lsp::Client);
|
||||
|
||||
#[async_trait]
|
||||
impl ClientTrait for TowerClient {
|
||||
fn publish_diagnostics(
|
||||
async fn publish_diagnostics(
|
||||
&self,
|
||||
uri: lsp::Url,
|
||||
diagnostics: Vec<lsp::Diagnostic>,
|
||||
version: Option<i32>,
|
||||
) -> AsyncReturn<()> {
|
||||
let client = self.0.clone();
|
||||
Box::pin(async move {
|
||||
client.publish_diagnostics(uri, diagnostics, version).await
|
||||
})
|
||||
) {
|
||||
self.0.publish_diagnostics(uri, diagnostics, version).await
|
||||
}
|
||||
|
||||
fn send_registry_state_notification(
|
||||
async fn send_registry_state_notification(
|
||||
&self,
|
||||
params: lsp_custom::RegistryStateNotificationParams,
|
||||
) -> AsyncReturn<()> {
|
||||
let client = self.0.clone();
|
||||
Box::pin(async move {
|
||||
client
|
||||
.send_notification::<lsp_custom::RegistryStateNotification>(params)
|
||||
.await
|
||||
})
|
||||
) {
|
||||
self
|
||||
.0
|
||||
.send_notification::<lsp_custom::RegistryStateNotification>(params)
|
||||
.await
|
||||
}
|
||||
|
||||
fn send_test_notification(&self, notification: TestingNotification) {
|
||||
let client = self.0.clone();
|
||||
tokio::task::spawn(async move {
|
||||
match notification {
|
||||
TestingNotification::Module(params) => {
|
||||
client
|
||||
.send_notification::<testing_lsp_custom::TestModuleNotification>(
|
||||
params,
|
||||
)
|
||||
.await
|
||||
}
|
||||
TestingNotification::DeleteModule(params) => client
|
||||
.send_notification::<testing_lsp_custom::TestModuleDeleteNotification>(
|
||||
async fn send_test_notification(&self, notification: TestingNotification) {
|
||||
match notification {
|
||||
TestingNotification::Module(params) => {
|
||||
self
|
||||
.0
|
||||
.send_notification::<testing_lsp_custom::TestModuleNotification>(
|
||||
params,
|
||||
)
|
||||
.await,
|
||||
TestingNotification::Progress(params) => client
|
||||
.await
|
||||
}
|
||||
TestingNotification::DeleteModule(params) => self
|
||||
.0
|
||||
.send_notification::<testing_lsp_custom::TestModuleDeleteNotification>(
|
||||
params,
|
||||
)
|
||||
.await,
|
||||
TestingNotification::Progress(params) => {
|
||||
self
|
||||
.0
|
||||
.send_notification::<testing_lsp_custom::TestRunProgressNotification>(
|
||||
params,
|
||||
)
|
||||
.await,
|
||||
.await
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn specifier_configurations(
|
||||
async fn specifier_configurations(
|
||||
&self,
|
||||
uris: Vec<lsp::Url>,
|
||||
) -> AsyncReturn<Result<Vec<Result<SpecifierSettings, AnyError>>, AnyError>>
|
||||
{
|
||||
let client = self.0.clone();
|
||||
Box::pin(async move {
|
||||
let config_response = client
|
||||
.configuration(
|
||||
uris
|
||||
.into_iter()
|
||||
.map(|uri| ConfigurationItem {
|
||||
scope_uri: Some(uri),
|
||||
section: Some(SETTINGS_SECTION.to_string()),
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(
|
||||
config_response
|
||||
) -> Result<Vec<Result<SpecifierSettings, AnyError>>, AnyError> {
|
||||
let config_response = self
|
||||
.0
|
||||
.configuration(
|
||||
uris
|
||||
.into_iter()
|
||||
.map(|value| {
|
||||
serde_json::from_value::<SpecifierSettings>(value).map_err(|err| {
|
||||
anyhow!("Error converting specifier settings: {}", err)
|
||||
})
|
||||
.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(),
|
||||
)
|
||||
}
|
||||
|
||||
fn workspace_configuration(&self) -> AsyncReturn<Result<Value, AnyError>> {
|
||||
let client = self.0.clone();
|
||||
Box::pin(async move {
|
||||
let config_response = client
|
||||
.configuration(vec![ConfigurationItem {
|
||||
scope_uri: None,
|
||||
section: Some(SETTINGS_SECTION.to_string()),
|
||||
}])
|
||||
.await;
|
||||
match config_response {
|
||||
Ok(value_vec) => match value_vec.get(0).cloned() {
|
||||
Some(value) => Ok(value),
|
||||
None => bail!("Missing response workspace configuration."),
|
||||
},
|
||||
Err(err) => {
|
||||
bail!("Error getting workspace configuration: {}", err)
|
||||
}
|
||||
async fn workspace_configuration(&self) -> Result<Value, AnyError> {
|
||||
let config_response = self
|
||||
.0
|
||||
.configuration(vec![ConfigurationItem {
|
||||
scope_uri: None,
|
||||
section: Some(SETTINGS_SECTION.to_string()),
|
||||
}])
|
||||
.await;
|
||||
match config_response {
|
||||
Ok(value_vec) => match value_vec.get(0).cloned() {
|
||||
Some(value) => Ok(value),
|
||||
None => bail!("Missing response workspace configuration."),
|
||||
},
|
||||
Err(err) => {
|
||||
bail!("Error getting workspace configuration: {}", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn show_message(
|
||||
async fn show_message(
|
||||
&self,
|
||||
message_type: lsp::MessageType,
|
||||
message: String,
|
||||
) -> AsyncReturn<()> {
|
||||
let client = self.0.clone();
|
||||
Box::pin(async move { client.show_message(message_type, message).await })
|
||||
) {
|
||||
self.0.show_message(message_type, message).await
|
||||
}
|
||||
|
||||
fn register_capability(
|
||||
async fn register_capability(
|
||||
&self,
|
||||
registrations: Vec<lsp::Registration>,
|
||||
) -> AsyncReturn<Result<(), AnyError>> {
|
||||
let client = self.0.clone();
|
||||
Box::pin(async move {
|
||||
client
|
||||
.register_capability(registrations)
|
||||
.await
|
||||
.map_err(|err| anyhow!("{}", err))
|
||||
})
|
||||
) -> Result<(), AnyError> {
|
||||
self
|
||||
.0
|
||||
.register_capability(registrations)
|
||||
.await
|
||||
.map_err(|err| anyhow!("{}", err))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct ReplClient;
|
||||
|
||||
#[async_trait]
|
||||
impl ClientTrait for ReplClient {
|
||||
fn publish_diagnostics(
|
||||
async fn publish_diagnostics(
|
||||
&self,
|
||||
_uri: lsp::Url,
|
||||
_diagnostics: Vec<lsp::Diagnostic>,
|
||||
_version: Option<i32>,
|
||||
) -> AsyncReturn<()> {
|
||||
Box::pin(future::ready(()))
|
||||
) {
|
||||
}
|
||||
|
||||
fn send_registry_state_notification(
|
||||
async fn send_registry_state_notification(
|
||||
&self,
|
||||
_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,
|
||||
uris: Vec<lsp::Url>,
|
||||
) -> AsyncReturn<Result<Vec<Result<SpecifierSettings, AnyError>>, AnyError>>
|
||||
{
|
||||
) -> Result<Vec<Result<SpecifierSettings, AnyError>>, AnyError> {
|
||||
// all specifiers are enabled for the REPL
|
||||
let settings = uris
|
||||
.into_iter()
|
||||
|
@ -315,27 +320,24 @@ impl ClientTrait for ReplClient {
|
|||
})
|
||||
})
|
||||
.collect();
|
||||
Box::pin(future::ready(Ok(settings)))
|
||||
Ok(settings)
|
||||
}
|
||||
|
||||
fn workspace_configuration(&self) -> AsyncReturn<Result<Value, AnyError>> {
|
||||
Box::pin(future::ready(Ok(
|
||||
serde_json::to_value(get_repl_workspace_settings()).unwrap(),
|
||||
)))
|
||||
async fn workspace_configuration(&self) -> Result<Value, AnyError> {
|
||||
Ok(serde_json::to_value(get_repl_workspace_settings()).unwrap())
|
||||
}
|
||||
|
||||
fn show_message(
|
||||
async fn show_message(
|
||||
&self,
|
||||
_message_type: lsp::MessageType,
|
||||
_message: String,
|
||||
) -> AsyncReturn<()> {
|
||||
Box::pin(future::ready(()))
|
||||
) {
|
||||
}
|
||||
|
||||
fn register_capability(
|
||||
async fn register_capability(
|
||||
&self,
|
||||
_registrations: Vec<lsp::Registration>,
|
||||
) -> AsyncReturn<Result<(), AnyError>> {
|
||||
Box::pin(future::ready(Ok(())))
|
||||
) -> Result<(), AnyError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,7 +48,7 @@ pub struct CompletionItemData {
|
|||
async fn check_auto_config_registry(
|
||||
url_str: &str,
|
||||
config: &ConfigSnapshot,
|
||||
client: Client,
|
||||
client: &Client,
|
||||
module_registries: &ModuleRegistry,
|
||||
) {
|
||||
// check to see if auto discovery is enabled
|
||||
|
@ -78,14 +78,12 @@ async fn check_auto_config_registry(
|
|||
// incompatible.
|
||||
// TODO(@kitsonk) clean up protocol when doing v2 of suggestions
|
||||
if suggestions {
|
||||
client
|
||||
.send_registry_state_notification(
|
||||
lsp_custom::RegistryStateNotificationParams {
|
||||
origin,
|
||||
suggestions,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
client.send_registry_state_notification(
|
||||
lsp_custom::RegistryStateNotificationParams {
|
||||
origin,
|
||||
suggestions,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -139,7 +137,7 @@ pub async fn get_import_completions(
|
|||
specifier: &ModuleSpecifier,
|
||||
position: &lsp::Position,
|
||||
config: &ConfigSnapshot,
|
||||
client: Client,
|
||||
client: &Client,
|
||||
module_registries: &ModuleRegistry,
|
||||
documents: &Documents,
|
||||
maybe_import_map: Option<Arc<ImportMap>>,
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
use super::client::Client;
|
||||
use super::logging::lsp_log;
|
||||
use crate::util::path::ensure_directory_specifier;
|
||||
use crate::util::path::specifier_to_file_path;
|
||||
use deno_core::error::AnyError;
|
||||
use deno_core::serde::Deserialize;
|
||||
|
@ -10,6 +8,7 @@ use deno_core::serde::Serialize;
|
|||
use deno_core::serde_json;
|
||||
use deno_core::serde_json::Value;
|
||||
use deno_core::ModuleSpecifier;
|
||||
use lsp::Url;
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
@ -226,7 +225,7 @@ impl Default for ImportCompletionSettings {
|
|||
|
||||
/// Deno language server specific settings that can be applied uniquely to a
|
||||
/// specifier.
|
||||
#[derive(Debug, Default, Clone, Deserialize)]
|
||||
#[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.
|
||||
|
@ -388,7 +387,7 @@ impl WorkspaceSettings {
|
|||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ConfigSnapshot {
|
||||
pub client_capabilities: ClientCapabilities,
|
||||
pub enabled_paths: HashMap<String, Vec<String>>,
|
||||
pub enabled_paths: HashMap<Url, Vec<Url>>,
|
||||
pub settings: Settings,
|
||||
}
|
||||
|
||||
|
@ -396,42 +395,33 @@ impl ConfigSnapshot {
|
|||
/// Determine if the provided specifier is enabled or not.
|
||||
pub fn specifier_enabled(&self, specifier: &ModuleSpecifier) -> bool {
|
||||
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() {
|
||||
if specifier_str.starts_with(workspace) {
|
||||
if specifier_str.starts_with(workspace.as_str()) {
|
||||
return enabled_paths
|
||||
.iter()
|
||||
.any(|path| specifier_str.starts_with(path));
|
||||
.any(|path| specifier_str.starts_with(path.as_str()));
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some((_, SpecifierSettings { enable, .. })) =
|
||||
self.settings.specifiers.get(specifier)
|
||||
{
|
||||
*enable
|
||||
if let Some(settings) = self.settings.specifiers.get(specifier) {
|
||||
settings.enable
|
||||
} else {
|
||||
self.settings.workspace.enable
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SpecifierWithClientUri {
|
||||
pub specifier: ModuleSpecifier,
|
||||
pub client_uri: ModuleSpecifier,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct Settings {
|
||||
pub specifiers:
|
||||
BTreeMap<ModuleSpecifier, (ModuleSpecifier, SpecifierSettings)>,
|
||||
pub specifiers: BTreeMap<ModuleSpecifier, SpecifierSettings>,
|
||||
pub workspace: WorkspaceSettings,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Config {
|
||||
pub client_capabilities: ClientCapabilities,
|
||||
enabled_paths: HashMap<String, Vec<String>>,
|
||||
enabled_paths: HashMap<Url, Vec<Url>>,
|
||||
pub root_uri: Option<ModuleSpecifier>,
|
||||
settings: Settings,
|
||||
pub workspace_folders: Option<Vec<(ModuleSpecifier, lsp::WorkspaceFolder)>>,
|
||||
|
@ -478,12 +468,12 @@ impl Config {
|
|||
|
||||
pub fn specifier_enabled(&self, specifier: &ModuleSpecifier) -> bool {
|
||||
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() {
|
||||
if specifier_str.starts_with(workspace) {
|
||||
if specifier_str.starts_with(workspace.as_str()) {
|
||||
return enabled_paths
|
||||
.iter()
|
||||
.any(|path| specifier_str.starts_with(path));
|
||||
.any(|path| specifier_str.starts_with(path.as_str()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -491,7 +481,7 @@ impl Config {
|
|||
.settings
|
||||
.specifiers
|
||||
.get(specifier)
|
||||
.map(|(_, s)| s.enable)
|
||||
.map(|settings| settings.enable)
|
||||
.unwrap_or_else(|| self.settings.workspace.enable)
|
||||
}
|
||||
|
||||
|
@ -500,7 +490,7 @@ impl Config {
|
|||
.settings
|
||||
.specifiers
|
||||
.get(specifier)
|
||||
.map(|(_, s)| s.code_lens.test)
|
||||
.map(|settings| settings.code_lens.test)
|
||||
.unwrap_or_else(|| self.settings.workspace.code_lens.test);
|
||||
value
|
||||
}
|
||||
|
@ -554,13 +544,15 @@ impl Config {
|
|||
|
||||
/// Given the configured workspaces or root URI and the their settings,
|
||||
/// 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() {
|
||||
let mut touched = false;
|
||||
for (workspace, folder) in workspace_folders {
|
||||
if let Ok(settings) = client.specifier_configuration(&folder.uri).await
|
||||
{
|
||||
if self.update_enabled_paths_entry(workspace, settings.enable_paths) {
|
||||
for (workspace, _) in workspace_folders {
|
||||
if let Some(settings) = self.settings.specifiers.get(&workspace) {
|
||||
if self.update_enabled_paths_entry(
|
||||
workspace,
|
||||
settings.enable_paths.clone(),
|
||||
) {
|
||||
touched = true;
|
||||
}
|
||||
}
|
||||
|
@ -582,8 +574,6 @@ impl Config {
|
|||
workspace: ModuleSpecifier,
|
||||
enabled_paths: Vec<String>,
|
||||
) -> bool {
|
||||
let workspace = ensure_directory_specifier(workspace);
|
||||
let key = workspace.to_string();
|
||||
let mut touched = false;
|
||||
if !enabled_paths.is_empty() {
|
||||
if let Ok(workspace_path) = specifier_to_file_path(&workspace) {
|
||||
|
@ -592,7 +582,7 @@ impl Config {
|
|||
let fs_path = workspace_path.join(path);
|
||||
match ModuleSpecifier::from_file_path(fs_path) {
|
||||
Ok(path_uri) => {
|
||||
paths.push(path_uri.to_string());
|
||||
paths.push(path_uri);
|
||||
}
|
||||
Err(_) => {
|
||||
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() {
|
||||
touched = true;
|
||||
self.enabled_paths.insert(key, paths);
|
||||
self.enabled_paths.insert(workspace.clone(), paths);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
touched = true;
|
||||
self.enabled_paths.remove(&key);
|
||||
self.enabled_paths.remove(&workspace);
|
||||
}
|
||||
touched
|
||||
}
|
||||
|
||||
pub fn get_specifiers_with_client_uris(&self) -> Vec<SpecifierWithClientUri> {
|
||||
self
|
||||
.settings
|
||||
.specifiers
|
||||
.iter()
|
||||
.map(|(s, (u, _))| SpecifierWithClientUri {
|
||||
specifier: s.clone(),
|
||||
client_uri: u.clone(),
|
||||
})
|
||||
.collect()
|
||||
pub fn get_specifiers(&self) -> Vec<ModuleSpecifier> {
|
||||
self.settings.specifiers.keys().cloned().collect()
|
||||
}
|
||||
|
||||
pub fn set_specifier_settings(
|
||||
&mut self,
|
||||
specifier: ModuleSpecifier,
|
||||
client_uri: ModuleSpecifier,
|
||||
settings: SpecifierSettings,
|
||||
) {
|
||||
self
|
||||
.settings
|
||||
.specifiers
|
||||
.insert(specifier, (client_uri, settings));
|
||||
) -> bool {
|
||||
if let Some(existing) = self.settings.specifiers.get(&specifier) {
|
||||
if *existing == settings {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
self.settings.specifiers.insert(specifier, settings);
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -678,8 +663,8 @@ mod tests {
|
|||
assert!(!config.specifier_enabled(&specifier_b));
|
||||
let mut enabled_paths = HashMap::new();
|
||||
enabled_paths.insert(
|
||||
"file:///project/".to_string(),
|
||||
vec!["file:///project/worker/".to_string()],
|
||||
Url::parse("file:///project/").unwrap(),
|
||||
vec![Url::parse("file:///project/worker/").unwrap()],
|
||||
);
|
||||
config.enabled_paths = enabled_paths;
|
||||
assert!(config.specifier_enabled(&specifier_a));
|
||||
|
|
|
@ -88,6 +88,7 @@ impl DiagnosticsPublisher {
|
|||
|
||||
self
|
||||
.client
|
||||
.when_outside_lsp_lock()
|
||||
.publish_diagnostics(specifier, version_diagnostics.clone(), version)
|
||||
.await;
|
||||
}
|
||||
|
@ -1177,14 +1178,11 @@ let c: number = "a";
|
|||
let mut disabled_config = mock_config();
|
||||
disabled_config.settings.specifiers.insert(
|
||||
specifier.clone(),
|
||||
(
|
||||
specifier.clone(),
|
||||
SpecifierSettings {
|
||||
enable: false,
|
||||
enable_paths: Vec::new(),
|
||||
code_lens: Default::default(),
|
||||
},
|
||||
),
|
||||
SpecifierSettings {
|
||||
enable: false,
|
||||
enable_paths: Vec::new(),
|
||||
code_lens: Default::default(),
|
||||
},
|
||||
);
|
||||
|
||||
let diagnostics = generate_lint_diagnostics(
|
||||
|
|
|
@ -1184,12 +1184,8 @@ impl Documents {
|
|||
hasher.write_str(&import_map.to_json());
|
||||
hasher.write_str(import_map.base_url().as_str());
|
||||
}
|
||||
if let Some(jsx_config) = maybe_jsx_config {
|
||||
hasher.write_hashable(&jsx_config);
|
||||
}
|
||||
if let Some(deps) = maybe_package_json_deps {
|
||||
hasher.write_hashable(&deps);
|
||||
}
|
||||
hasher.write_hashable(&maybe_jsx_config);
|
||||
hasher.write_hashable(&maybe_package_json_deps);
|
||||
hasher.finish()
|
||||
}
|
||||
|
||||
|
|
|
@ -75,6 +75,7 @@ use crate::cache::HttpCache;
|
|||
use crate::file_fetcher::FileFetcher;
|
||||
use crate::graph_util;
|
||||
use crate::http_util::HttpClient;
|
||||
use crate::lsp::urls::LspUrlKind;
|
||||
use crate::npm::create_npm_fs_resolver;
|
||||
use crate::npm::NpmCache;
|
||||
use crate::npm::NpmPackageResolver;
|
||||
|
@ -84,7 +85,6 @@ use crate::proc_state::ProcState;
|
|||
use crate::tools::fmt::format_file;
|
||||
use crate::tools::fmt::format_parsed_source;
|
||||
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::progress_bar::ProgressBar;
|
||||
use crate::util::progress_bar::ProgressBarStyle;
|
||||
|
@ -214,8 +214,7 @@ impl LanguageServer {
|
|||
.read()
|
||||
.await
|
||||
.client
|
||||
.show_message(MessageType::WARNING, err)
|
||||
.await;
|
||||
.show_message(MessageType::WARNING, err);
|
||||
return Err(LspError::internal_error());
|
||||
}
|
||||
}
|
||||
|
@ -233,8 +232,7 @@ impl LanguageServer {
|
|||
.read()
|
||||
.await
|
||||
.client
|
||||
.show_message(MessageType::WARNING, err)
|
||||
.await;
|
||||
.show_message(MessageType::WARNING, err);
|
||||
}
|
||||
// do npm resolution in a write—we should have everything
|
||||
// cached by this point anyway
|
||||
|
@ -323,6 +321,92 @@ impl LanguageServer {
|
|||
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(
|
||||
|
@ -923,7 +1007,7 @@ impl Inner {
|
|||
tsconfig.merge(&unstable_libs);
|
||||
}
|
||||
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
|
||||
.ts_server
|
||||
|
@ -978,8 +1062,7 @@ impl Inner {
|
|||
// sometimes this root uri may not have a trailing slash, so force it to
|
||||
self.config.root_uri = params
|
||||
.root_uri
|
||||
.map(|s| self.url_map.normalize_url(&s))
|
||||
.map(ensure_directory_specifier);
|
||||
.map(|s| self.url_map.normalize_url(&s, LspUrlKind::Folder));
|
||||
|
||||
if let Some(value) = params.initialization_options {
|
||||
self.config.set_workspace_settings(value).map_err(|err| {
|
||||
|
@ -990,7 +1073,12 @@ impl Inner {
|
|||
self.config.workspace_folders = params.workspace_folders.map(|folders| {
|
||||
folders
|
||||
.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()
|
||||
});
|
||||
self.config.update_capabilities(¶ms.capabilities);
|
||||
|
@ -999,16 +1087,16 @@ impl Inner {
|
|||
self.update_debug_flag();
|
||||
// Check to see if we need to change the cache path
|
||||
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() {
|
||||
self.client.show_message(MessageType::WARNING, err).await;
|
||||
self.client.show_message(MessageType::WARNING, err);
|
||||
}
|
||||
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 {
|
||||
self.client.show_message(MessageType::WARNING, err).await;
|
||||
self.client.show_message(MessageType::WARNING, err);
|
||||
}
|
||||
|
||||
if capabilities.code_action_provider.is_some() {
|
||||
|
@ -1025,20 +1113,14 @@ impl Inner {
|
|||
|
||||
// Check to see if we need to setup the import map
|
||||
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
|
||||
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.performance.measure(mark);
|
||||
|
@ -1049,47 +1131,14 @@ impl Inner {
|
|||
})
|
||||
}
|
||||
|
||||
async fn initialized(&mut self, _: InitializedParams) {
|
||||
if self
|
||||
.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),
|
||||
}],
|
||||
};
|
||||
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.");
|
||||
fn refresh_documents_config(&mut self) {
|
||||
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(),
|
||||
);
|
||||
}
|
||||
|
||||
async fn shutdown(&self) -> LspResult<()> {
|
||||
|
@ -1130,7 +1179,9 @@ impl Inner {
|
|||
|
||||
async fn did_change(&mut self, params: DidChangeTextDocumentParams) {
|
||||
let mark = self.performance.mark("did_change", Some(¶ms));
|
||||
let specifier = self.url_map.normalize_url(¶ms.text_document.uri);
|
||||
let specifier = self
|
||||
.url_map
|
||||
.normalize_url(¶ms.text_document.uri, LspUrlKind::File);
|
||||
match self.documents.change(
|
||||
&specifier,
|
||||
params.text_document.version,
|
||||
|
@ -1166,7 +1217,9 @@ impl Inner {
|
|||
// already managed by the language service
|
||||
return;
|
||||
}
|
||||
let specifier = self.url_map.normalize_url(¶ms.text_document.uri);
|
||||
let specifier = self
|
||||
.url_map
|
||||
.normalize_url(¶ms.text_document.uri, LspUrlKind::File);
|
||||
|
||||
if let Err(err) = self.documents.close(&specifier) {
|
||||
error!("{}", err);
|
||||
|
@ -1206,31 +1259,25 @@ impl Inner {
|
|||
|
||||
self.update_debug_flag();
|
||||
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 {
|
||||
self.client.show_message(MessageType::WARNING, err).await;
|
||||
self.client.show_message(MessageType::WARNING, err);
|
||||
}
|
||||
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() {
|
||||
self.client.show_message(MessageType::WARNING, err).await;
|
||||
self.client.show_message(MessageType::WARNING, err);
|
||||
}
|
||||
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 {
|
||||
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();
|
||||
|
||||
self.send_diagnostics_update();
|
||||
self.send_testing_update();
|
||||
|
@ -1247,17 +1294,17 @@ impl Inner {
|
|||
let changes: HashSet<Url> = params
|
||||
.changes
|
||||
.iter()
|
||||
.map(|f| self.url_map.normalize_url(&f.uri))
|
||||
.map(|f| self.url_map.normalize_url(&f.uri, LspUrlKind::File))
|
||||
.collect();
|
||||
|
||||
// if the current deno.json has changed, we need to reload it
|
||||
if let Some(config_file) = &self.maybe_config_file {
|
||||
if changes.contains(&config_file.specifier) {
|
||||
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 {
|
||||
self.client.show_message(MessageType::WARNING, err).await;
|
||||
self.client.show_message(MessageType::WARNING, err);
|
||||
}
|
||||
touched = true;
|
||||
}
|
||||
|
@ -1266,7 +1313,7 @@ impl Inner {
|
|||
// always update the package json if the deno config changes
|
||||
if touched || changes.contains(&package_json.specifier()) {
|
||||
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;
|
||||
}
|
||||
|
@ -1276,19 +1323,13 @@ impl Inner {
|
|||
if let Some(import_map_uri) = &self.maybe_import_map_uri {
|
||||
if touched || changes.contains(import_map_uri) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
if touched {
|
||||
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();
|
||||
self.refresh_npm_specifiers().await;
|
||||
self.diagnostics_server.invalidate_all();
|
||||
self.restart_ts_server().await;
|
||||
|
@ -1298,18 +1339,20 @@ impl Inner {
|
|||
self.performance.measure(mark);
|
||||
}
|
||||
|
||||
async fn did_change_workspace_folders(
|
||||
fn did_change_workspace_folders(
|
||||
&mut self,
|
||||
params: DidChangeWorkspaceFoldersParams,
|
||||
) {
|
||||
let mark = self
|
||||
.performance
|
||||
.mark("did_change_workspace_folders", Some(¶ms));
|
||||
let mut workspace_folders = params
|
||||
.event
|
||||
.added
|
||||
.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)>>();
|
||||
if let Some(current_folders) = &self.config.workspace_folders {
|
||||
for (specifier, folder) in current_folders {
|
||||
|
@ -1323,14 +1366,15 @@ impl Inner {
|
|||
}
|
||||
|
||||
self.config.workspace_folders = Some(workspace_folders);
|
||||
self.performance.measure(mark);
|
||||
}
|
||||
|
||||
async fn document_symbol(
|
||||
&self,
|
||||
params: DocumentSymbolParams,
|
||||
) -> LspResult<Option<DocumentSymbolResponse>> {
|
||||
let specifier = self.url_map.normalize_url(¶ms.text_document.uri);
|
||||
let specifier = self
|
||||
.url_map
|
||||
.normalize_url(¶ms.text_document.uri, LspUrlKind::File);
|
||||
if !self.is_diagnosable(&specifier)
|
||||
|| !self.config.specifier_enabled(&specifier)
|
||||
{
|
||||
|
@ -1368,7 +1412,9 @@ impl Inner {
|
|||
&self,
|
||||
params: DocumentFormattingParams,
|
||||
) -> LspResult<Option<Vec<TextEdit>>> {
|
||||
let specifier = self.url_map.normalize_url(¶ms.text_document.uri);
|
||||
let specifier = self
|
||||
.url_map
|
||||
.normalize_url(¶ms.text_document.uri, LspUrlKind::File);
|
||||
let document = match self.documents.get(&specifier) {
|
||||
Some(doc) if doc.is_open() => doc,
|
||||
_ => return Ok(None),
|
||||
|
@ -1425,15 +1471,16 @@ impl Inner {
|
|||
Ok(Some(text_edits))
|
||||
}
|
||||
} 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)
|
||||
}
|
||||
}
|
||||
|
||||
async fn hover(&self, params: HoverParams) -> LspResult<Option<Hover>> {
|
||||
let specifier = self
|
||||
.url_map
|
||||
.normalize_url(¶ms.text_document_position_params.text_document.uri);
|
||||
let specifier = self.url_map.normalize_url(
|
||||
¶ms.text_document_position_params.text_document.uri,
|
||||
LspUrlKind::File,
|
||||
);
|
||||
if !self.is_diagnosable(&specifier)
|
||||
|| !self.config.specifier_enabled(&specifier)
|
||||
{
|
||||
|
@ -1518,7 +1565,9 @@ impl Inner {
|
|||
&self,
|
||||
params: CodeActionParams,
|
||||
) -> LspResult<Option<CodeActionResponse>> {
|
||||
let specifier = self.url_map.normalize_url(¶ms.text_document.uri);
|
||||
let specifier = self
|
||||
.url_map
|
||||
.normalize_url(¶ms.text_document.uri, LspUrlKind::File);
|
||||
if !self.is_diagnosable(&specifier)
|
||||
|| !self.config.specifier_enabled(&specifier)
|
||||
{
|
||||
|
@ -1778,7 +1827,9 @@ impl Inner {
|
|||
&self,
|
||||
params: CodeLensParams,
|
||||
) -> LspResult<Option<Vec<CodeLens>>> {
|
||||
let specifier = self.url_map.normalize_url(¶ms.text_document.uri);
|
||||
let specifier = self
|
||||
.url_map
|
||||
.normalize_url(¶ms.text_document.uri, LspUrlKind::File);
|
||||
if !self.is_diagnosable(&specifier)
|
||||
|| !self.config.specifier_enabled(&specifier)
|
||||
|| !(self.config.get_workspace_settings().enabled_code_lens()
|
||||
|
@ -1838,9 +1889,10 @@ impl Inner {
|
|||
&self,
|
||||
params: DocumentHighlightParams,
|
||||
) -> LspResult<Option<Vec<DocumentHighlight>>> {
|
||||
let specifier = self
|
||||
.url_map
|
||||
.normalize_url(¶ms.text_document_position_params.text_document.uri);
|
||||
let specifier = self.url_map.normalize_url(
|
||||
¶ms.text_document_position_params.text_document.uri,
|
||||
LspUrlKind::File,
|
||||
);
|
||||
if !self.is_diagnosable(&specifier)
|
||||
|| !self.config.specifier_enabled(&specifier)
|
||||
{
|
||||
|
@ -1882,9 +1934,10 @@ impl Inner {
|
|||
&self,
|
||||
params: ReferenceParams,
|
||||
) -> LspResult<Option<Vec<Location>>> {
|
||||
let specifier = self
|
||||
.url_map
|
||||
.normalize_url(¶ms.text_document_position.text_document.uri);
|
||||
let specifier = self.url_map.normalize_url(
|
||||
¶ms.text_document_position.text_document.uri,
|
||||
LspUrlKind::File,
|
||||
);
|
||||
if !self.is_diagnosable(&specifier)
|
||||
|| !self.config.specifier_enabled(&specifier)
|
||||
{
|
||||
|
@ -1938,9 +1991,10 @@ impl Inner {
|
|||
&self,
|
||||
params: GotoDefinitionParams,
|
||||
) -> LspResult<Option<GotoDefinitionResponse>> {
|
||||
let specifier = self
|
||||
.url_map
|
||||
.normalize_url(¶ms.text_document_position_params.text_document.uri);
|
||||
let specifier = self.url_map.normalize_url(
|
||||
¶ms.text_document_position_params.text_document.uri,
|
||||
LspUrlKind::File,
|
||||
);
|
||||
if !self.is_diagnosable(&specifier)
|
||||
|| !self.config.specifier_enabled(&specifier)
|
||||
{
|
||||
|
@ -1977,9 +2031,10 @@ impl Inner {
|
|||
&self,
|
||||
params: GotoTypeDefinitionParams,
|
||||
) -> LspResult<Option<GotoTypeDefinitionResponse>> {
|
||||
let specifier = self
|
||||
.url_map
|
||||
.normalize_url(¶ms.text_document_position_params.text_document.uri);
|
||||
let specifier = self.url_map.normalize_url(
|
||||
¶ms.text_document_position_params.text_document.uri,
|
||||
LspUrlKind::File,
|
||||
);
|
||||
if !self.is_diagnosable(&specifier)
|
||||
|| !self.config.specifier_enabled(&specifier)
|
||||
{
|
||||
|
@ -2024,9 +2079,10 @@ impl Inner {
|
|||
&self,
|
||||
params: CompletionParams,
|
||||
) -> LspResult<Option<CompletionResponse>> {
|
||||
let specifier = self
|
||||
.url_map
|
||||
.normalize_url(¶ms.text_document_position.text_document.uri);
|
||||
let specifier = self.url_map.normalize_url(
|
||||
¶ms.text_document_position.text_document.uri,
|
||||
LspUrlKind::File,
|
||||
);
|
||||
if !self.is_diagnosable(&specifier)
|
||||
|| !self.config.specifier_enabled(&specifier)
|
||||
{
|
||||
|
@ -2043,7 +2099,7 @@ impl Inner {
|
|||
&specifier,
|
||||
¶ms.text_document_position.position,
|
||||
&self.config.snapshot(),
|
||||
self.client.clone(),
|
||||
&self.client,
|
||||
&self.module_registries,
|
||||
&self.documents,
|
||||
self.maybe_import_map.clone(),
|
||||
|
@ -2183,9 +2239,10 @@ impl Inner {
|
|||
&self,
|
||||
params: GotoImplementationParams,
|
||||
) -> LspResult<Option<GotoImplementationResponse>> {
|
||||
let specifier = self
|
||||
.url_map
|
||||
.normalize_url(¶ms.text_document_position_params.text_document.uri);
|
||||
let specifier = self.url_map.normalize_url(
|
||||
¶ms.text_document_position_params.text_document.uri,
|
||||
LspUrlKind::File,
|
||||
);
|
||||
if !self.is_diagnosable(&specifier)
|
||||
|| !self.config.specifier_enabled(&specifier)
|
||||
{
|
||||
|
@ -2229,7 +2286,9 @@ impl Inner {
|
|||
&self,
|
||||
params: FoldingRangeParams,
|
||||
) -> LspResult<Option<Vec<FoldingRange>>> {
|
||||
let specifier = self.url_map.normalize_url(¶ms.text_document.uri);
|
||||
let specifier = self
|
||||
.url_map
|
||||
.normalize_url(¶ms.text_document.uri, LspUrlKind::File);
|
||||
if !self.is_diagnosable(&specifier)
|
||||
|| !self.config.specifier_enabled(&specifier)
|
||||
{
|
||||
|
@ -2273,7 +2332,9 @@ impl Inner {
|
|||
&self,
|
||||
params: CallHierarchyIncomingCallsParams,
|
||||
) -> LspResult<Option<Vec<CallHierarchyIncomingCall>>> {
|
||||
let specifier = self.url_map.normalize_url(¶ms.item.uri);
|
||||
let specifier = self
|
||||
.url_map
|
||||
.normalize_url(¶ms.item.uri, LspUrlKind::File);
|
||||
if !self.is_diagnosable(&specifier)
|
||||
|| !self.config.specifier_enabled(&specifier)
|
||||
{
|
||||
|
@ -2319,7 +2380,9 @@ impl Inner {
|
|||
&self,
|
||||
params: CallHierarchyOutgoingCallsParams,
|
||||
) -> LspResult<Option<Vec<CallHierarchyOutgoingCall>>> {
|
||||
let specifier = self.url_map.normalize_url(¶ms.item.uri);
|
||||
let specifier = self
|
||||
.url_map
|
||||
.normalize_url(¶ms.item.uri, LspUrlKind::File);
|
||||
if !self.is_diagnosable(&specifier)
|
||||
|| !self.config.specifier_enabled(&specifier)
|
||||
{
|
||||
|
@ -2366,9 +2429,10 @@ impl Inner {
|
|||
&self,
|
||||
params: CallHierarchyPrepareParams,
|
||||
) -> LspResult<Option<Vec<CallHierarchyItem>>> {
|
||||
let specifier = self
|
||||
.url_map
|
||||
.normalize_url(¶ms.text_document_position_params.text_document.uri);
|
||||
let specifier = self.url_map.normalize_url(
|
||||
¶ms.text_document_position_params.text_document.uri,
|
||||
LspUrlKind::File,
|
||||
);
|
||||
if !self.is_diagnosable(&specifier)
|
||||
|| !self.config.specifier_enabled(&specifier)
|
||||
{
|
||||
|
@ -2434,9 +2498,10 @@ impl Inner {
|
|||
&self,
|
||||
params: RenameParams,
|
||||
) -> LspResult<Option<WorkspaceEdit>> {
|
||||
let specifier = self
|
||||
.url_map
|
||||
.normalize_url(¶ms.text_document_position.text_document.uri);
|
||||
let specifier = self.url_map.normalize_url(
|
||||
¶ms.text_document_position.text_document.uri,
|
||||
LspUrlKind::File,
|
||||
);
|
||||
if !self.is_diagnosable(&specifier)
|
||||
|| !self.config.specifier_enabled(&specifier)
|
||||
{
|
||||
|
@ -2486,7 +2551,9 @@ impl Inner {
|
|||
&self,
|
||||
params: SelectionRangeParams,
|
||||
) -> LspResult<Option<Vec<SelectionRange>>> {
|
||||
let specifier = self.url_map.normalize_url(¶ms.text_document.uri);
|
||||
let specifier = self
|
||||
.url_map
|
||||
.normalize_url(¶ms.text_document.uri, LspUrlKind::File);
|
||||
if !self.is_diagnosable(&specifier)
|
||||
|| !self.config.specifier_enabled(&specifier)
|
||||
{
|
||||
|
@ -2524,7 +2591,9 @@ impl Inner {
|
|||
&self,
|
||||
params: SemanticTokensParams,
|
||||
) -> LspResult<Option<SemanticTokensResult>> {
|
||||
let specifier = self.url_map.normalize_url(¶ms.text_document.uri);
|
||||
let specifier = self
|
||||
.url_map
|
||||
.normalize_url(¶ms.text_document.uri, LspUrlKind::File);
|
||||
if !self.is_diagnosable(&specifier)
|
||||
|| !self.config.specifier_enabled(&specifier)
|
||||
{
|
||||
|
@ -2566,7 +2635,9 @@ impl Inner {
|
|||
&self,
|
||||
params: SemanticTokensRangeParams,
|
||||
) -> LspResult<Option<SemanticTokensRangeResult>> {
|
||||
let specifier = self.url_map.normalize_url(¶ms.text_document.uri);
|
||||
let specifier = self
|
||||
.url_map
|
||||
.normalize_url(¶ms.text_document.uri, LspUrlKind::File);
|
||||
if !self.is_diagnosable(&specifier)
|
||||
|| !self.config.specifier_enabled(&specifier)
|
||||
{
|
||||
|
@ -2609,9 +2680,10 @@ impl Inner {
|
|||
&self,
|
||||
params: SignatureHelpParams,
|
||||
) -> LspResult<Option<SignatureHelp>> {
|
||||
let specifier = self
|
||||
.url_map
|
||||
.normalize_url(¶ms.text_document_position_params.text_document.uri);
|
||||
let specifier = self.url_map.normalize_url(
|
||||
¶ms.text_document_position_params.text_document.uri,
|
||||
LspUrlKind::File,
|
||||
);
|
||||
if !self.is_diagnosable(&specifier)
|
||||
|| !self.config.specifier_enabled(&specifier)
|
||||
{
|
||||
|
@ -2728,8 +2800,61 @@ impl tower_lsp::LanguageServer for LanguageServer {
|
|||
language_server.initialize(params).await
|
||||
}
|
||||
|
||||
async fn initialized(&self, params: InitializedParams) {
|
||||
self.0.write().await.initialized(params).await
|
||||
async fn initialized(&self, _: InitializedParams) {
|
||||
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<()> {
|
||||
|
@ -2744,11 +2869,12 @@ impl tower_lsp::LanguageServer for LanguageServer {
|
|||
return;
|
||||
}
|
||||
|
||||
let (client, uri, specifier, had_specifier_settings) = {
|
||||
let (client, client_uri, specifier, had_specifier_settings) = {
|
||||
let mut inner = self.0.write().await;
|
||||
let client = inner.client.clone();
|
||||
let uri = params.text_document.uri.clone();
|
||||
let specifier = inner.url_map.normalize_url(&uri);
|
||||
let client_uri = params.text_document.uri.clone();
|
||||
let specifier =
|
||||
inner.url_map.normalize_url(&client_uri, LspUrlKind::File);
|
||||
let document = inner.did_open(&specifier, params).await;
|
||||
let has_specifier_settings =
|
||||
inner.config.has_specifier_settings(&specifier);
|
||||
|
@ -2762,39 +2888,38 @@ impl tower_lsp::LanguageServer for LanguageServer {
|
|||
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
|
||||
// they haven't been asked for yet on its own time
|
||||
// they haven't been asked for yet
|
||||
if !had_specifier_settings {
|
||||
let language_server = self.clone();
|
||||
tokio::spawn(async move {
|
||||
let response = client.specifier_configuration(&uri).await;
|
||||
let mut inner = language_server.0.write().await;
|
||||
match response {
|
||||
Ok(specifier_settings) => {
|
||||
// now update the config and send a diagnostics update
|
||||
inner.config.set_specifier_settings(
|
||||
specifier.clone(),
|
||||
uri,
|
||||
specifier_settings,
|
||||
);
|
||||
}
|
||||
Err(err) => {
|
||||
error!("{}", err);
|
||||
}
|
||||
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);
|
||||
ls.config.update_enabled_paths();
|
||||
}
|
||||
if inner
|
||||
.documents
|
||||
.get(&specifier)
|
||||
.map(|d| d.is_diagnosable())
|
||||
.unwrap_or(false)
|
||||
{
|
||||
inner.send_diagnostics_update();
|
||||
inner.send_testing_update();
|
||||
Err(err) => {
|
||||
error!("{}", err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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,
|
||||
params: DidChangeConfigurationParams,
|
||||
) {
|
||||
let (has_workspace_capability, client, specifiers, mark) = {
|
||||
let inner = self.0.write().await;
|
||||
let mark = inner
|
||||
.performance
|
||||
.mark("did_change_configuration", Some(¶ms));
|
||||
|
||||
let specifiers =
|
||||
if inner.config.client_capabilities.workspace_configuration {
|
||||
Some(inner.config.get_specifiers_with_client_uris())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let (mark, has_workspace_capability, client) = {
|
||||
let inner = self.0.read().await;
|
||||
(
|
||||
inner
|
||||
.performance
|
||||
.mark("did_change_configuration", Some(¶ms)),
|
||||
inner.config.client_capabilities.workspace_configuration,
|
||||
inner.client.clone(),
|
||||
specifiers,
|
||||
mark,
|
||||
)
|
||||
};
|
||||
|
||||
// start retrieving all the specifiers' settings outside the lock on its own
|
||||
// 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();
|
||||
}
|
||||
});
|
||||
}
|
||||
self.refresh_specifiers_from_client().await;
|
||||
|
||||
// Get the configuration from the client outside of the lock
|
||||
// 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
|
||||
// won't be any racing here.
|
||||
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 {
|
||||
Ok(value) => Some(value),
|
||||
Err(err) => {
|
||||
|
@ -2915,19 +2995,17 @@ impl tower_lsp::LanguageServer for LanguageServer {
|
|||
&self,
|
||||
params: DidChangeWorkspaceFoldersParams,
|
||||
) {
|
||||
let client = {
|
||||
let mut inner = self.0.write().await;
|
||||
inner.did_change_workspace_folders(params).await;
|
||||
inner.client.clone()
|
||||
let (performance, mark) = {
|
||||
let mut ls = self.0.write().await;
|
||||
let mark = ls
|
||||
.performance
|
||||
.mark("did_change_workspace_folders", Some(¶ms));
|
||||
ls.did_change_workspace_folders(params);
|
||||
(ls.performance.clone(), mark)
|
||||
};
|
||||
let language_server = self.clone();
|
||||
tokio::spawn(async move {
|
||||
let mut ls = language_server.0.write().await;
|
||||
if ls.config.update_enabled_paths(client).await {
|
||||
ls.diagnostics_server.invalidate_all();
|
||||
ls.send_diagnostics_update();
|
||||
}
|
||||
});
|
||||
|
||||
self.refresh_specifiers_from_client().await;
|
||||
performance.measure(mark);
|
||||
}
|
||||
|
||||
async fn document_symbol(
|
||||
|
@ -3106,7 +3184,9 @@ impl Inner {
|
|||
&self,
|
||||
params: lsp_custom::CacheParams,
|
||||
) -> Result<Option<PrepareCacheResult>, AnyError> {
|
||||
let referrer = self.url_map.normalize_url(¶ms.referrer.uri);
|
||||
let referrer = self
|
||||
.url_map
|
||||
.normalize_url(¶ms.referrer.uri, LspUrlKind::File);
|
||||
if !self.is_diagnosable(&referrer) {
|
||||
return Ok(None);
|
||||
}
|
||||
|
@ -3116,7 +3196,7 @@ impl Inner {
|
|||
params
|
||||
.uris
|
||||
.iter()
|
||||
.map(|t| self.url_map.normalize_url(&t.uri))
|
||||
.map(|t| self.url_map.normalize_url(&t.uri, LspUrlKind::File))
|
||||
.collect()
|
||||
} else {
|
||||
vec![referrer]
|
||||
|
@ -3190,7 +3270,9 @@ impl Inner {
|
|||
&self,
|
||||
params: InlayHintParams,
|
||||
) -> LspResult<Option<Vec<InlayHint>>> {
|
||||
let specifier = self.url_map.normalize_url(¶ms.text_document.uri);
|
||||
let specifier = self
|
||||
.url_map
|
||||
.normalize_url(¶ms.text_document.uri, LspUrlKind::File);
|
||||
let workspace_settings = self.config.get_workspace_settings();
|
||||
if !self.is_diagnosable(&specifier)
|
||||
|| !self.config.specifier_enabled(&specifier)
|
||||
|
@ -3251,7 +3333,9 @@ impl Inner {
|
|||
let mark = self
|
||||
.performance
|
||||
.mark("virtual_text_document", Some(¶ms));
|
||||
let specifier = self.url_map.normalize_url(¶ms.text_document.uri);
|
||||
let specifier = self
|
||||
.url_map
|
||||
.normalize_url(¶ms.text_document.uri, LspUrlKind::File);
|
||||
let contents = if specifier.as_str() == "deno:/status.md" {
|
||||
let mut contents = String::new();
|
||||
let mut documents_specifiers = self
|
||||
|
|
|
@ -156,7 +156,7 @@ impl TestServer {
|
|||
match run.exec(&client, maybe_root_uri.as_ref()).await {
|
||||
Ok(_) => (),
|
||||
Err(err) => {
|
||||
client.show_message(lsp::MessageType::ERROR, err).await;
|
||||
client.show_message(lsp::MessageType::ERROR, err);
|
||||
}
|
||||
}
|
||||
client.send_test_notification(TestingNotification::Progress(
|
||||
|
|
|
@ -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
|
||||
/// 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.
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct LspUrlMap(Arc<Mutex<LspUrlMapInner>>);
|
||||
|
@ -142,16 +148,26 @@ impl LspUrlMap {
|
|||
/// converted into proper module specifiers, as well as handle situations
|
||||
/// where the client encodes a file URL differently than Rust does by default
|
||||
/// 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() {
|
||||
return specifier;
|
||||
///
|
||||
/// Note: Sometimes the url provided by the client may not have a trailing slash,
|
||||
/// 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();
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -210,7 +226,7 @@ mod tests {
|
|||
let expected_url = Url::parse("deno:/c21c7fc382b2b0553dc0864aa81a3acacfb7b3d1285ab5ae76da6abec213fb37/data_url.ts").unwrap();
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -222,7 +238,7 @@ mod tests {
|
|||
"file:///c%3A/Users/deno/Desktop/file%20with%20spaces%20in%20name.txt",
|
||||
)
|
||||
.unwrap();
|
||||
let actual = map.normalize_url(&fixture);
|
||||
let actual = map.normalize_url(&fixture, LspUrlKind::File);
|
||||
let expected =
|
||||
Url::parse("file:///C:/Users/deno/Desktop/file with spaces in name.txt")
|
||||
.unwrap();
|
||||
|
@ -237,7 +253,7 @@ mod tests {
|
|||
"file:///Users/deno/Desktop/file%20with%20spaces%20in%20name.txt",
|
||||
)
|
||||
.unwrap();
|
||||
let actual = map.normalize_url(&fixture);
|
||||
let actual = map.normalize_url(&fixture, LspUrlKind::File);
|
||||
let expected =
|
||||
Url::parse("file:///Users/deno/Desktop/file with spaces in name.txt")
|
||||
.unwrap();
|
||||
|
|
|
@ -110,7 +110,7 @@ fn cafile_fetch() {
|
|||
#[test]
|
||||
fn cafile_compile() {
|
||||
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) {
|
||||
temp_dir.join("cert.exe")
|
||||
} else {
|
||||
|
|
|
@ -26,7 +26,7 @@ fn lsp_startup_shutdown() {
|
|||
#[test]
|
||||
fn lsp_init_tsconfig() {
|
||||
let context = TestContextBuilder::new().build();
|
||||
let temp_dir = context.deno_dir();
|
||||
let temp_dir = context.temp_dir();
|
||||
|
||||
temp_dir.write(
|
||||
"lib.tsconfig.json",
|
||||
|
@ -59,7 +59,7 @@ fn lsp_init_tsconfig() {
|
|||
#[test]
|
||||
fn lsp_tsconfig_types() {
|
||||
let context = TestContextBuilder::new().build();
|
||||
let temp_dir = context.deno_dir();
|
||||
let temp_dir = context.temp_dir();
|
||||
|
||||
temp_dir.write(
|
||||
"types.tsconfig.json",
|
||||
|
@ -124,7 +124,7 @@ fn lsp_tsconfig_bad_config_path() {
|
|||
#[test]
|
||||
fn lsp_triple_slash_types() {
|
||||
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;";
|
||||
temp_dir.write("a.d.ts", a_dts);
|
||||
let mut client = context.new_lsp_command().build();
|
||||
|
@ -147,7 +147,7 @@ fn lsp_triple_slash_types() {
|
|||
#[test]
|
||||
fn lsp_import_map() {
|
||||
let context = TestContextBuilder::new().build();
|
||||
let temp_dir = context.deno_dir();
|
||||
let temp_dir = context.temp_dir();
|
||||
let import_map = r#"{
|
||||
"imports": {
|
||||
"/~/": "./lib/"
|
||||
|
@ -231,7 +231,7 @@ fn lsp_import_map_data_url() {
|
|||
#[test]
|
||||
fn lsp_import_map_config_file() {
|
||||
let context = TestContextBuilder::new().build();
|
||||
let temp_dir = context.deno_dir();
|
||||
let temp_dir = context.temp_dir();
|
||||
temp_dir.write(
|
||||
"deno.import_map.jsonc",
|
||||
r#"{
|
||||
|
@ -298,7 +298,7 @@ fn lsp_import_map_config_file() {
|
|||
#[test]
|
||||
fn lsp_import_map_embedded_in_config_file() {
|
||||
let context = TestContextBuilder::new().build();
|
||||
let temp_dir = context.deno_dir();
|
||||
let temp_dir = context.temp_dir();
|
||||
temp_dir.write(
|
||||
"deno.embedded_import_map.jsonc",
|
||||
r#"{
|
||||
|
@ -359,7 +359,7 @@ fn lsp_import_map_embedded_in_config_file() {
|
|||
#[test]
|
||||
fn lsp_deno_task() {
|
||||
let context = TestContextBuilder::new().build();
|
||||
let temp_dir = context.deno_dir();
|
||||
let temp_dir = context.temp_dir();
|
||||
temp_dir.write(
|
||||
"deno.jsonc",
|
||||
r#"{
|
||||
|
@ -510,7 +510,7 @@ fn lsp_import_assertions() {
|
|||
#[test]
|
||||
fn lsp_import_map_import_completions() {
|
||||
let context = TestContextBuilder::new().build();
|
||||
let temp_dir = context.deno_dir();
|
||||
let temp_dir = context.temp_dir();
|
||||
temp_dir.write(
|
||||
"import-map.json",
|
||||
r#"{
|
||||
|
@ -932,26 +932,27 @@ fn lsp_workspace_enable_paths() {
|
|||
// 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
|
||||
// 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 mut client = context.new_lsp_command().build();
|
||||
client.initialize(|builder| {
|
||||
builder
|
||||
.set_enable_paths(vec!["./worker".to_string()])
|
||||
.set_root_uri(root_specifier.clone())
|
||||
.set_workspace_folders(vec![lsp::WorkspaceFolder {
|
||||
uri: root_specifier.clone(),
|
||||
name: "project".to_string(),
|
||||
}])
|
||||
.set_deno_enable(false);
|
||||
});
|
||||
|
||||
client.handle_configuration_request(json!([{
|
||||
"enable": false,
|
||||
"enablePaths": ["./worker"],
|
||||
}]));
|
||||
client.initialize_with_config(
|
||||
|builder| {
|
||||
builder
|
||||
.set_enable_paths(vec!["./worker".to_string()])
|
||||
.set_root_uri(root_specifier.clone())
|
||||
.set_workspace_folders(vec![lsp::WorkspaceFolder {
|
||||
uri: root_specifier.clone(),
|
||||
name: "project".to_string(),
|
||||
}])
|
||||
.set_deno_enable(false);
|
||||
},
|
||||
json!([{
|
||||
"enable": false,
|
||||
"enablePaths": ["./worker"],
|
||||
}]),
|
||||
);
|
||||
|
||||
client.did_open(json!({
|
||||
"textDocument": {
|
||||
|
@ -1216,7 +1217,7 @@ fn lsp_hover_change_mbc() {
|
|||
#[test]
|
||||
fn lsp_hover_closed_document() {
|
||||
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("b.ts", r#"export * from "./a.ts";"#);
|
||||
temp_dir.write("c.ts", "import { a } from \"./b.ts\";\nconsole.log(a);\n");
|
||||
|
@ -5188,7 +5189,7 @@ fn lsp_auto_discover_registry() {
|
|||
#[test]
|
||||
fn lsp_cache_location() {
|
||||
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();
|
||||
client.initialize(|builder| {
|
||||
builder.set_cache(".cache").add_test_server_suggestions();
|
||||
|
@ -5668,7 +5669,7 @@ fn lsp_diagnostics_refresh_dependents() {
|
|||
assert_eq!(client.queue_len(), 0);
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PerformanceAverage {
|
||||
pub name: String,
|
||||
|
@ -5676,7 +5677,7 @@ pub struct PerformanceAverage {
|
|||
pub average_duration: u32,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct PerformanceAverages {
|
||||
averages: Vec<PerformanceAverage>,
|
||||
|
@ -5707,7 +5708,30 @@ fn lsp_performance() {
|
|||
"deno/performance",
|
||||
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();
|
||||
}
|
||||
|
||||
|
@ -5826,7 +5850,7 @@ fn lsp_format_mbc() {
|
|||
#[test]
|
||||
fn lsp_format_exclude_with_config() {
|
||||
let context = TestContextBuilder::new().build();
|
||||
let temp_dir = context.deno_dir();
|
||||
let temp_dir = context.temp_dir();
|
||||
|
||||
temp_dir.write(
|
||||
"deno.fmt.jsonc",
|
||||
|
@ -5879,7 +5903,7 @@ fn lsp_format_exclude_with_config() {
|
|||
#[test]
|
||||
fn lsp_format_exclude_default_config() {
|
||||
let context = TestContextBuilder::new().build();
|
||||
let temp_dir = context.deno_dir();
|
||||
let temp_dir = context.temp_dir();
|
||||
|
||||
temp_dir.write(
|
||||
"deno.fmt.jsonc",
|
||||
|
@ -6071,7 +6095,7 @@ fn lsp_format_markdown() {
|
|||
#[test]
|
||||
fn lsp_format_with_config() {
|
||||
let context = TestContextBuilder::new().build();
|
||||
let temp_dir = context.deno_dir();
|
||||
let temp_dir = context.temp_dir();
|
||||
temp_dir.write(
|
||||
"deno.fmt.jsonc",
|
||||
r#"{
|
||||
|
@ -6231,32 +6255,32 @@ fn lsp_configuration_did_change() {
|
|||
"settings": {}
|
||||
}),
|
||||
);
|
||||
let (id, method, _) = client.read_request::<Value>();
|
||||
assert_eq!(method, "workspace/configuration");
|
||||
client.write_response(
|
||||
id,
|
||||
json!([{
|
||||
"enable": true,
|
||||
"codeLens": {
|
||||
"implementations": true,
|
||||
"references": true
|
||||
},
|
||||
"importMap": null,
|
||||
"lint": true,
|
||||
"suggest": {
|
||||
"autoImports": true,
|
||||
"completeFunctionCalls": false,
|
||||
"names": true,
|
||||
"paths": true,
|
||||
"imports": {
|
||||
"hosts": {
|
||||
"http://localhost:4545/": true
|
||||
}
|
||||
let request = json!([{
|
||||
"enable": true,
|
||||
"codeLens": {
|
||||
"implementations": true,
|
||||
"references": true
|
||||
},
|
||||
"importMap": null,
|
||||
"lint": true,
|
||||
"suggest": {
|
||||
"autoImports": true,
|
||||
"completeFunctionCalls": false,
|
||||
"names": 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(
|
||||
"file:///a/file.ts",
|
||||
(0, 46),
|
||||
|
@ -6635,7 +6659,7 @@ console.log(snake_case);
|
|||
#[test]
|
||||
fn lsp_lint_with_config() {
|
||||
let context = TestContextBuilder::new().build();
|
||||
let temp_dir = context.deno_dir();
|
||||
let temp_dir = context.temp_dir();
|
||||
|
||||
temp_dir.write(
|
||||
"deno.lint.jsonc",
|
||||
|
@ -6676,7 +6700,7 @@ fn lsp_lint_with_config() {
|
|||
#[test]
|
||||
fn lsp_lint_exclude_with_config() {
|
||||
let context = TestContextBuilder::new().build();
|
||||
let temp_dir = context.deno_dir();
|
||||
let temp_dir = context.temp_dir();
|
||||
|
||||
temp_dir.write(
|
||||
"deno.lint.jsonc",
|
||||
|
@ -6814,7 +6838,7 @@ struct TestRunResponseParams {
|
|||
#[test]
|
||||
fn lsp_testing_api() {
|
||||
let context = TestContextBuilder::new().build();
|
||||
let temp_dir = context.deno_dir();
|
||||
let temp_dir = context.temp_dir();
|
||||
|
||||
let contents = r#"
|
||||
Deno.test({
|
||||
|
|
|
@ -2874,7 +2874,7 @@ fn package_json_no_node_modules_dir_created() {
|
|||
.add_npm_env_vars()
|
||||
.use_temp_cwd()
|
||||
.build();
|
||||
let temp_dir = context.deno_dir();
|
||||
let temp_dir = context.temp_dir();
|
||||
|
||||
temp_dir.write("deno.json", "{}");
|
||||
temp_dir.write("package.json", "{}");
|
||||
|
@ -2892,7 +2892,7 @@ fn node_modules_dir_no_npm_specifiers_no_dir_created() {
|
|||
.add_npm_env_vars()
|
||||
.use_temp_cwd()
|
||||
.build();
|
||||
let temp_dir = context.deno_dir();
|
||||
let temp_dir = context.temp_dir();
|
||||
|
||||
temp_dir.write("deno.json", "{}");
|
||||
temp_dir.write("main.ts", "");
|
||||
|
|
|
@ -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.
|
||||
pub fn specifier_parent(specifier: &ModuleSpecifier) -> ModuleSpecifier {
|
||||
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]
|
||||
fn test_specifier_parent() {
|
||||
run_test("file:///", "file:///");
|
||||
|
|
|
@ -148,6 +148,11 @@ impl LspStdoutReader {
|
|||
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 {
|
||||
self.read_messages.iter().any(&is_match)
|
||||
|| self.pending_messages.0.lock().iter().any(&is_match)
|
||||
|
@ -453,6 +458,9 @@ impl LspClientBuilder {
|
|||
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 {
|
||||
self.print_stderr = true;
|
||||
self
|
||||
|
@ -541,6 +549,7 @@ impl LspClient {
|
|||
}
|
||||
|
||||
pub fn queue_len(&self) -> usize {
|
||||
self.reader.output_pending_messages();
|
||||
self.reader.pending_len()
|
||||
}
|
||||
|
||||
|
@ -551,12 +560,26 @@ impl LspClient {
|
|||
pub fn initialize(
|
||||
&mut self,
|
||||
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();
|
||||
builder.set_root_uri(self.context.deno_dir().uri());
|
||||
do_build(&mut builder);
|
||||
self.write_request("initialize", builder.build());
|
||||
self.write_notification("initialized", json!({}));
|
||||
self.handle_configuration_request(config);
|
||||
}
|
||||
|
||||
pub fn did_open(&mut self, params: Value) -> CollectedDiagnostics {
|
||||
|
|
Loading…
Reference in a new issue