diff options
Diffstat (limited to 'cli/lsp/language_server.rs')
-rw-r--r-- | cli/lsp/language_server.rs | 439 |
1 files changed, 268 insertions, 171 deletions
diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs index 2ccbb6284..21a229b47 100644 --- a/cli/lsp/language_server.rs +++ b/cli/lsp/language_server.rs @@ -18,7 +18,6 @@ use std::collections::HashMap; use std::env; use std::path::PathBuf; use std::sync::Arc; -use std::sync::Mutex; use tokio::fs; use crate::deno_dir; @@ -42,35 +41,44 @@ use super::tsc::TsServer; use super::utils; #[derive(Debug, Clone)] -pub struct LanguageServer { - assets: Arc<Mutex<HashMap<ModuleSpecifier, Option<AssetDocument>>>>, - client: Client, - ts_server: TsServer, - config: Arc<Mutex<Config>>, - documents: Arc<Mutex<DocumentCache>>, - sources: Arc<Mutex<Sources>>, - diagnostics: Arc<Mutex<DiagnosticCollection>>, - maybe_config_uri: Arc<Mutex<Option<Url>>>, - maybe_import_map: Arc<Mutex<Option<ImportMap>>>, - maybe_import_map_uri: Arc<Mutex<Option<Url>>>, -} +pub struct LanguageServer(Arc<tokio::sync::Mutex<Inner>>); #[derive(Debug, Clone, Default)] pub struct StateSnapshot { - pub assets: Arc<Mutex<HashMap<ModuleSpecifier, Option<AssetDocument>>>>, + pub assets: HashMap<ModuleSpecifier, Option<AssetDocument>>, pub documents: DocumentCache, - pub sources: Arc<Mutex<Sources>>, + pub sources: Sources, +} + +#[derive(Debug)] +struct Inner { + assets: HashMap<ModuleSpecifier, Option<AssetDocument>>, + client: Client, + ts_server: TsServer, + config: Config, + documents: DocumentCache, + sources: Sources, + diagnostics: DiagnosticCollection, + maybe_config_uri: Option<Url>, + maybe_import_map: Option<ImportMap>, + maybe_import_map_uri: Option<Url>, } impl LanguageServer { pub fn new(client: Client) -> Self { + Self(Arc::new(tokio::sync::Mutex::new(Inner::new(client)))) + } +} + +impl Inner { + fn new(client: Client) -> Self { let maybe_custom_root = env::var("DENO_DIR").map(String::into).ok(); let dir = deno_dir::DenoDir::new(maybe_custom_root) .expect("could not access DENO_DIR"); let location = dir.root.join("deps"); - let sources = Arc::new(Mutex::new(Sources::new(&location))); + let sources = Sources::new(&location); - LanguageServer { + Self { assets: Default::default(), client, ts_server: TsServer::new(), @@ -85,20 +93,18 @@ impl LanguageServer { } fn enabled(&self) -> bool { - let config = self.config.lock().unwrap(); - config.settings.enable + self.config.settings.enable } /// Searches assets, open documents and external sources for a line_index, /// which might be performed asynchronously, hydrating in memory caches for /// subsequent requests. - pub async fn get_line_index( + async fn get_line_index( &self, specifier: ModuleSpecifier, ) -> Result<LineIndex, AnyError> { if specifier.as_url().scheme() == "asset" { - let maybe_asset = - { self.assets.lock().unwrap().get(&specifier).cloned() }; + let maybe_asset = self.assets.get(&specifier).cloned(); if let Some(maybe_asset) = maybe_asset { if let Some(asset) = maybe_asset { Ok(asset.line_index) @@ -106,22 +112,19 @@ impl LanguageServer { Err(anyhow!("asset is missing: {}", specifier)) } } else { - let state_snapshot = self.snapshot(); + let mut state_snapshot = self.snapshot(); if let Some(asset) = - tsc::get_asset(&specifier, &self.ts_server, &state_snapshot).await? + tsc::get_asset(&specifier, &self.ts_server, &mut state_snapshot) + .await? { Ok(asset.line_index) } else { Err(anyhow!("asset is missing: {}", specifier)) } } - } else if let Some(line_index) = - self.documents.lock().unwrap().line_index(&specifier) - { + } else if let Some(line_index) = self.documents.line_index(&specifier) { Ok(line_index) - } else if let Some(line_index) = - self.sources.lock().unwrap().get_line_index(&specifier) - { + } else if let Some(line_index) = self.sources.get_line_index(&specifier) { Ok(line_index) } else { Err(anyhow!("Unable to find line index for: {}", specifier)) @@ -130,124 +133,129 @@ impl LanguageServer { /// Only searches already cached assets and documents for a line index. If /// the line index cannot be found, `None` is returned. - pub fn get_line_index_sync( + fn get_line_index_sync( &self, specifier: &ModuleSpecifier, ) -> Option<LineIndex> { if specifier.as_url().scheme() == "asset" { - if let Some(Some(asset)) = self.assets.lock().unwrap().get(specifier) { + if let Some(Some(asset)) = self.assets.get(specifier) { Some(asset.line_index.clone()) } else { None } } else { - let documents = self.documents.lock().unwrap(); + let documents = &self.documents; if documents.contains(specifier) { documents.line_index(specifier) } else { - self.sources.lock().unwrap().get_line_index(specifier) + self.sources.get_line_index(specifier) } } } - async fn prepare_diagnostics(&self) -> Result<(), AnyError> { + async fn prepare_diagnostics(&mut self) -> Result<(), AnyError> { let (enabled, lint_enabled) = { - let config = self.config.lock().unwrap(); + let config = &self.config; (config.settings.enable, config.settings.lint) }; let lint = async { - let mut disturbed = false; + let mut diagnostics = None; if lint_enabled { - let diagnostic_collection = self.diagnostics.lock().unwrap().clone(); - let diagnostics = diagnostics::generate_lint_diagnostics( - self.snapshot(), - diagnostic_collection, - ) - .await; - disturbed = !diagnostics.is_empty(); - { - let mut diagnostics_collection = self.diagnostics.lock().unwrap(); - for (specifier, version, diagnostics) in diagnostics { - diagnostics_collection.set( - specifier, - DiagnosticSource::Lint, - version, - diagnostics, - ); - } - } + diagnostics = Some( + diagnostics::generate_lint_diagnostics( + self.snapshot(), + self.diagnostics.clone(), + ) + .await, + ); }; - Ok::<bool, AnyError>(disturbed) + Ok::<_, AnyError>(diagnostics) }; let ts = async { - let mut disturbed = false; + let mut diagnostics = None; if enabled { - let diagnostics_collection = self.diagnostics.lock().unwrap().clone(); - let diagnostics = diagnostics::generate_ts_diagnostics( - self.snapshot(), - diagnostics_collection, - &self.ts_server, - ) - .await?; - disturbed = !diagnostics.is_empty(); - { - let mut diagnostics_collection = self.diagnostics.lock().unwrap(); - for (specifier, version, diagnostics) in diagnostics { - diagnostics_collection.set( - specifier, - DiagnosticSource::TypeScript, - version, - diagnostics, - ); - } - } + diagnostics = Some( + diagnostics::generate_ts_diagnostics( + self.snapshot(), + self.diagnostics.clone(), + &self.ts_server, + ) + .await?, + ); }; - Ok::<bool, AnyError>(disturbed) + Ok::<_, AnyError>(diagnostics) }; let deps = async { - let mut disturbed = false; + let mut diagnostics = None; if enabled { - let diagnostics_collection = self.diagnostics.lock().unwrap().clone(); - let diagnostics = diagnostics::generate_dependency_diagnostics( - self.snapshot(), - diagnostics_collection, - ) - .await?; - disturbed = !diagnostics.is_empty(); - { - let mut diagnostics_collection = self.diagnostics.lock().unwrap(); - for (specifier, version, diagnostics) in diagnostics { - diagnostics_collection.set( - specifier, - DiagnosticSource::Deno, - version, - diagnostics, - ); - } - } + diagnostics = Some( + diagnostics::generate_dependency_diagnostics( + self.snapshot(), + self.diagnostics.clone(), + ) + .await?, + ); }; - Ok::<bool, AnyError>(disturbed) + Ok::<_, AnyError>(diagnostics) }; let (lint_res, ts_res, deps_res) = tokio::join!(lint, ts, deps); - if lint_res? || ts_res? || deps_res? { + let mut disturbed = false; + + if let Some(diagnostics) = lint_res? { + for (specifier, version, diagnostics) in diagnostics { + self.diagnostics.set( + specifier, + DiagnosticSource::Lint, + version, + diagnostics, + ); + disturbed = true; + } + } + + if let Some(diagnostics) = ts_res? { + for (specifier, version, diagnostics) in diagnostics { + self.diagnostics.set( + specifier, + DiagnosticSource::TypeScript, + version, + diagnostics, + ); + disturbed = true; + } + } + + if let Some(diagnostics) = deps_res? { + for (specifier, version, diagnostics) in diagnostics { + self.diagnostics.set( + specifier, + DiagnosticSource::Deno, + version, + diagnostics, + ); + disturbed = true; + } + } + + if disturbed { self.publish_diagnostics().await?; } Ok(()) } - async fn publish_diagnostics(&self) -> Result<(), AnyError> { + async fn publish_diagnostics(&mut self) -> Result<(), AnyError> { let (maybe_changes, diagnostics_collection) = { - let mut diagnostics_collection = self.diagnostics.lock().unwrap(); + let diagnostics_collection = &mut self.diagnostics; let maybe_changes = diagnostics_collection.take_changes(); (maybe_changes, diagnostics_collection.clone()) }; if let Some(diagnostic_changes) = maybe_changes { - let settings = self.config.lock().unwrap().settings.clone(); + let settings = self.config.settings.clone(); for specifier in diagnostic_changes { // TODO(@kitsonk) not totally happy with the way we collect and store // different types of diagnostics and offer them up to the client, we @@ -275,7 +283,7 @@ impl LanguageServer { ); } let uri = specifier.as_url().clone(); - let version = self.documents.lock().unwrap().version(&specifier); + let version = self.documents.version(&specifier); self .client .publish_diagnostics(uri, diagnostics, version) @@ -286,17 +294,17 @@ impl LanguageServer { Ok(()) } - pub fn snapshot(&self) -> StateSnapshot { + fn snapshot(&self) -> StateSnapshot { StateSnapshot { assets: self.assets.clone(), - documents: self.documents.lock().unwrap().clone(), + documents: self.documents.clone(), sources: self.sources.clone(), } } - pub async fn update_import_map(&self) -> Result<(), AnyError> { + async fn update_import_map(&mut self) -> Result<(), AnyError> { let (maybe_import_map, maybe_root_uri) = { - let config = self.config.lock().unwrap(); + let config = &self.config; (config.settings.import_map.clone(), config.root_uri.clone()) }; if let Some(import_map_str) = &maybe_import_map { @@ -331,15 +339,15 @@ impl LanguageServer { })?; let import_map = ImportMap::from_json(&import_map_url.to_string(), &import_map_json)?; - *self.maybe_import_map_uri.lock().unwrap() = Some(import_map_url); - *self.maybe_import_map.lock().unwrap() = Some(import_map); + self.maybe_import_map_uri = Some(import_map_url); + self.maybe_import_map = Some(import_map); } else { - *self.maybe_import_map.lock().unwrap() = None; + self.maybe_import_map = None; } Ok(()) } - async fn update_tsconfig(&self) -> Result<(), AnyError> { + async fn update_tsconfig(&mut self) -> Result<(), AnyError> { let mut tsconfig = TsConfig::new(json!({ "allowJs": true, "experimentalDecorators": true, @@ -351,7 +359,7 @@ impl LanguageServer { "target": "esnext", })); let (maybe_config, maybe_root_uri) = { - let config = self.config.lock().unwrap(); + let config = &self.config; if config.settings.unstable { let unstable_libs = json!({ "lib": ["deno.ns", "deno.window", "deno.unstable"] @@ -394,7 +402,7 @@ impl LanguageServer { let (value, maybe_ignored_options) = parse_config(&config_text, &config_path)?; tsconfig.merge(&value); - *self.maybe_config_uri.lock().unwrap() = Some(config_url); + self.maybe_config_uri = Some(config_url); if let Some(ignored_options) = maybe_ignored_options { // TODO(@kitsonk) turn these into diagnostics that can be sent to the // client @@ -409,10 +417,10 @@ impl LanguageServer { } } -#[lspower::async_trait] -impl lspower::LanguageServer for LanguageServer { +// lspower::LanguageServer methods. This file's LanguageServer delegates to us. +impl Inner { async fn initialize( - &self, + &mut self, params: InitializeParams, ) -> LspResult<InitializeResult> { info!("Starting Deno language server..."); @@ -441,7 +449,7 @@ impl lspower::LanguageServer for LanguageServer { } { - let mut config = self.config.lock().unwrap(); + let config = &mut self.config; config.root_uri = params.root_uri; if let Some(value) = params.initialization_options { config.update(value)?; @@ -459,7 +467,7 @@ impl lspower::LanguageServer for LanguageServer { }) } - async fn initialized(&self, _: InitializedParams) { + async fn initialized(&mut self, _: InitializedParams) { // Check to see if we need to setup the import map if let Err(err) = self.update_import_map().await { self @@ -470,8 +478,6 @@ impl lspower::LanguageServer for LanguageServer { if self .config - .lock() - .unwrap() .client_capabilities .workspace_did_change_watched_files { @@ -506,7 +512,7 @@ impl lspower::LanguageServer for LanguageServer { Ok(()) } - async fn did_open(&self, params: DidOpenTextDocumentParams) { + async fn did_open(&mut self, params: DidOpenTextDocumentParams) { if params.text_document.uri.scheme() == "deno" { // we can ignore virtual text documents opening, as they don't need to // be tracked in memory, as they are static assets that won't change @@ -514,16 +520,14 @@ impl lspower::LanguageServer for LanguageServer { return; } let specifier = utils::normalize_url(params.text_document.uri); - self.documents.lock().unwrap().open( + self.documents.open( specifier.clone(), params.text_document.version, params.text_document.text, ); if let Err(err) = self .documents - .lock() - .unwrap() - .analyze_dependencies(&specifier, &self.maybe_import_map.lock().unwrap()) + .analyze_dependencies(&specifier, &self.maybe_import_map) { error!("{}", err); } @@ -534,9 +538,9 @@ impl lspower::LanguageServer for LanguageServer { } } - async fn did_change(&self, params: DidChangeTextDocumentParams) { + async fn did_change(&mut self, params: DidChangeTextDocumentParams) { let specifier = utils::normalize_url(params.text_document.uri); - if let Err(err) = self.documents.lock().unwrap().change( + if let Err(err) = self.documents.change( &specifier, params.text_document.version, params.content_changes, @@ -545,9 +549,7 @@ impl lspower::LanguageServer for LanguageServer { } if let Err(err) = self .documents - .lock() - .unwrap() - .analyze_dependencies(&specifier, &self.maybe_import_map.lock().unwrap()) + .analyze_dependencies(&specifier, &self.maybe_import_map) { error!("{}", err); } @@ -558,7 +560,7 @@ impl lspower::LanguageServer for LanguageServer { } } - async fn did_close(&self, params: DidCloseTextDocumentParams) { + async fn did_close(&mut self, params: DidCloseTextDocumentParams) { if params.text_document.uri.scheme() == "deno" { // we can ignore virtual text documents opening, as they don't need to // be tracked in memory, as they are static assets that won't change @@ -566,7 +568,7 @@ impl lspower::LanguageServer for LanguageServer { return; } let specifier = utils::normalize_url(params.text_document.uri); - self.documents.lock().unwrap().close(&specifier); + self.documents.close(&specifier); // TODO(@kitsonk): how to better lazily do this? if let Err(err) = self.prepare_diagnostics().await { @@ -579,16 +581,10 @@ impl lspower::LanguageServer for LanguageServer { } async fn did_change_configuration( - &self, + &mut self, params: DidChangeConfigurationParams, ) { - let config = if self - .config - .lock() - .unwrap() - .client_capabilities - .workspace_configuration - { + let config = if self.config.client_capabilities.workspace_configuration { self .client .configuration(vec![ConfigurationItem { @@ -611,7 +607,7 @@ impl lspower::LanguageServer for LanguageServer { }; if let Some(config) = config { - if let Err(err) = self.config.lock().unwrap().update(config) { + if let Err(err) = self.config.update(config) { error!("failed to update settings: {}", err); } if let Err(err) = self.update_import_map().await { @@ -632,14 +628,12 @@ impl lspower::LanguageServer for LanguageServer { } async fn did_change_watched_files( - &self, + &mut self, params: DidChangeWatchedFilesParams, ) { // if the current import map has changed, we need to reload it - let maybe_import_map_uri = - self.maybe_import_map_uri.lock().unwrap().clone(); - if let Some(import_map_uri) = maybe_import_map_uri { - if params.changes.iter().any(|fe| import_map_uri == fe.uri) { + if let Some(import_map_uri) = &self.maybe_import_map_uri { + if params.changes.iter().any(|fe| *import_map_uri == fe.uri) { if let Err(err) = self.update_import_map().await { self .client @@ -649,9 +643,8 @@ impl lspower::LanguageServer for LanguageServer { } } // if the current tsconfig has changed, we need to reload it - let maybe_config_uri = self.maybe_config_uri.lock().unwrap().clone(); - if let Some(config_uri) = maybe_config_uri { - if params.changes.iter().any(|fe| config_uri == fe.uri) { + if let Some(config_uri) = &self.maybe_config_uri { + if params.changes.iter().any(|fe| *config_uri == fe.uri) { if let Err(err) = self.update_tsconfig().await { self .client @@ -669,8 +662,6 @@ impl lspower::LanguageServer for LanguageServer { let specifier = utils::normalize_url(params.text_document.uri.clone()); let file_text = self .documents - .lock() - .unwrap() .content(&specifier) .map_err(|_| { LspError::invalid_params( @@ -793,7 +784,7 @@ impl lspower::LanguageServer for LanguageServer { } async fn references( - &self, + &mut self, params: ReferenceParams, ) -> LspResult<Option<Vec<Location>>> { if !self.enabled() { @@ -842,7 +833,7 @@ impl lspower::LanguageServer for LanguageServer { } async fn goto_definition( - &self, + &mut self, params: GotoDefinitionParams, ) -> LspResult<Option<GotoDefinitionResponse>> { if !self.enabled() { @@ -1038,7 +1029,7 @@ impl lspower::LanguageServer for LanguageServer { .into_workspace_edit( ¶ms.new_name, |s| self.get_line_index(s), - |s| self.documents.lock().unwrap().version(&s), + |s| self.documents.version(&s), ) .await .map_err(|err| { @@ -1052,7 +1043,7 @@ impl lspower::LanguageServer for LanguageServer { } async fn request_else( - &self, + &mut self, method: &str, params: Option<Value>, ) -> LspResult<Option<Value>> { @@ -1089,30 +1080,139 @@ impl lspower::LanguageServer for LanguageServer { } } +#[lspower::async_trait] +impl lspower::LanguageServer for LanguageServer { + async fn initialize( + &self, + params: InitializeParams, + ) -> LspResult<InitializeResult> { + self.0.lock().await.initialize(params).await + } + + async fn initialized(&self, params: InitializedParams) { + self.0.lock().await.initialized(params).await + } + + async fn shutdown(&self) -> LspResult<()> { + self.0.lock().await.shutdown().await + } + + async fn did_open(&self, params: DidOpenTextDocumentParams) { + self.0.lock().await.did_open(params).await + } + + async fn did_change(&self, params: DidChangeTextDocumentParams) { + self.0.lock().await.did_change(params).await + } + + async fn did_close(&self, params: DidCloseTextDocumentParams) { + self.0.lock().await.did_close(params).await + } + + async fn did_save(&self, params: DidSaveTextDocumentParams) { + self.0.lock().await.did_save(params).await + } + + async fn did_change_configuration( + &self, + params: DidChangeConfigurationParams, + ) { + self.0.lock().await.did_change_configuration(params).await + } + + async fn did_change_watched_files( + &self, + params: DidChangeWatchedFilesParams, + ) { + self.0.lock().await.did_change_watched_files(params).await + } + + async fn formatting( + &self, + params: DocumentFormattingParams, + ) -> LspResult<Option<Vec<TextEdit>>> { + self.0.lock().await.formatting(params).await + } + + async fn hover(&self, params: HoverParams) -> LspResult<Option<Hover>> { + self.0.lock().await.hover(params).await + } + + async fn document_highlight( + &self, + params: DocumentHighlightParams, + ) -> LspResult<Option<Vec<DocumentHighlight>>> { + self.0.lock().await.document_highlight(params).await + } + + async fn references( + &self, + params: ReferenceParams, + ) -> LspResult<Option<Vec<Location>>> { + self.0.lock().await.references(params).await + } + + async fn goto_definition( + &self, + params: GotoDefinitionParams, + ) -> LspResult<Option<GotoDefinitionResponse>> { + self.0.lock().await.goto_definition(params).await + } + + async fn completion( + &self, + params: CompletionParams, + ) -> LspResult<Option<CompletionResponse>> { + self.0.lock().await.completion(params).await + } + + async fn goto_implementation( + &self, + params: GotoImplementationParams, + ) -> LspResult<Option<GotoImplementationResponse>> { + self.0.lock().await.goto_implementation(params).await + } + + async fn rename( + &self, + params: RenameParams, + ) -> LspResult<Option<WorkspaceEdit>> { + self.0.lock().await.rename(params).await + } + + async fn request_else( + &self, + method: &str, + params: Option<Value>, + ) -> LspResult<Option<Value>> { + self.0.lock().await.request_else(method, params).await + } +} + #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] -pub struct CacheParams { - pub text_document: TextDocumentIdentifier, +struct CacheParams { + text_document: TextDocumentIdentifier, } #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] -pub struct VirtualTextDocumentParams { - pub text_document: TextDocumentIdentifier, +struct VirtualTextDocumentParams { + text_document: TextDocumentIdentifier, } -impl LanguageServer { - async fn cache(&self, params: CacheParams) -> LspResult<bool> { +impl Inner { + async fn cache(&mut self, params: CacheParams) -> LspResult<bool> { let specifier = utils::normalize_url(params.text_document.uri); - let maybe_import_map = self.maybe_import_map.lock().unwrap().clone(); + let maybe_import_map = self.maybe_import_map.clone(); sources::cache(specifier.clone(), maybe_import_map) .await .map_err(|err| { error!("{}", err); LspError::internal_error() })?; - if self.documents.lock().unwrap().contains(&specifier) { - self.diagnostics.lock().unwrap().invalidate(&specifier); + if self.documents.contains(&specifier) { + self.diagnostics.invalidate(&specifier); } self.prepare_diagnostics().await.map_err(|err| { error!("{}", err); @@ -1128,20 +1228,18 @@ impl LanguageServer { let specifier = utils::normalize_url(params.text_document.uri); let url = specifier.as_url(); let contents = if url.as_str() == "deno:/status.md" { - let documents = self.documents.lock().unwrap(); Some(format!( r#"# Deno Language Server Status - Documents in memory: {} "#, - documents.len() + self.documents.len() )) } else { match url.scheme() { "asset" => { - let maybe_asset = - { self.assets.lock().unwrap().get(&specifier).cloned() }; + let maybe_asset = self.assets.get(&specifier).cloned(); if let Some(maybe_asset) = maybe_asset { if let Some(asset) = maybe_asset { Some(asset.text) @@ -1149,9 +1247,9 @@ impl LanguageServer { None } } else { - let state_snapshot = self.snapshot(); + let mut state_snapshot = self.snapshot(); if let Some(asset) = - tsc::get_asset(&specifier, &self.ts_server, &state_snapshot) + tsc::get_asset(&specifier, &self.ts_server, &mut state_snapshot) .await .map_err(|_| LspError::internal_error())? { @@ -1163,8 +1261,7 @@ impl LanguageServer { } } _ => { - let mut sources = self.sources.lock().unwrap(); - if let Some(text) = sources.get_text(&specifier) { + if let Some(text) = self.sources.get_text(&specifier) { Some(text) } else { error!("The cached sources was not found: {}", specifier); |