1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-22 07:14:47 -05:00

fix(lsp): re-enable the per resource configuration without a deadlock (#10625)

Fixes #10603
This commit is contained in:
Kitson Kelly 2021-05-20 19:56:48 +10:00 committed by GitHub
parent bdee065d42
commit 014d8d51c0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 499 additions and 189 deletions

View file

@ -5,6 +5,8 @@ use deno_core::serde::Deserialize;
use deno_core::serde_json; use deno_core::serde_json;
use deno_core::serde_json::json; use deno_core::serde_json::json;
use deno_core::serde_json::Value; use deno_core::serde_json::Value;
use deno_core::url::Url;
use lspower::lsp;
use std::collections::HashMap; use std::collections::HashMap;
use std::path::Path; use std::path::Path;
use std::time::Duration; use std::time::Duration;
@ -62,16 +64,15 @@ fn bench_big_file_edits(deno_exe: &Path) -> Result<Duration, AnyError> {
}), }),
)?; )?;
// TODO(@kitsonk) work around https://github.com/denoland/deno/issues/10603 let (id, method, _): (u64, String, Option<Value>) = client.read_request()?;
// let (id, method, _): (u64, String, Option<Value>) = client.read_request()?; assert_eq!(method, "workspace/configuration");
// assert_eq!(method, "workspace/configuration");
// client.write_response( client.write_response(
// id, id,
// json!({ json!({
// "enable": true "enable": true
// }), }),
// )?; )?;
let (method, _): (String, Option<Value>) = client.read_notification()?; let (method, _): (String, Option<Value>) = client.read_notification()?;
assert_eq!(method, "textDocument/publishDiagnostics"); assert_eq!(method, "textDocument/publishDiagnostics");
@ -122,13 +123,108 @@ fn bench_big_file_edits(deno_exe: &Path) -> Result<Duration, AnyError> {
Ok(client.duration()) Ok(client.duration())
} }
fn bench_find_replace(deno_exe: &Path) -> Result<Duration, AnyError> {
let mut client = LspClient::new(deno_exe)?;
let params: Value = serde_json::from_slice(FIXTURE_INIT_JSON)?;
let (_, maybe_err) =
client.write_request::<_, _, Value>("initialize", params)?;
assert!(maybe_err.is_none());
client.write_notification("initialized", json!({}))?;
for i in 0..10 {
client.write_notification(
"textDocument/didOpen",
json!({
"textDocument": {
"uri": format!("file:///a/file_{}.ts", i),
"languageId": "typescript",
"version": 1,
"text": "console.log(\"000\");\n"
}
}),
)?;
}
for _ in 0..10 {
let (id, method, _) = client.read_request::<Value>()?;
assert_eq!(method, "workspace/configuration");
client.write_response(id, json!({ "enable": true }))?;
}
for _ in 0..3 {
let (method, _): (String, Option<Value>) = client.read_notification()?;
assert_eq!(method, "textDocument/publishDiagnostics");
}
for i in 0..10 {
let file_name = format!("file:///a/file_{}.ts", i);
client.write_notification(
"textDocument/didChange",
lsp::DidChangeTextDocumentParams {
text_document: lsp::VersionedTextDocumentIdentifier {
uri: Url::parse(&file_name).unwrap(),
version: 2,
},
content_changes: vec![lsp::TextDocumentContentChangeEvent {
range: Some(lsp::Range {
start: lsp::Position {
line: 0,
character: 13,
},
end: lsp::Position {
line: 0,
character: 16,
},
}),
range_length: None,
text: "111".to_string(),
}],
},
)?;
}
for i in 0..10 {
let file_name = format!("file:///a/file_{}.ts", i);
let (maybe_res, maybe_err) = client.write_request::<_, _, Value>(
"textDocument/formatting",
lsp::DocumentFormattingParams {
text_document: lsp::TextDocumentIdentifier {
uri: Url::parse(&file_name).unwrap(),
},
options: lsp::FormattingOptions {
tab_size: 2,
insert_spaces: true,
..Default::default()
},
work_done_progress_params: Default::default(),
},
)?;
assert!(maybe_err.is_none());
assert!(maybe_res.is_some());
}
for _ in 0..3 {
let (method, _): (String, Option<Value>) = client.read_notification()?;
assert_eq!(method, "textDocument/publishDiagnostics");
}
let (_, response_error): (Option<Value>, Option<LspResponseError>) =
client.write_request("shutdown", json!(null))?;
assert!(response_error.is_none());
client.write_notification("exit", json!(null))?;
Ok(client.duration())
}
/// A test that starts up the LSP, opens a single line document, and exits. /// A test that starts up the LSP, opens a single line document, and exits.
fn bench_startup_shutdown(deno_exe: &Path) -> Result<Duration, AnyError> { fn bench_startup_shutdown(deno_exe: &Path) -> Result<Duration, AnyError> {
let mut client = LspClient::new(deno_exe)?; let mut client = LspClient::new(deno_exe)?;
let params: Value = serde_json::from_slice(FIXTURE_INIT_JSON)?; let params: Value = serde_json::from_slice(FIXTURE_INIT_JSON)?;
let (_, response_error): (Option<Value>, Option<LspResponseError>) = let (_, response_error) =
client.write_request("initialize", params)?; client.write_request::<_, _, Value>("initialize", params)?;
assert!(response_error.is_none()); assert!(response_error.is_none());
client.write_notification("initialized", json!({}))?; client.write_notification("initialized", json!({}))?;
@ -145,16 +241,15 @@ fn bench_startup_shutdown(deno_exe: &Path) -> Result<Duration, AnyError> {
}), }),
)?; )?;
// TODO(@kitsonk) work around https://github.com/denoland/deno/issues/10603 let (id, method, _) = client.read_request::<Value>()?;
// let (id, method, _): (u64, String, Option<Value>) = client.read_request()?; assert_eq!(method, "workspace/configuration");
// assert_eq!(method, "workspace/configuration");
// client.write_response( client.write_response(
// id, id,
// json!({ json!({
// "enable": true "enable": true
// }), }),
// )?; )?;
let (method, _): (String, Option<Value>) = client.read_notification()?; let (method, _): (String, Option<Value>) = client.read_notification()?;
assert_eq!(method, "textDocument/publishDiagnostics"); assert_eq!(method, "textDocument/publishDiagnostics");
@ -199,6 +294,16 @@ pub(crate) fn benchmarks(
println!(" ({} runs, mean: {}ms)", times.len(), mean); println!(" ({} runs, mean: {}ms)", times.len(), mean);
exec_times.insert("big_file_edits".to_string(), mean); exec_times.insert("big_file_edits".to_string(), mean);
println!(" - Find/Replace");
let mut times = Vec::new();
for _ in 0..10 {
times.push(bench_find_replace(deno_exe)?);
}
let mean =
(times.iter().sum::<Duration>() / times.len() as u32).as_millis() as u64;
println!(" ({} runs, mean: {}ms)", times.len(), mean);
exec_times.insert("find_replace".to_string(), mean);
println!("<- End benchmarking lsp"); println!("<- End benchmarking lsp");
Ok(exec_times) Ok(exec_times)

View file

@ -1,14 +1,22 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
use crate::tokio_util::create_basic_runtime;
use deno_core::error::anyhow;
use deno_core::error::AnyError;
use deno_core::serde::Deserialize; use deno_core::serde::Deserialize;
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::url::Url; use deno_core::url::Url;
use deno_core::ModuleSpecifier; use deno_core::ModuleSpecifier;
use lspower::jsonrpc::Error as LSPError; use log::error;
use lspower::jsonrpc::Result as LSPResult;
use lspower::lsp; use lspower::lsp;
use std::collections::BTreeMap;
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc;
use std::sync::RwLock;
use std::thread;
use tokio::sync::mpsc;
pub const SETTINGS_SECTION: &str = "deno"; pub const SETTINGS_SECTION: &str = "deno";
@ -139,25 +147,201 @@ impl WorkspaceSettings {
} }
} }
#[derive(Debug, Clone, Default)]
pub struct ConfigSnapshot {
pub client_capabilities: ClientCapabilities,
pub root_uri: Option<Url>,
pub settings: Settings,
}
impl ConfigSnapshot {
pub fn specifier_enabled(&self, specifier: &ModuleSpecifier) -> bool {
if let Some(settings) = self.settings.specifiers.get(specifier) {
settings.1.enable
} else {
self.settings.workspace.enable
}
}
}
enum ConfigRequest {
Specifier(ModuleSpecifier, ModuleSpecifier),
Workspace,
}
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]
pub struct Settings {
pub specifiers:
BTreeMap<ModuleSpecifier, (ModuleSpecifier, SpecifierSettings)>,
pub workspace: WorkspaceSettings,
}
#[derive(Debug)]
pub struct Config { pub struct Config {
pub client_capabilities: ClientCapabilities, pub client_capabilities: ClientCapabilities,
pub root_uri: Option<Url>, pub root_uri: Option<Url>,
pub specifier_settings: HashMap<ModuleSpecifier, SpecifierSettings>, settings: Arc<RwLock<Settings>>,
pub workspace_settings: WorkspaceSettings, tx: mpsc::Sender<ConfigRequest>,
} }
impl Config { impl Config {
#[allow(unused)] pub fn new(client: lspower::Client) -> Self {
pub fn contains(&self, specifier: &ModuleSpecifier) -> bool { let (tx, mut rx) = mpsc::channel::<ConfigRequest>(100);
self.specifier_settings.contains_key(specifier) let settings = Arc::new(RwLock::new(Settings::default()));
let settings_ref = settings.clone();
let _join_handle = thread::spawn(move || {
let runtime = create_basic_runtime();
runtime.block_on(async {
loop {
match rx.recv().await {
None => break,
Some(ConfigRequest::Workspace) => {
let mut items = vec![lsp::ConfigurationItem {
scope_uri: None,
section: Some(SETTINGS_SECTION.to_string()),
}];
let (specifier_uri_map, mut specifier_items): (
Vec<(ModuleSpecifier, ModuleSpecifier)>,
Vec<lsp::ConfigurationItem>,
) = {
let settings = settings_ref.read().unwrap();
(
settings
.specifiers
.iter()
.map(|(s, (u, _))| (s.clone(), u.clone()))
.collect(),
settings
.specifiers
.iter()
.map(|(_, (uri, _))| lsp::ConfigurationItem {
scope_uri: Some(uri.clone()),
section: Some(SETTINGS_SECTION.to_string()),
})
.collect(),
)
};
items.append(&mut specifier_items);
if let Ok(configs) = client.configuration(items).await {
let mut settings = settings_ref.write().unwrap();
for (i, value) in configs.into_iter().enumerate() {
match i {
0 => {
match serde_json::from_value::<WorkspaceSettings>(value) {
Ok(workspace_settings) => {
settings.workspace = workspace_settings;
}
Err(err) => {
error!(
"Error converting workspace settings: {}",
err
);
}
}
}
_ => {
match serde_json::from_value::<SpecifierSettings>(value) {
Ok(specifier_settings) => {
let (specifier, uri) =
specifier_uri_map[i - 1].clone();
settings
.specifiers
.insert(specifier, (uri, specifier_settings));
}
Err(err) => {
error!(
"Error converting specifier settings: {}",
err
);
}
}
}
}
}
}
}
Some(ConfigRequest::Specifier(specifier, uri)) => {
if settings_ref
.read()
.unwrap()
.specifiers
.contains_key(&specifier)
{
continue;
}
if let Ok(value) = client
.configuration(vec![lsp::ConfigurationItem {
scope_uri: Some(uri.clone()),
section: Some(SETTINGS_SECTION.to_string()),
}])
.await
{
match serde_json::from_value::<SpecifierSettings>(
value[0].clone(),
) {
Ok(specifier_settings) => {
settings_ref
.write()
.unwrap()
.specifiers
.insert(specifier, (uri, specifier_settings));
}
Err(err) => {
error!("Error converting specifier settings: {}", err);
}
}
} else {
error!(
"Error retrieving settings for specifier: {}",
specifier
);
}
}
}
}
})
});
Self {
client_capabilities: ClientCapabilities::default(),
root_uri: None,
settings,
tx,
}
}
pub fn get_workspace_settings(&self) -> WorkspaceSettings {
self.settings.read().unwrap().workspace.clone()
}
/// Set the workspace settings directly, which occurs during initialization
/// and when the client does not support workspace configuration requests
pub fn set_workspace_settings(&self, value: Value) -> Result<(), AnyError> {
let workspace_settings = serde_json::from_value(value)?;
self.settings.write().unwrap().workspace = workspace_settings;
Ok(())
}
pub fn snapshot(&self) -> Result<ConfigSnapshot, AnyError> {
Ok(ConfigSnapshot {
client_capabilities: self.client_capabilities.clone(),
root_uri: self.root_uri.clone(),
settings: self
.settings
.try_read()
.map_err(|_| anyhow!("Error reading settings."))?
.clone(),
})
} }
pub fn specifier_enabled(&self, specifier: &ModuleSpecifier) -> bool { pub fn specifier_enabled(&self, specifier: &ModuleSpecifier) -> bool {
if let Some(settings) = self.specifier_settings.get(specifier) { let settings = self.settings.read().unwrap();
settings.enable if let Some(specifier_settings) = settings.specifiers.get(specifier) {
specifier_settings.1.enable
} else { } else {
self.workspace_settings.enable settings.workspace.enable
} }
} }
@ -192,23 +376,24 @@ impl Config {
} }
} }
pub fn update_specifier( pub async fn update_specifier_settings(
&mut self, &self,
specifier: ModuleSpecifier, specifier: &ModuleSpecifier,
value: Value, uri: &ModuleSpecifier,
) -> LSPResult<()> { ) -> Result<(), AnyError> {
let settings: SpecifierSettings = serde_json::from_value(value) self
.map_err(|err| LSPError::invalid_params(err.to_string()))?; .tx
self.specifier_settings.insert(specifier, settings); .send(ConfigRequest::Specifier(specifier.clone(), uri.clone()))
Ok(()) .await
.map_err(|_| anyhow!("Error sending config update task."))
} }
pub fn update_workspace(&mut self, value: Value) -> LSPResult<()> { pub async fn update_workspace_settings(&self) -> Result<(), AnyError> {
let settings: WorkspaceSettings = serde_json::from_value(value) self
.map_err(|err| LSPError::invalid_params(err.to_string()))?; .tx
self.workspace_settings = settings; .send(ConfigRequest::Workspace)
self.specifier_settings = HashMap::new(); .await
Ok(()) .map_err(|_| anyhow!("Error sending config update task."))
} }
} }
@ -218,41 +403,45 @@ mod tests {
use deno_core::resolve_url; use deno_core::resolve_url;
use deno_core::serde_json::json; use deno_core::serde_json::json;
#[test] #[derive(Debug, Default)]
fn test_config_contains() { struct MockLanguageServer;
let mut config = Config::default();
let specifier = resolve_url("https://deno.land/x/a.ts").unwrap(); #[lspower::async_trait]
assert!(!config.contains(&specifier)); impl lspower::LanguageServer for MockLanguageServer {
config async fn initialize(
.update_specifier( &self,
specifier.clone(), _params: lspower::lsp::InitializeParams,
json!({ ) -> lspower::jsonrpc::Result<lsp::InitializeResult> {
"enable": true Ok(lspower::lsp::InitializeResult {
}), capabilities: lspower::lsp::ServerCapabilities::default(),
) server_info: None,
.expect("could not update specifier"); })
assert!(config.contains(&specifier)); }
async fn shutdown(&self) -> lspower::jsonrpc::Result<()> {
Ok(())
}
}
fn setup() -> Config {
let mut maybe_client: Option<lspower::Client> = None;
let (_service, _) = lspower::LspService::new(|client| {
maybe_client = Some(client);
MockLanguageServer::default()
});
Config::new(maybe_client.unwrap())
} }
#[test] #[test]
fn test_config_specifier_enabled() { fn test_config_specifier_enabled() {
let mut config = Config::default(); let config = setup();
let specifier = resolve_url("file:///a.ts").unwrap(); let specifier = resolve_url("file:///a.ts").unwrap();
assert!(!config.specifier_enabled(&specifier)); assert!(!config.specifier_enabled(&specifier));
config config
.update_workspace(json!({ .set_workspace_settings(json!({
"enable": true "enable": true
})) }))
.expect("could not update"); .expect("could not update");
assert!(config.specifier_enabled(&specifier)); assert!(config.specifier_enabled(&specifier));
config
.update_specifier(
specifier.clone(),
json!({
"enable": false
}),
)
.expect("could not update");
assert!(!config.specifier_enabled(&specifier));
} }
} }

View file

@ -170,7 +170,7 @@ impl DiagnosticsServer {
dirty = false; dirty = false;
debounce_timer.as_mut().reset(Instant::now() + NEVER); debounce_timer.as_mut().reset(Instant::now() + NEVER);
let snapshot = language_server.lock().await.snapshot(); let snapshot = language_server.lock().await.snapshot().unwrap();
update_diagnostics( update_diagnostics(
&client, &client,
collection.clone(), collection.clone(),
@ -314,7 +314,7 @@ async fn generate_lint_diagnostics(
collection: Arc<Mutex<DiagnosticCollection>>, collection: Arc<Mutex<DiagnosticCollection>>,
) -> Result<DiagnosticVec, AnyError> { ) -> Result<DiagnosticVec, AnyError> {
let documents = snapshot.documents.clone(); let documents = snapshot.documents.clone();
let workspace_settings = snapshot.config.workspace_settings.clone(); let workspace_settings = snapshot.config.settings.workspace.clone();
tokio::task::spawn(async move { tokio::task::spawn(async move {
let mut diagnostics_vec = Vec::new(); let mut diagnostics_vec = Vec::new();
if workspace_settings.lint { if workspace_settings.lint {
@ -487,7 +487,7 @@ async fn publish_diagnostics(
if let Some(changes) = collection.take_changes() { if let Some(changes) = collection.take_changes() {
for specifier in changes { for specifier in changes {
let mut diagnostics: Vec<lsp::Diagnostic> = let mut diagnostics: Vec<lsp::Diagnostic> =
if snapshot.config.workspace_settings.lint { if snapshot.config.settings.workspace.lint {
collection collection
.get(&specifier, DiagnosticSource::DenoLint) .get(&specifier, DiagnosticSource::DenoLint)
.cloned() .cloned()

View file

@ -38,6 +38,7 @@ use super::analysis::ResolvedDependency;
use super::capabilities; use super::capabilities;
use super::completions; use super::completions;
use super::config::Config; use super::config::Config;
use super::config::ConfigSnapshot;
use super::config::SETTINGS_SECTION; use super::config::SETTINGS_SECTION;
use super::diagnostics; use super::diagnostics;
use super::diagnostics::DiagnosticSource; use super::diagnostics::DiagnosticSource;
@ -77,7 +78,7 @@ pub struct LanguageServer(Arc<tokio::sync::Mutex<Inner>>);
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct StateSnapshot { pub struct StateSnapshot {
pub assets: Assets, pub assets: Assets,
pub config: Config, pub config: ConfigSnapshot,
pub documents: DocumentCache, pub documents: DocumentCache,
pub module_registries: registries::ModuleRegistry, pub module_registries: registries::ModuleRegistry,
pub performance: Performance, pub performance: Performance,
@ -141,11 +142,12 @@ impl Inner {
let ts_server = Arc::new(TsServer::new()); let ts_server = Arc::new(TsServer::new());
let performance = Performance::default(); let performance = Performance::default();
let diagnostics_server = diagnostics::DiagnosticsServer::new(); let diagnostics_server = diagnostics::DiagnosticsServer::new();
let config = Config::new(client.clone());
Self { Self {
assets: Default::default(), assets: Default::default(),
client, client,
config: Default::default(), config,
diagnostics_server, diagnostics_server,
documents: Default::default(), documents: Default::default(),
maybe_config_uri: Default::default(), maybe_config_uri: Default::default(),
@ -282,7 +284,7 @@ impl Inner {
let navigation_tree: tsc::NavigationTree = self let navigation_tree: tsc::NavigationTree = self
.ts_server .ts_server
.request( .request(
self.snapshot(), self.snapshot()?,
tsc::RequestMethod::GetNavigationTree(specifier.clone()), tsc::RequestMethod::GetNavigationTree(specifier.clone()),
) )
.await?; .await?;
@ -294,16 +296,19 @@ impl Inner {
} }
} }
pub(crate) fn snapshot(&self) -> StateSnapshot { pub(crate) fn snapshot(&self) -> LspResult<StateSnapshot> {
StateSnapshot { Ok(StateSnapshot {
assets: self.assets.clone(), assets: self.assets.clone(),
config: self.config.clone(), config: self.config.snapshot().map_err(|err| {
error!("{}", err);
LspError::internal_error()
})?,
documents: self.documents.clone(), documents: self.documents.clone(),
module_registries: self.module_registries.clone(), module_registries: self.module_registries.clone(),
performance: self.performance.clone(), performance: self.performance.clone(),
sources: self.sources.clone(), sources: self.sources.clone(),
url_map: self.url_map.clone(), url_map: self.url_map.clone(),
} })
} }
pub async fn update_import_map(&mut self) -> Result<(), AnyError> { pub async fn update_import_map(&mut self) -> Result<(), AnyError> {
@ -311,7 +316,7 @@ impl Inner {
let (maybe_import_map, maybe_root_uri) = { let (maybe_import_map, maybe_root_uri) = {
let config = &self.config; let config = &self.config;
( (
config.workspace_settings.import_map.clone(), config.get_workspace_settings().import_map,
config.root_uri.clone(), config.root_uri.clone(),
) )
}; };
@ -357,10 +362,11 @@ impl Inner {
} }
pub fn update_debug_flag(&self) -> bool { pub fn update_debug_flag(&self) -> bool {
let internal_debug = self.config.get_workspace_settings().internal_debug;
logger::LSP_DEBUG_FLAG logger::LSP_DEBUG_FLAG
.compare_exchange( .compare_exchange(
!self.config.workspace_settings.internal_debug, !internal_debug,
self.config.workspace_settings.internal_debug, internal_debug,
Ordering::Acquire, Ordering::Acquire,
Ordering::Relaxed, Ordering::Relaxed,
) )
@ -369,8 +375,13 @@ impl Inner {
async fn update_registries(&mut self) -> Result<(), AnyError> { async fn update_registries(&mut self) -> Result<(), AnyError> {
let mark = self.performance.mark("update_registries", None::<()>); let mark = self.performance.mark("update_registries", None::<()>);
for (registry, enabled) in for (registry, enabled) in self
self.config.workspace_settings.suggest.imports.hosts.iter() .config
.get_workspace_settings()
.suggest
.imports
.hosts
.iter()
{ {
if *enabled { if *enabled {
info!("Enabling auto complete registry for: {}", registry); info!("Enabling auto complete registry for: {}", registry);
@ -401,16 +412,14 @@ impl Inner {
})); }));
let (maybe_config, maybe_root_uri) = { let (maybe_config, maybe_root_uri) = {
let config = &self.config; let config = &self.config;
if config.workspace_settings.unstable { let workspace_settings = config.get_workspace_settings();
if workspace_settings.unstable {
let unstable_libs = json!({ let unstable_libs = json!({
"lib": ["deno.ns", "deno.window", "deno.unstable"] "lib": ["deno.ns", "deno.window", "deno.unstable"]
}); });
tsconfig.merge(&unstable_libs); tsconfig.merge(&unstable_libs);
} }
( (workspace_settings.config, config.root_uri.clone())
config.workspace_settings.config.clone(),
config.root_uri.clone(),
)
}; };
if let Some(config_str) = &maybe_config { if let Some(config_str) = &maybe_config {
info!("Updating TypeScript configuration from: \"{}\"", config_str); info!("Updating TypeScript configuration from: \"{}\"", config_str);
@ -443,7 +452,7 @@ impl Inner {
} }
let _ok: bool = self let _ok: bool = self
.ts_server .ts_server
.request(self.snapshot(), tsc::RequestMethod::Configure(tsconfig)) .request(self.snapshot()?, tsc::RequestMethod::Configure(tsconfig))
.await?; .await?;
self.performance.measure(mark); self.performance.measure(mark);
Ok(()) Ok(())
@ -464,7 +473,7 @@ impl Inner {
return Ok(maybe_asset.clone()); return Ok(maybe_asset.clone());
} else { } else {
let maybe_asset = let maybe_asset =
tsc::get_asset(&specifier, &self.ts_server, self.snapshot()).await?; tsc::get_asset(&specifier, &self.ts_server, self.snapshot()?).await?;
self.assets.insert(specifier.clone(), maybe_asset.clone()); self.assets.insert(specifier.clone(), maybe_asset.clone());
Ok(maybe_asset) Ok(maybe_asset)
} }
@ -507,7 +516,10 @@ impl Inner {
let config = &mut self.config; let config = &mut self.config;
config.root_uri = params.root_uri; config.root_uri = params.root_uri;
if let Some(value) = params.initialization_options { if let Some(value) = params.initialization_options {
config.update_workspace(value)?; config.set_workspace_settings(value).map_err(|err| {
error!("Cannot set workspace settings: {}", err);
LspError::internal_error()
})?;
} }
config.update_capabilities(&params.capabilities); config.update_capabilities(&params.capabilities);
} }
@ -520,7 +532,7 @@ impl Inner {
if capabilities.code_action_provider.is_some() { if capabilities.code_action_provider.is_some() {
let fixable_diagnostics: Vec<String> = self let fixable_diagnostics: Vec<String> = self
.ts_server .ts_server
.request(self.snapshot(), tsc::RequestMethod::GetSupportedCodeFixes) .request(self.snapshot()?, tsc::RequestMethod::GetSupportedCodeFixes)
.await .await
.map_err(|err| { .map_err(|err| {
error!("Unable to get fixable diagnostics: {}", err); error!("Unable to get fixable diagnostics: {}", err);
@ -592,27 +604,13 @@ impl Inner {
let mark = self.performance.mark("did_open", Some(&params)); let mark = self.performance.mark("did_open", Some(&params));
let specifier = self.url_map.normalize_url(&params.text_document.uri); let specifier = self.url_map.normalize_url(&params.text_document.uri);
// we only query the individual resource file if the client supports it if let Err(err) = self
// TODO(@kitsonk) workaround https://github.com/denoland/deno/issues/10603 .config
// if self.config.client_capabilities.workspace_configuration .update_specifier_settings(&specifier, &params.text_document.uri)
// && !self.config.contains(&specifier) .await
// { {
// if let Ok(value) = self error!("Error updating specifier settings: {}", err);
// .client }
// .configuration(vec![ConfigurationItem {
// scope_uri: Some(params.text_document.uri.clone()),
// section: Some(SETTINGS_SECTION.to_string()),
// }])
// .await
// {
// if let Err(err) = self
// .config
// .update_specifier(specifier.clone(), value[0].clone())
// {
// warn!("Error updating specifier configuration: {}", err);
// }
// }
// }
if params.text_document.uri.scheme() == "deno" { if params.text_document.uri.scheme() == "deno" {
// we can ignore virtual text documents opening, as they don't need to // we can ignore virtual text documents opening, as they don't need to
@ -679,32 +677,8 @@ impl Inner {
.mark("did_change_configuration", Some(&params)); .mark("did_change_configuration", Some(&params));
if self.config.client_capabilities.workspace_configuration { if self.config.client_capabilities.workspace_configuration {
let specifiers: Vec<ModuleSpecifier> = if let Err(err) = self.config.update_workspace_settings().await {
self.config.specifier_settings.keys().cloned().collect(); error!("Error updating workspace settings: {}", err);
let mut snapshot = self.snapshot();
let mut config_items = specifiers
.iter()
.map(|s| ConfigurationItem {
scope_uri: Some(snapshot.url_map.normalize_specifier(s).unwrap()),
section: Some(SETTINGS_SECTION.to_string()),
})
.collect();
let mut items = vec![ConfigurationItem {
scope_uri: None,
section: Some(SETTINGS_SECTION.to_string()),
}];
items.append(&mut config_items);
if let Ok(configs) = self.client.configuration(items).await {
for (i, value) in configs.into_iter().enumerate() {
if let Err(err) = match i {
0 => self.config.update_workspace(value),
_ => self
.config
.update_specifier(specifiers[i - 1].clone(), value),
} {
error!("failed to update settings: {}", err);
}
}
} }
} else if let Some(config) = params } else if let Some(config) = params
.settings .settings
@ -713,7 +687,7 @@ impl Inner {
.flatten() .flatten()
.cloned() .cloned()
{ {
if let Err(err) = self.config.update_workspace(config) { if let Err(err) = self.config.set_workspace_settings(config) {
error!("failed to update settings: {}", err); error!("failed to update settings: {}", err);
} }
} }
@ -804,7 +778,7 @@ impl Inner {
let req = tsc::RequestMethod::GetNavigationTree(specifier); let req = tsc::RequestMethod::GetNavigationTree(specifier);
let navigation_tree: tsc::NavigationTree = self let navigation_tree: tsc::NavigationTree = self
.ts_server .ts_server
.request(self.snapshot(), req) .request(self.snapshot()?, req)
.await .await
.map_err(|err| { .map_err(|err| {
error!("Failed to request to tsserver {}", err); error!("Failed to request to tsserver {}", err);
@ -900,7 +874,7 @@ impl Inner {
)); ));
let maybe_quick_info: Option<tsc::QuickInfo> = self let maybe_quick_info: Option<tsc::QuickInfo> = self
.ts_server .ts_server
.request(self.snapshot(), req) .request(self.snapshot()?, req)
.await .await
.map_err(|err| { .map_err(|err| {
error!("Unable to get quick info: {}", err); error!("Unable to get quick info: {}", err);
@ -977,7 +951,7 @@ impl Inner {
codes, codes,
)); ));
let actions: Vec<tsc::CodeFixAction> = let actions: Vec<tsc::CodeFixAction> =
match self.ts_server.request(self.snapshot(), req).await { match self.ts_server.request(self.snapshot()?, req).await {
Ok(items) => items, Ok(items) => items,
Err(err) => { Err(err) => {
// sometimes tsc reports errors when retrieving code actions // sometimes tsc reports errors when retrieving code actions
@ -1040,7 +1014,7 @@ impl Inner {
)); ));
let combined_code_actions: tsc::CombinedCodeActions = self let combined_code_actions: tsc::CombinedCodeActions = self
.ts_server .ts_server
.request(self.snapshot(), req) .request(self.snapshot()?, req)
.await .await
.map_err(|err| { .map_err(|err| {
error!("Unable to get combined fix from TypeScript: {}", err); error!("Unable to get combined fix from TypeScript: {}", err);
@ -1074,7 +1048,7 @@ impl Inner {
) -> 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);
if !self.config.specifier_enabled(&specifier) if !self.config.specifier_enabled(&specifier)
|| !self.config.workspace_settings.enabled_code_lens() || !self.config.get_workspace_settings().enabled_code_lens()
{ {
return Ok(None); return Ok(None);
} }
@ -1093,9 +1067,10 @@ impl Inner {
let cl = Rc::new(RefCell::new(Vec::new())); let cl = Rc::new(RefCell::new(Vec::new()));
navigation_tree.walk(&|i, mp| { navigation_tree.walk(&|i, mp| {
let mut code_lenses = cl.borrow_mut(); let mut code_lenses = cl.borrow_mut();
let workspace_settings = self.config.get_workspace_settings();
// TSC Implementations Code Lens // TSC Implementations Code Lens
if self.config.workspace_settings.code_lens.implementations { if workspace_settings.code_lens.implementations {
let source = CodeLensSource::Implementations; let source = CodeLensSource::Implementations;
match i.kind { match i.kind {
tsc::ScriptElementKind::InterfaceElement => { tsc::ScriptElementKind::InterfaceElement => {
@ -1119,7 +1094,7 @@ impl Inner {
} }
// TSC References Code Lens // TSC References Code Lens
if self.config.workspace_settings.code_lens.references { if workspace_settings.code_lens.references {
let source = CodeLensSource::References; let source = CodeLensSource::References;
if let Some(parent) = &mp { if let Some(parent) = &mp {
if parent.kind == tsc::ScriptElementKind::EnumElement { if parent.kind == tsc::ScriptElementKind::EnumElement {
@ -1128,12 +1103,7 @@ impl Inner {
} }
match i.kind { match i.kind {
tsc::ScriptElementKind::FunctionElement => { tsc::ScriptElementKind::FunctionElement => {
if self if workspace_settings.code_lens.references_all_functions {
.config
.workspace_settings
.code_lens
.references_all_functions
{
code_lenses.push(i.to_code_lens( code_lenses.push(i.to_code_lens(
&line_index, &line_index,
&specifier, &specifier,
@ -1214,12 +1184,14 @@ impl Inner {
line_index.offset_tsc(params.range.start)?, line_index.offset_tsc(params.range.start)?,
)); ));
let maybe_implementations: Option<Vec<tsc::ImplementationLocation>> = let maybe_implementations: Option<Vec<tsc::ImplementationLocation>> =
self.ts_server.request(self.snapshot(), req).await.map_err( self
|err| { .ts_server
.request(self.snapshot()?, req)
.await
.map_err(|err| {
error!("Error processing TypeScript request: {}", err); error!("Error processing TypeScript request: {}", err);
LspError::internal_error() LspError::internal_error()
}, })?;
)?;
if let Some(implementations) = maybe_implementations { if let Some(implementations) = maybe_implementations {
let mut locations = Vec::new(); let mut locations = Vec::new();
for implementation in implementations { for implementation in implementations {
@ -1292,13 +1264,14 @@ impl Inner {
code_lens_data.specifier.clone(), code_lens_data.specifier.clone(),
line_index.offset_tsc(params.range.start)?, line_index.offset_tsc(params.range.start)?,
)); ));
let maybe_references: Option<Vec<tsc::ReferenceEntry>> = let maybe_references: Option<Vec<tsc::ReferenceEntry>> = self
self.ts_server.request(self.snapshot(), req).await.map_err( .ts_server
|err| { .request(self.snapshot()?, req)
error!("Error processing TypeScript request: {}", err); .await
LspError::internal_error() .map_err(|err| {
}, error!("Error processing TypeScript request: {}", err);
)?; LspError::internal_error()
})?;
if let Some(references) = maybe_references { if let Some(references) = maybe_references {
let mut locations = Vec::new(); let mut locations = Vec::new();
for reference in references { for reference in references {
@ -1408,7 +1381,7 @@ impl Inner {
)); ));
let maybe_document_highlights: Option<Vec<tsc::DocumentHighlights>> = self let maybe_document_highlights: Option<Vec<tsc::DocumentHighlights>> = self
.ts_server .ts_server
.request(self.snapshot(), req) .request(self.snapshot()?, req)
.await .await
.map_err(|err| { .map_err(|err| {
error!("Unable to get document highlights from TypeScript: {}", err); error!("Unable to get document highlights from TypeScript: {}", err);
@ -1455,7 +1428,7 @@ impl Inner {
)); ));
let maybe_references: Option<Vec<tsc::ReferenceEntry>> = self let maybe_references: Option<Vec<tsc::ReferenceEntry>> = self
.ts_server .ts_server
.request(self.snapshot(), req) .request(self.snapshot()?, req)
.await .await
.map_err(|err| { .map_err(|err| {
error!("Unable to get references from TypeScript: {}", err); error!("Unable to get references from TypeScript: {}", err);
@ -1510,7 +1483,7 @@ impl Inner {
)); ));
let maybe_definition: Option<tsc::DefinitionInfoAndBoundSpan> = self let maybe_definition: Option<tsc::DefinitionInfoAndBoundSpan> = self
.ts_server .ts_server
.request(self.snapshot(), req) .request(self.snapshot()?, req)
.await .await
.map_err(|err| { .map_err(|err| {
error!("Unable to get definition from TypeScript: {}", err); error!("Unable to get definition from TypeScript: {}", err);
@ -1545,7 +1518,7 @@ impl Inner {
let response = if let Some(response) = completions::get_import_completions( let response = if let Some(response) = completions::get_import_completions(
&specifier, &specifier,
&params.text_document_position.position, &params.text_document_position.position,
&self.snapshot(), &self.snapshot()?,
) )
.await .await
{ {
@ -1580,7 +1553,7 @@ impl Inner {
)); ));
let maybe_completion_info: Option<tsc::CompletionInfo> = self let maybe_completion_info: Option<tsc::CompletionInfo> = self
.ts_server .ts_server
.request(self.snapshot(), req) .request(self.snapshot()?, req)
.await .await
.map_err(|err| { .map_err(|err| {
error!("Unable to get completion info from TypeScript: {}", err); error!("Unable to get completion info from TypeScript: {}", err);
@ -1590,7 +1563,7 @@ impl Inner {
if let Some(completions) = maybe_completion_info { if let Some(completions) = maybe_completion_info {
let results = completions.as_completion_response( let results = completions.as_completion_response(
&line_index, &line_index,
&self.config.workspace_settings.suggest, &self.config.get_workspace_settings().suggest,
&specifier, &specifier,
position, position,
); );
@ -1618,13 +1591,14 @@ impl Inner {
})?; })?;
if let Some(data) = data.tsc { if let Some(data) = data.tsc {
let req = tsc::RequestMethod::GetCompletionDetails(data.into()); let req = tsc::RequestMethod::GetCompletionDetails(data.into());
let maybe_completion_info: Option<tsc::CompletionEntryDetails> = let maybe_completion_info: Option<tsc::CompletionEntryDetails> = self
self.ts_server.request(self.snapshot(), req).await.map_err( .ts_server
|err| { .request(self.snapshot()?, req)
error!("Unable to get completion info from TypeScript: {}", err); .await
LspError::internal_error() .map_err(|err| {
}, error!("Unable to get completion info from TypeScript: {}", err);
)?; LspError::internal_error()
})?;
if let Some(completion_info) = maybe_completion_info { if let Some(completion_info) = maybe_completion_info {
completion_info.as_completion_item(&params) completion_info.as_completion_item(&params)
} else { } else {
@ -1670,7 +1644,7 @@ impl Inner {
)); ));
let maybe_implementations: Option<Vec<tsc::ImplementationLocation>> = self let maybe_implementations: Option<Vec<tsc::ImplementationLocation>> = self
.ts_server .ts_server
.request(self.snapshot(), req) .request(self.snapshot()?, req)
.await .await
.map_err(|err| { .map_err(|err| {
error!("Failed to request to tsserver {}", err); error!("Failed to request to tsserver {}", err);
@ -1716,7 +1690,7 @@ impl Inner {
let req = tsc::RequestMethod::GetOutliningSpans(specifier.clone()); let req = tsc::RequestMethod::GetOutliningSpans(specifier.clone());
let outlining_spans: Vec<tsc::OutliningSpan> = self let outlining_spans: Vec<tsc::OutliningSpan> = self
.ts_server .ts_server
.request(self.snapshot(), req) .request(self.snapshot()?, req)
.await .await
.map_err(|err| { .map_err(|err| {
error!("Failed to request to tsserver {}", err); error!("Failed to request to tsserver {}", err);
@ -1776,7 +1750,7 @@ impl Inner {
)); ));
let incoming_calls: Vec<tsc::CallHierarchyIncomingCall> = self let incoming_calls: Vec<tsc::CallHierarchyIncomingCall> = self
.ts_server .ts_server
.request(self.snapshot(), req) .request(self.snapshot()?, req)
.await .await
.map_err(|err| { .map_err(|err| {
error!("Failed to request to tsserver {}", err); error!("Failed to request to tsserver {}", err);
@ -1830,7 +1804,7 @@ impl Inner {
)); ));
let outgoing_calls: Vec<tsc::CallHierarchyOutgoingCall> = self let outgoing_calls: Vec<tsc::CallHierarchyOutgoingCall> = self
.ts_server .ts_server
.request(self.snapshot(), req) .request(self.snapshot()?, req)
.await .await
.map_err(|err| { .map_err(|err| {
error!("Failed to request to tsserver {}", err); error!("Failed to request to tsserver {}", err);
@ -1890,7 +1864,7 @@ impl Inner {
let maybe_one_or_many: Option<tsc::OneOrMany<tsc::CallHierarchyItem>> = let maybe_one_or_many: Option<tsc::OneOrMany<tsc::CallHierarchyItem>> =
self self
.ts_server .ts_server
.request(self.snapshot(), req) .request(self.snapshot()?, req)
.await .await
.map_err(|err| { .map_err(|err| {
error!("Failed to request to tsserver {}", err); error!("Failed to request to tsserver {}", err);
@ -1970,7 +1944,7 @@ impl Inner {
let maybe_locations: Option<Vec<tsc::RenameLocation>> = self let maybe_locations: Option<Vec<tsc::RenameLocation>> = self
.ts_server .ts_server
.request(self.snapshot(), req) .request(self.snapshot()?, req)
.await .await
.map_err(|err| { .map_err(|err| {
error!("Failed to request to tsserver {}", err); error!("Failed to request to tsserver {}", err);
@ -2057,7 +2031,7 @@ impl Inner {
let selection_range: tsc::SelectionRange = self let selection_range: tsc::SelectionRange = self
.ts_server .ts_server
.request(self.snapshot(), req) .request(self.snapshot()?, req)
.await .await
.map_err(|err| { .map_err(|err| {
error!("Failed to request to tsserver {}", err); error!("Failed to request to tsserver {}", err);
@ -2099,7 +2073,7 @@ impl Inner {
)); ));
let semantic_classification: tsc::Classifications = self let semantic_classification: tsc::Classifications = self
.ts_server .ts_server
.request(self.snapshot(), req) .request(self.snapshot()?, req)
.await .await
.map_err(|err| { .map_err(|err| {
error!("Failed to request to tsserver {}", err); error!("Failed to request to tsserver {}", err);
@ -2147,7 +2121,7 @@ impl Inner {
)); ));
let semantic_classification: tsc::Classifications = self let semantic_classification: tsc::Classifications = self
.ts_server .ts_server
.request(self.snapshot(), req) .request(self.snapshot()?, req)
.await .await
.map_err(|err| { .map_err(|err| {
error!("Failed to request to tsserver {}", err); error!("Failed to request to tsserver {}", err);
@ -2204,7 +2178,7 @@ impl Inner {
)); ));
let maybe_signature_help_items: Option<tsc::SignatureHelpItems> = self let maybe_signature_help_items: Option<tsc::SignatureHelpItems> = self
.ts_server .ts_server
.request(self.snapshot(), req) .request(self.snapshot()?, req)
.await .await
.map_err(|err| { .map_err(|err| {
error!("Failed to request to tsserver: {}", err); error!("Failed to request to tsserver: {}", err);

View file

@ -38,6 +38,13 @@ where
client client
.write_notification("textDocument/didOpen", params) .write_notification("textDocument/didOpen", params)
.unwrap(); .unwrap();
let (id, method, _) = client.read_request::<Value>().unwrap();
assert_eq!(method, "workspace/configuration");
client
.write_response(id, json!({ "enable": true }))
.unwrap();
let (method, _) = client.read_notification::<Value>().unwrap(); let (method, _) = client.read_notification::<Value>().unwrap();
assert_eq!(method, "textDocument/publishDiagnostics"); assert_eq!(method, "textDocument/publishDiagnostics");
let (method, _) = client.read_notification::<Value>().unwrap(); let (method, _) = client.read_notification::<Value>().unwrap();
@ -209,6 +216,13 @@ fn lsp_hover_disabled() {
}), }),
) )
.unwrap(); .unwrap();
let (id, method, _) = client.read_request::<Value>().unwrap();
assert_eq!(method, "workspace/configuration");
client
.write_response(id, json!({ "enable": false }))
.unwrap();
let (maybe_res, maybe_err) = client let (maybe_res, maybe_err) = client
.write_request( .write_request(
"textDocument/hover", "textDocument/hover",
@ -453,6 +467,12 @@ fn lsp_hover_closed_document() {
}), }),
) )
.unwrap(); .unwrap();
let (id, method, _) = client.read_request::<Value>().unwrap();
assert_eq!(method, "workspace/configuration");
client
.write_response(id, json!({ "enable": true }))
.unwrap();
client client
.write_notification( .write_notification(
"textDocument/didOpen", "textDocument/didOpen",
@ -466,6 +486,12 @@ fn lsp_hover_closed_document() {
}), }),
) )
.unwrap(); .unwrap();
let (id, method, _) = client.read_request::<Value>().unwrap();
assert_eq!(method, "workspace/configuration");
client
.write_response(id, json!({ "enable": true }))
.unwrap();
let (method, _) = client.read_notification::<Value>().unwrap(); let (method, _) = client.read_notification::<Value>().unwrap();
assert_eq!(method, "textDocument/publishDiagnostics"); assert_eq!(method, "textDocument/publishDiagnostics");
let (method, _) = client.read_notification::<Value>().unwrap(); let (method, _) = client.read_notification::<Value>().unwrap();

View file

@ -51,6 +51,10 @@
"willSaveWaitUntil": true, "willSaveWaitUntil": true,
"didSave": true "didSave": true
} }
},
"workspace": {
"configuration": true,
"workspaceFolders": true
} }
} }
} }

View file

@ -51,6 +51,10 @@
"willSaveWaitUntil": true, "willSaveWaitUntil": true,
"didSave": true "didSave": true
} }
},
"workspace": {
"configuration": true,
"workspaceFolders": true
} }
} }
} }

View file

@ -53,6 +53,10 @@
"willSaveWaitUntil": true, "willSaveWaitUntil": true,
"didSave": true "didSave": true
} }
},
"workspace": {
"configuration": true,
"workspaceFolders": true
} }
} }
} }

View file

@ -51,6 +51,10 @@
"willSaveWaitUntil": true, "willSaveWaitUntil": true,
"didSave": true "didSave": true
} }
},
"workspace": {
"configuration": true,
"workspaceFolders": true
} }
} }
} }