diff --git a/cli/bench/fixtures/initialize_params.json b/cli/bench/fixtures/initialize_params.json index c4eb19c70f..d8a396b90c 100644 --- a/cli/bench/fixtures/initialize_params.json +++ b/cli/bench/fixtures/initialize_params.json @@ -39,6 +39,10 @@ "willSaveWaitUntil": true, "didSave": true } + }, + "workspace": { + "configuration": true, + "workspaceFolders": true } } } diff --git a/cli/bench/lsp.rs b/cli/bench/lsp.rs index aea238441e..173c527740 100644 --- a/cli/bench/lsp.rs +++ b/cli/bench/lsp.rs @@ -184,6 +184,22 @@ impl LspClient { } } + fn read_request(&mut self) -> Result<(u64, String, Option), AnyError> + where + R: de::DeserializeOwned, + { + loop { + if let LspMessage::Request(id, method, maybe_params) = self.read()? { + if let Some(p) = maybe_params { + let params = serde_json::from_value(p)?; + return Ok((id, method, Some(params))); + } else { + return Ok((id, method, None)); + } + } + } + } + fn write(&mut self, value: Value) -> Result<(), AnyError> { let value_str = value.to_string(); let msg = format!( @@ -222,6 +238,18 @@ impl LspClient { } } + fn write_response(&mut self, id: u64, result: V) -> Result<(), AnyError> + where + V: Serialize, + { + let value = json!({ + "jsonrpc": "2.0", + "id": id, + "result": result + }); + self.write(value) + } + fn write_notification( &mut self, method: S, @@ -266,6 +294,16 @@ fn bench_big_file_edits(deno_exe: &Path) -> Result { }), )?; + let (id, method, _): (u64, String, Option) = client.read_request()?; + assert_eq!(method, "workspace/configuration"); + + client.write_response( + id, + json!({ + "enable": true + }), + )?; + let (method, _): (String, Option) = client.read_notification()?; assert_eq!(method, "textDocument/publishDiagnostics"); let (method, _): (String, Option) = client.read_notification()?; @@ -328,6 +366,16 @@ fn bench_startup_shutdown(deno_exe: &Path) -> Result { }), )?; + let (id, method, _): (u64, String, Option) = client.read_request()?; + assert_eq!(method, "workspace/configuration"); + + client.write_response( + id, + json!({ + "enable": true + }), + )?; + let (method, _): (String, Option) = client.read_notification()?; assert_eq!(method, "textDocument/publishDiagnostics"); let (method, _): (String, Option) = client.read_notification()?; diff --git a/cli/lsp/README.md b/cli/lsp/README.md index 11464efd2a..d480e31335 100644 --- a/cli/lsp/README.md +++ b/cli/lsp/README.md @@ -61,3 +61,56 @@ with Deno: textDocument: TextDocumentIdentifier; } ``` + +## Settings + +There are several settings that the language server supports for a workspace: + +- `deno.enable` +- `deno.config` +- `deno.import_map` +- `deno.code_lens.implementations` +- `deno.code_lens.references` +- `deno.code_lens.references_all_functions` +- `deno.suggest.complete_function_calls` +- `deno.suggest.names` +- `deno.suggest.paths` +- `deno.suggest.auto_imports` +- `deno.imports.hosts` +- `deno.lint` +- `deno.unstable` + +There are settings that are support on a per resource basis by the language +server: + +- `deno.enable` + +There are several points in the process where Deno analyzes these settings. +First, when the `initialize` request from the client, the +`initializationOptions` will be assumed to be an object that represents the +`deno` namespace of options. For example, the following value: + +```json +{ + "enable": true, + "unstable": true +} +``` + +Would enable Deno with the unstable APIs for this instance of the language +server. + +When the language server receives a `workspace/didChangeConfiguration` +notification, it will assess if the client has indicated if it has a +`workspaceConfiguration` capability. If it does, it will send a +`workspace/configuration` request which will include a request for the workspace +configuration as well as the configuration of all URIs that the language server +is currently tracking. + +If the client has the `workspaceConfiguration` capability, the language server +will send a configuration request for the URI when it received the +`textDocument/didOpen` notification in order to get the resources specific +settings. + +If the client does not have the `workspaceConfiguration` capability, the +language server will assume the workspace setting applies to all resources. diff --git a/cli/lsp/capabilities.rs b/cli/lsp/capabilities.rs index 677e3921ee..89400d108d 100644 --- a/cli/lsp/capabilities.rs +++ b/cli/lsp/capabilities.rs @@ -27,6 +27,8 @@ use lspower::lsp::TextDocumentSyncCapability; use lspower::lsp::TextDocumentSyncKind; use lspower::lsp::TextDocumentSyncOptions; use lspower::lsp::WorkDoneProgressOptions; +use lspower::lsp::WorkspaceFoldersServerCapabilities; +use lspower::lsp::WorkspaceServerCapabilities; use super::semantic_tokens::get_legend; @@ -132,7 +134,13 @@ pub fn server_capabilities( }, ), ), - workspace: None, + workspace: Some(WorkspaceServerCapabilities { + workspace_folders: Some(WorkspaceFoldersServerCapabilities { + supported: Some(true), + change_notifications: None, + }), + file_operations: None, + }), experimental: None, linked_editing_range_provider: None, moniker_provider: None, diff --git a/cli/lsp/config.rs b/cli/lsp/config.rs index 63f04e3cf1..7aca52a9b6 100644 --- a/cli/lsp/config.rs +++ b/cli/lsp/config.rs @@ -4,11 +4,14 @@ use deno_core::serde::Deserialize; use deno_core::serde_json; use deno_core::serde_json::Value; use deno_core::url::Url; +use deno_core::ModuleSpecifier; use lspower::jsonrpc::Error as LSPError; use lspower::jsonrpc::Result as LSPResult; use lspower::lsp; use std::collections::HashMap; +pub const SETTINGS_SECTION: &str = "deno"; + #[derive(Debug, Clone, Default)] pub struct ClientCapabilities { pub status_notification: bool, @@ -84,19 +87,43 @@ impl Default for ImportCompletionSettings { } } +/// Deno language server specific settings that can be applied uniquely to a +/// specifier. +#[derive(Debug, Default, Clone, Deserialize)] +pub struct SpecifierSettings { + /// A flag that indicates if Deno is enabled for this specifier or not. + pub enable: bool, +} + +/// Deno language server specific settings that are applied to a workspace. #[derive(Debug, Default, Clone, Deserialize)] #[serde(rename_all = "camelCase")] pub struct WorkspaceSettings { + /// A flag that indicates if Deno is enabled for the workspace. pub enable: bool, + + /// An option that points to a path string of the tsconfig file to apply to + /// code within the workspace. pub config: Option, + + /// An option that points to a path string of the import map to apply to the + /// code within the workspace. pub import_map: Option, + + /// Code lens specific settings for the workspace. #[serde(default)] pub code_lens: CodeLensSettings, #[serde(default)] + + /// Suggestion (auto-completion) settings for the workspace. pub suggest: CompletionSettings, + /// A flag that indicates if linting is enabled for the workspace. #[serde(default)] pub lint: bool, + + /// A flag that indicates if Dene should validate code against the unstable + /// APIs for the workspace. #[serde(default)] pub unstable: bool, } @@ -113,15 +140,21 @@ impl WorkspaceSettings { pub struct Config { pub client_capabilities: ClientCapabilities, pub root_uri: Option, - pub settings: WorkspaceSettings, + pub specifier_settings: HashMap, + pub workspace_settings: WorkspaceSettings, } impl Config { - pub fn update(&mut self, value: Value) -> LSPResult<()> { - let settings: WorkspaceSettings = serde_json::from_value(value) - .map_err(|err| LSPError::invalid_params(err.to_string()))?; - self.settings = settings; - Ok(()) + pub fn contains(&self, specifier: &ModuleSpecifier) -> bool { + self.specifier_settings.contains_key(specifier) + } + + pub fn specifier_enabled(&self, specifier: &ModuleSpecifier) -> bool { + if let Some(settings) = self.specifier_settings.get(specifier) { + settings.enable + } else { + self.workspace_settings.enable + } } #[allow(clippy::redundant_closure_call)] @@ -154,4 +187,68 @@ impl Config { .unwrap_or(false); } } + + pub fn update_specifier( + &mut self, + specifier: ModuleSpecifier, + value: Value, + ) -> LSPResult<()> { + let settings: SpecifierSettings = serde_json::from_value(value) + .map_err(|err| LSPError::invalid_params(err.to_string()))?; + self.specifier_settings.insert(specifier, settings); + Ok(()) + } + + pub fn update_workspace(&mut self, value: Value) -> LSPResult<()> { + let settings: WorkspaceSettings = serde_json::from_value(value) + .map_err(|err| LSPError::invalid_params(err.to_string()))?; + self.workspace_settings = settings; + self.specifier_settings = HashMap::new(); + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use deno_core::resolve_url; + use deno_core::serde_json::json; + + #[test] + fn test_config_contains() { + let mut config = Config::default(); + let specifier = resolve_url("https://deno.land/x/a.ts").unwrap(); + assert!(!config.contains(&specifier)); + config + .update_specifier( + specifier.clone(), + json!({ + "enable": true + }), + ) + .expect("could not update specifier"); + assert!(config.contains(&specifier)); + } + + #[test] + fn test_config_specifier_enabled() { + let mut config = Config::default(); + let specifier = resolve_url("file:///a.ts").unwrap(); + assert!(!config.specifier_enabled(&specifier)); + config + .update_workspace(json!({ + "enable": true + })) + .expect("could not update"); + assert!(config.specifier_enabled(&specifier)); + config + .update_specifier( + specifier.clone(), + json!({ + "enable": false + }), + ) + .expect("could not update"); + assert!(!config.specifier_enabled(&specifier)); + } } diff --git a/cli/lsp/diagnostics.rs b/cli/lsp/diagnostics.rs index 4fcf2959a9..b03a320595 100644 --- a/cli/lsp/diagnostics.rs +++ b/cli/lsp/diagnostics.rs @@ -65,7 +65,7 @@ async fn publish_diagnostics( // disabled, otherwise the client will not clear down previous // diagnostics let mut diagnostics: Vec = - if snapshot.config.settings.lint { + if snapshot.config.workspace_settings.lint { collection .diagnostics_for(&specifier, &DiagnosticSource::Lint) .cloned() @@ -73,7 +73,7 @@ async fn publish_diagnostics( } else { vec![] }; - if snapshot.config.settings.enable { + if snapshot.config.specifier_enabled(&specifier) { diagnostics.extend( collection .diagnostics_for(&specifier, &DiagnosticSource::TypeScript) @@ -98,12 +98,8 @@ async fn update_diagnostics( snapshot: &language_server::StateSnapshot, ts_server: &tsc::TsServer, ) { - let (enabled, lint_enabled) = { - let config = &snapshot.config; - (config.settings.enable, config.settings.lint) - }; - let mark = snapshot.performance.mark("update_diagnostics"); + let lint_enabled = snapshot.config.workspace_settings.lint; let lint = async { let collection = collection.clone(); @@ -128,62 +124,50 @@ async fn update_diagnostics( }; let ts = async { - if enabled { - let collection = collection.clone(); - let mark = snapshot.performance.mark("update_diagnostics_ts"); - let diagnostics = generate_ts_diagnostics( - snapshot.clone(), - collection.clone(), - ts_server, - ) - .await - .map_err(|err| { - error!("Error generating TypeScript diagnostics: {}", err); - err - }) - .unwrap_or_default(); - { - let mut collection = collection.lock().unwrap(); - for (specifier, version, diagnostics) in diagnostics { - collection.set( - specifier, - DiagnosticSource::TypeScript, - version, - diagnostics, - ); - } + let collection = collection.clone(); + let mark = snapshot.performance.mark("update_diagnostics_ts"); + let diagnostics = + generate_ts_diagnostics(snapshot.clone(), collection.clone(), ts_server) + .await + .map_err(|err| { + error!("Error generating TypeScript diagnostics: {}", err); + err + }) + .unwrap_or_default(); + { + let mut collection = collection.lock().unwrap(); + for (specifier, version, diagnostics) in diagnostics { + collection.set( + specifier, + DiagnosticSource::TypeScript, + version, + diagnostics, + ); } - publish_diagnostics(client, collection, snapshot).await; - snapshot.performance.measure(mark); - }; + } + publish_diagnostics(client, collection, snapshot).await; + snapshot.performance.measure(mark); }; let deps = async { - if enabled { - let collection = collection.clone(); - let mark = snapshot.performance.mark("update_diagnostics_deps"); - let diagnostics = - generate_dependency_diagnostics(snapshot.clone(), collection.clone()) - .await - .map_err(|err| { - error!("Error generating dependency diagnostics: {}", err); - err - }) - .unwrap_or_default(); - { - let mut collection = collection.lock().unwrap(); - for (specifier, version, diagnostics) in diagnostics { - collection.set( - specifier, - DiagnosticSource::Deno, - version, - diagnostics, - ); - } + let collection = collection.clone(); + let mark = snapshot.performance.mark("update_diagnostics_deps"); + let diagnostics = + generate_dependency_diagnostics(snapshot.clone(), collection.clone()) + .await + .map_err(|err| { + error!("Error generating dependency diagnostics: {}", err); + err + }) + .unwrap_or_default(); + { + let mut collection = collection.lock().unwrap(); + for (specifier, version, diagnostics) in diagnostics { + collection.set(specifier, DiagnosticSource::Deno, version, diagnostics); } - publish_diagnostics(client, collection, snapshot).await; - snapshot.performance.measure(mark); - }; + } + publish_diagnostics(client, collection, snapshot).await; + snapshot.performance.measure(mark); }; tokio::join!(lint, ts, deps); @@ -534,11 +518,13 @@ async fn generate_ts_diagnostics( { let collection = collection.lock().unwrap(); for specifier in state_snapshot.documents.open_specifiers() { - let version = state_snapshot.documents.version(specifier); - let current_version = - collection.get_version(specifier, &DiagnosticSource::TypeScript); - if version != current_version { - specifiers.push(specifier.clone()); + if state_snapshot.config.specifier_enabled(specifier) { + let version = state_snapshot.documents.version(specifier); + let current_version = + collection.get_version(specifier, &DiagnosticSource::TypeScript); + if version != current_version { + specifiers.push(specifier.clone()); + } } } } @@ -568,6 +554,9 @@ async fn generate_dependency_diagnostics( let sources = &mut state_snapshot.sources; for specifier in state_snapshot.documents.open_specifiers() { + if !state_snapshot.config.specifier_enabled(specifier) { + continue; + } let version = state_snapshot.documents.version(specifier); let current_version = collection.lock().unwrap().get_version(specifier, &DiagnosticSource::Deno); if version != current_version { diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs index 12fc70c8c5..2fbc41b034 100644 --- a/cli/lsp/language_server.rs +++ b/cli/lsp/language_server.rs @@ -44,6 +44,7 @@ use super::analysis::ResolvedDependency; use super::capabilities; use super::completions; use super::config::Config; +use super::config::SETTINGS_SECTION; use super::diagnostics; use super::diagnostics::DiagnosticSource; use super::documents::DocumentCache; @@ -78,6 +79,7 @@ pub struct StateSnapshot { pub module_registries: registries::ModuleRegistry, pub performance: Performance, pub sources: Sources, + pub url_map: urls::LspUrlMap, } #[derive(Debug)] @@ -188,10 +190,6 @@ impl Inner { } } - fn enabled(&self) -> bool { - 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. @@ -293,6 +291,7 @@ impl Inner { module_registries: self.module_registries.clone(), performance: self.performance.clone(), sources: self.sources.clone(), + url_map: self.url_map.clone(), } } @@ -300,7 +299,10 @@ impl Inner { let mark = self.performance.mark("update_import_map"); let (maybe_import_map, maybe_root_uri) = { let config = &self.config; - (config.settings.import_map.clone(), config.root_uri.clone()) + ( + config.workspace_settings.import_map.clone(), + config.root_uri.clone(), + ) }; if let Some(import_map_str) = &maybe_import_map { info!("Updating import map from: \"{}\"", import_map_str); @@ -345,7 +347,8 @@ impl Inner { async fn update_registries(&mut self) -> Result<(), AnyError> { let mark = self.performance.mark("update_registries"); - for (registry, enabled) in self.config.settings.suggest.imports.hosts.iter() + for (registry, enabled) in + self.config.workspace_settings.suggest.imports.hosts.iter() { if *enabled { info!("Enabling auto complete registry for: {}", registry); @@ -376,13 +379,16 @@ impl Inner { })); let (maybe_config, maybe_root_uri) = { let config = &self.config; - if config.settings.unstable { + if config.workspace_settings.unstable { let unstable_libs = json!({ "lib": ["deno.ns", "deno.window", "deno.unstable"] }); tsconfig.merge(&unstable_libs); } - (config.settings.config.clone(), config.root_uri.clone()) + ( + config.workspace_settings.config.clone(), + config.root_uri.clone(), + ) }; if let Some(config_str) = &maybe_config { info!("Updating TypeScript configuration from: \"{}\"", config_str); @@ -491,7 +497,7 @@ impl Inner { let config = &mut self.config; config.root_uri = params.root_uri; if let Some(value) = params.initialization_options { - config.update(value)?; + config.update_workspace(value)?; } config.update_capabilities(¶ms.capabilities); } @@ -573,13 +579,35 @@ impl Inner { async fn did_open(&mut self, params: DidOpenTextDocumentParams) { let mark = self.performance.mark("did_open"); + let specifier = self.url_map.normalize_url(¶ms.text_document.uri); + + // we only query the individual resource file if the client supports it + if self.config.client_capabilities.workspace_configuration + && !self.config.contains(&specifier) + { + if let Ok(value) = self + .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" { // 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 // already managed by the language service return; } - let specifier = self.url_map.normalize_url(¶ms.text_document.uri); self.documents.open( specifier.clone(), params.text_document.version, @@ -635,56 +663,69 @@ impl Inner { params: DidChangeConfigurationParams, ) { let mark = self.performance.mark("did_change_configuration"); - let config = if self.config.client_capabilities.workspace_configuration { - self - .client - .configuration(vec![ConfigurationItem { - scope_uri: None, - section: Some("deno".to_string()), - }]) - .await - .map(|vec| vec.get(0).cloned()) - .unwrap_or_else(|err| { - error!("failed to fetch the extension settings {}", err); - None - }) - } else { - params - .settings - .as_object() - .map(|settings| settings.get("deno")) - .flatten() - .cloned() - }; - if let Some(config) = config { - if let Err(err) = self.config.update(config) { + if self.config.client_capabilities.workspace_configuration { + let specifiers: Vec = + self.config.specifier_settings.keys().cloned().collect(); + 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 + .settings + .as_object() + .map(|settings| settings.get(SETTINGS_SECTION)) + .flatten() + .cloned() + { + if let Err(err) = self.config.update_workspace(config) { error!("failed to update settings: {}", err); } - if let Err(err) = self.update_import_map().await { - self - .client - .show_message(MessageType::Warning, err.to_string()) - .await; - } - if let Err(err) = self.update_registries().await { - self - .client - .show_message(MessageType::Warning, err.to_string()) - .await; - } - if let Err(err) = self.update_tsconfig().await { - self - .client - .show_message(MessageType::Warning, err.to_string()) - .await; - } - if let Err(err) = self.diagnostics_server.update() { - error!("{}", err); - } - } else { - error!("received empty extension settings from the client"); } + + if let Err(err) = self.update_import_map().await { + self + .client + .show_message(MessageType::Warning, err.to_string()) + .await; + } + if let Err(err) = self.update_registries().await { + self + .client + .show_message(MessageType::Warning, err.to_string()) + .await; + } + if let Err(err) = self.update_tsconfig().await { + self + .client + .show_message(MessageType::Warning, err.to_string()) + .await; + } + if let Err(err) = self.diagnostics_server.update() { + error!("{}", err); + } + self.performance.measure(mark); } @@ -719,14 +760,14 @@ impl Inner { } async fn document_symbol( - &self, + &mut self, params: DocumentSymbolParams, ) -> LspResult> { - if !self.enabled() { + let specifier = self.url_map.normalize_url(¶ms.text_document.uri); + if !self.config.specifier_enabled(&specifier) { return Ok(None); } let mark = self.performance.mark("document_symbol"); - let specifier = self.url_map.normalize_url(¶ms.text_document.uri); let line_index = if let Some(line_index) = self.get_line_index_sync(&specifier) { @@ -817,14 +858,15 @@ impl Inner { } } - async fn hover(&self, params: HoverParams) -> LspResult> { - if !self.enabled() { - return Ok(None); - } - let mark = self.performance.mark("hover"); + async fn hover(&mut self, params: HoverParams) -> LspResult> { let specifier = self .url_map .normalize_url(¶ms.text_document_position_params.text_document.uri); + if !self.config.specifier_enabled(&specifier) { + return Ok(None); + } + let mark = self.performance.mark("hover"); + let line_index = if let Some(line_index) = self.get_line_index_sync(&specifier) { line_index @@ -860,12 +902,12 @@ impl Inner { &mut self, params: CodeActionParams, ) -> LspResult> { - if !self.enabled() { + let specifier = self.url_map.normalize_url(¶ms.text_document.uri); + if !self.config.specifier_enabled(&specifier) { return Ok(None); } let mark = self.performance.mark("code_action"); - let specifier = self.url_map.normalize_url(¶ms.text_document.uri); let fixable_diagnostics: Vec<&Diagnostic> = params .context .diagnostics @@ -1016,12 +1058,14 @@ impl Inner { &mut self, params: CodeLensParams, ) -> LspResult>> { - if !self.enabled() || !self.config.settings.enabled_code_lens() { + let specifier = self.url_map.normalize_url(¶ms.text_document.uri); + if !self.config.specifier_enabled(&specifier) + || !self.config.workspace_settings.enabled_code_lens() + { return Ok(None); } let mark = self.performance.mark("code_lens"); - let specifier = self.url_map.normalize_url(¶ms.text_document.uri); let line_index = self.get_line_index_sync(&specifier).unwrap(); let navigation_tree = self.get_navigation_tree(&specifier).await.map_err(|err| { @@ -1037,7 +1081,7 @@ impl Inner { let mut code_lenses = cl.borrow_mut(); // TSC Implementations Code Lens - if self.config.settings.code_lens.implementations { + if self.config.workspace_settings.code_lens.implementations { let source = CodeLensSource::Implementations; match i.kind { tsc::ScriptElementKind::InterfaceElement => { @@ -1061,7 +1105,7 @@ impl Inner { } // TSC References Code Lens - if self.config.settings.code_lens.references { + if self.config.workspace_settings.code_lens.references { let source = CodeLensSource::References; if let Some(parent) = &mp { if parent.kind == tsc::ScriptElementKind::EnumElement { @@ -1070,7 +1114,12 @@ impl Inner { } match i.kind { tsc::ScriptElementKind::FunctionElement => { - if self.config.settings.code_lens.references_all_functions { + if self + .config + .workspace_settings + .code_lens + .references_all_functions + { code_lenses.push(i.to_code_lens( &line_index, &specifier, @@ -1317,16 +1366,17 @@ impl Inner { } async fn document_highlight( - &self, + &mut self, params: DocumentHighlightParams, ) -> LspResult>> { - if !self.enabled() { - return Ok(None); - } - let mark = self.performance.mark("document_highlight"); let specifier = self .url_map .normalize_url(¶ms.text_document_position_params.text_document.uri); + if !self.config.specifier_enabled(&specifier) { + return Ok(None); + } + + let mark = self.performance.mark("document_highlight"); let line_index = if let Some(line_index) = self.get_line_index_sync(&specifier) { line_index @@ -1369,13 +1419,13 @@ impl Inner { &mut self, params: ReferenceParams, ) -> LspResult>> { - if !self.enabled() { - return Ok(None); - } - let mark = self.performance.mark("references"); let specifier = self .url_map .normalize_url(¶ms.text_document_position.text_document.uri); + if !self.config.specifier_enabled(&specifier) { + return Ok(None); + } + let mark = self.performance.mark("references"); let line_index = if let Some(line_index) = self.get_line_index_sync(&specifier) { line_index @@ -1424,13 +1474,13 @@ impl Inner { &mut self, params: GotoDefinitionParams, ) -> LspResult> { - if !self.enabled() { - return Ok(None); - } - let mark = self.performance.mark("goto_definition"); let specifier = self .url_map .normalize_url(¶ms.text_document_position_params.text_document.uri); + if !self.config.specifier_enabled(&specifier) { + return Ok(None); + } + let mark = self.performance.mark("goto_definition"); let line_index = if let Some(line_index) = self.get_line_index_sync(&specifier) { line_index @@ -1464,16 +1514,16 @@ impl Inner { } async fn completion( - &self, + &mut self, params: CompletionParams, ) -> LspResult> { - if !self.enabled() { - return Ok(None); - } - let mark = self.performance.mark("completion"); let specifier = self .url_map .normalize_url(¶ms.text_document_position.text_document.uri); + if !self.config.specifier_enabled(&specifier) { + return Ok(None); + } + let mark = self.performance.mark("completion"); // Import specifiers are something wholly internal to Deno, so for // completions, we will use internal logic and if there are completions // for imports, we will return those and not send a message into tsc, where @@ -1526,7 +1576,7 @@ impl Inner { if let Some(completions) = maybe_completion_info { let results = completions.as_completion_response( &line_index, - &self.config.settings.suggest, + &self.config.workspace_settings.suggest, &specifier, position, ); @@ -1583,13 +1633,13 @@ impl Inner { &mut self, params: GotoImplementationParams, ) -> LspResult> { - if !self.enabled() { - return Ok(None); - } - let mark = self.performance.mark("goto_implementation"); let specifier = self .url_map .normalize_url(¶ms.text_document_position_params.text_document.uri); + if !self.config.specifier_enabled(&specifier) { + return Ok(None); + } + let mark = self.performance.mark("goto_implementation"); let line_index = if let Some(line_index) = self.get_line_index_sync(&specifier) { line_index @@ -1630,14 +1680,14 @@ impl Inner { } async fn folding_range( - &self, + &mut self, params: FoldingRangeParams, ) -> LspResult>> { - if !self.enabled() { + let specifier = self.url_map.normalize_url(¶ms.text_document.uri); + if !self.config.specifier_enabled(&specifier) { return Ok(None); } let mark = self.performance.mark("folding_range"); - let specifier = self.url_map.normalize_url(¶ms.text_document.uri); let line_index = if let Some(line_index) = self.get_line_index_sync(&specifier) { @@ -1690,11 +1740,11 @@ impl Inner { &mut self, params: CallHierarchyIncomingCallsParams, ) -> LspResult>> { - if !self.enabled() { + let specifier = self.url_map.normalize_url(¶ms.item.uri); + if !self.config.specifier_enabled(&specifier) { return Ok(None); } let mark = self.performance.mark("incoming_calls"); - let specifier = self.url_map.normalize_url(¶ms.item.uri); let line_index = if let Some(line_index) = self.get_line_index_sync(&specifier) { @@ -1744,11 +1794,11 @@ impl Inner { &mut self, params: CallHierarchyOutgoingCallsParams, ) -> LspResult>> { - if !self.enabled() { + let specifier = self.url_map.normalize_url(¶ms.item.uri); + if !self.config.specifier_enabled(&specifier) { return Ok(None); } let mark = self.performance.mark("outgoing_calls"); - let specifier = self.url_map.normalize_url(¶ms.item.uri); let line_index = if let Some(line_index) = self.get_line_index_sync(&specifier) { @@ -1799,13 +1849,13 @@ impl Inner { &mut self, params: CallHierarchyPrepareParams, ) -> LspResult>> { - if !self.enabled() { - return Ok(None); - } - let mark = self.performance.mark("prepare_call_hierarchy"); let specifier = self .url_map .normalize_url(¶ms.text_document_position_params.text_document.uri); + if !self.config.specifier_enabled(&specifier) { + return Ok(None); + } + let mark = self.performance.mark("prepare_call_hierarchy"); let line_index = if let Some(line_index) = self.get_line_index_sync(&specifier) { @@ -1876,13 +1926,13 @@ impl Inner { &mut self, params: RenameParams, ) -> LspResult> { - if !self.enabled() { - return Ok(None); - } - let mark = self.performance.mark("rename"); let specifier = self .url_map .normalize_url(¶ms.text_document_position.text_document.uri); + if !self.config.specifier_enabled(&specifier) { + return Ok(None); + } + let mark = self.performance.mark("rename"); let line_index = if let Some(line_index) = self.get_line_index_sync(&specifier) { @@ -1963,14 +2013,14 @@ impl Inner { } async fn selection_range( - &self, + &mut self, params: SelectionRangeParams, ) -> LspResult>> { - if !self.enabled() { + let specifier = self.url_map.normalize_url(¶ms.text_document.uri); + if !self.config.specifier_enabled(&specifier) { return Ok(None); } let mark = self.performance.mark("selection_range"); - let specifier = self.url_map.normalize_url(¶ms.text_document.uri); let line_index = if let Some(line_index) = self.get_line_index_sync(&specifier) { @@ -2005,14 +2055,14 @@ impl Inner { } async fn semantic_tokens_full( - &self, + &mut self, params: SemanticTokensParams, ) -> LspResult> { - if !self.enabled() { + let specifier = self.url_map.normalize_url(¶ms.text_document.uri); + if !self.config.specifier_enabled(&specifier) { return Ok(None); } let mark = self.performance.mark("semantic_tokens_full"); - let specifier = self.url_map.normalize_url(¶ms.text_document.uri); let line_index = if let Some(line_index) = self.get_line_index_sync(&specifier) { @@ -2052,14 +2102,14 @@ impl Inner { } async fn semantic_tokens_range( - &self, + &mut self, params: SemanticTokensRangeParams, ) -> LspResult> { - if !self.enabled() { + let specifier = self.url_map.normalize_url(¶ms.text_document.uri); + if !self.config.specifier_enabled(&specifier) { return Ok(None); } let mark = self.performance.mark("semantic_tokens_range"); - let specifier = self.url_map.normalize_url(¶ms.text_document.uri); let line_index = if let Some(line_index) = self.get_line_index_sync(&specifier) { @@ -2098,16 +2148,16 @@ impl Inner { } async fn signature_help( - &self, + &mut self, params: SignatureHelpParams, ) -> LspResult> { - if !self.enabled() { - return Ok(None); - } - let mark = self.performance.mark("signature_help"); let specifier = self .url_map .normalize_url(¶ms.text_document_position_params.text_document.uri); + if !self.config.specifier_enabled(&specifier) { + return Ok(None); + } + let mark = self.performance.mark("signature_help"); let line_index = if let Some(line_index) = self.get_line_index_sync(&specifier) { line_index diff --git a/cli/lsp/urls.rs b/cli/lsp/urls.rs index 559f1652ae..49049b5351 100644 --- a/cli/lsp/urls.rs +++ b/cli/lsp/urls.rs @@ -54,7 +54,7 @@ fn hash_data_specifier(specifier: &ModuleSpecifier) -> String { /// 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 /// to allow the Deno language server to manage these as virtual documents. -#[derive(Debug, Default)] +#[derive(Debug, Default, Clone)] pub struct LspUrlMap { specifier_to_url: HashMap, url_to_specifier: HashMap, diff --git a/docs/typescript/faqs.md b/docs/typescript/faqs.md index 65f1d92b7d..3b5b7e0b1a 100644 --- a/docs/typescript/faqs.md +++ b/docs/typescript/faqs.md @@ -114,3 +114,16 @@ compiler. Its main purpose is to ensure that TypeScript and JavaScript can run under Deno. The secondary ability to do TypeScript and JavaScript emitting via the runtime API `Deno.emit()` is intended to be simple and straight forward and support a certain set of use cases. + +### How do I combine Deno code with non-Deno code in my IDE? + +The Deno language server supports the ability to have a "per-resource" +configuration of enabling Deno or not. This also requires a client IDE to +support this ability. For Visual Studio Code the official +[Deno extension](https://marketplace.visualstudio.com/items?itemName=denoland.vscode-deno) +supports the vscode concept of +[multi-root workspace](https://code.visualstudio.com/docs/editor/multi-root-workspaces). +This means you just need to add folders to the workspace and set the +`deno.enable` setting as required on each folder. + +For other IDEs, the client extensions needs to support the similar IDE concepts.