diff --git a/crates/lsp/src/capabilities.rs b/crates/lsp/src/capabilities.rs new file mode 100644 index 0000000000..d8026df027 --- /dev/null +++ b/crates/lsp/src/capabilities.rs @@ -0,0 +1,90 @@ +use super::DynamicCapabilities; +use lsp_types::{ + ServerCapabilities, TextDocumentSyncCapability, TextDocumentSyncKind, + TextDocumentSyncSaveOptions, +}; + +pub mod cap { + pub struct DidChangeTextDocument; + pub struct DidSaveTextDocument; +} + +pub trait EffectiveCapability { + type Value; + fn compute(static_caps: &ServerCapabilities, dynamic_caps: &DynamicCapabilities) + -> Self::Value; +} + +impl EffectiveCapability for cap::DidChangeTextDocument { + type Value = Option; + + fn compute( + static_caps: &ServerCapabilities, + dynamic_caps: &DynamicCapabilities, + ) -> Self::Value { + dynamic_caps + .text_document_sync_did_change + .as_ref() + .and_then(|id_to_sync_kind_map| { + if id_to_sync_kind_map.is_empty() { + None + } else { + let mut best: Option = None; + for kind in id_to_sync_kind_map.values() { + best = Some(match (best, kind) { + (None, kind) => *kind, + ( + Some(TextDocumentSyncKind::FULL), + &TextDocumentSyncKind::INCREMENTAL, + ) => TextDocumentSyncKind::INCREMENTAL, + (Some(kind), _) => kind, + }); + } + best + } + }) + .or_else(|| { + static_caps + .text_document_sync + .as_ref() + .and_then(|sync| match sync { + TextDocumentSyncCapability::Kind(kind) => Some(*kind), + TextDocumentSyncCapability::Options(opts) => opts.change, + }) + }) + } +} + +impl EffectiveCapability for cap::DidSaveTextDocument { + type Value = Option; + + fn compute( + static_caps: &ServerCapabilities, + dynamic_caps: &DynamicCapabilities, + ) -> Self::Value { + dynamic_caps + .text_document_sync_did_save + .as_ref() + .and_then(|id_to_save_options_map| { + if id_to_save_options_map.is_empty() { + None + } else { + Some( + id_to_save_options_map + .values() + .any(|opts| opts.include_text.unwrap_or(false)), + ) + } + }) + .or_else(|| match static_caps.text_document_sync.as_ref()? { + TextDocumentSyncCapability::Options(opts) => match opts.save.as_ref()? { + TextDocumentSyncSaveOptions::Supported(true) => Some(false), + TextDocumentSyncSaveOptions::Supported(false) => None, + TextDocumentSyncSaveOptions::SaveOptions(save_opts) => { + Some(save_opts.include_text.unwrap_or(false)) + } + }, + TextDocumentSyncCapability::Kind(_) => None, + }) + } +} diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index 494904cc5d..9c2eb02120 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -1,5 +1,8 @@ +mod capabilities; mod input_handler; +pub use capabilities::{EffectiveCapability, cap}; + pub use lsp_types::request::*; pub use lsp_types::*; @@ -308,15 +311,6 @@ pub struct DynamicCapabilities { pub text_document_sync_did_save: Option>, } -/// Effective text document synchronization behavior, merging static and dynamic capabilities. -#[derive(Debug, Default, Clone)] -pub struct EffectiveTextDocumentSync { - /// Effective change sync kind (FULL or INCREMENTAL), if any. - pub change: Option, - /// Whether to include text on didSave, or None if didSave is not supported. - pub save_include_text: Option, -} - impl LanguageServer { /// Starts a language server process. pub fn new( @@ -1150,74 +1144,14 @@ impl LanguageServer { self.static_capabilities.read().clone() } - pub fn dynamic_capabilities(&self) -> DynamicCapabilities { - self.dynamic_capabilities.read().clone() - } - pub fn update_dynamic_capabilities(&self, update: impl FnOnce(&mut DynamicCapabilities)) { update(self.dynamic_capabilities.write().deref_mut()); } - pub fn effective_text_document_sync(&self) -> EffectiveTextDocumentSync { - let static_caps = self.capabilities(); - let dyn_caps = self.dynamic_capabilities(); - - let change = dyn_caps - .text_document_sync_did_change - .as_ref() - .and_then(|m| { - if m.is_empty() { - None - } else { - let mut best: Option = None; - for kind in m.values() { - best = Some(match (best, kind) { - (None, k) => *k, - ( - Some(TextDocumentSyncKind::FULL), - &TextDocumentSyncKind::INCREMENTAL, - ) => TextDocumentSyncKind::INCREMENTAL, - (Some(curr), _) => curr, - }); - } - best - } - }) - .or_else(|| { - static_caps - .text_document_sync - .as_ref() - .and_then(|sync| match sync { - TextDocumentSyncCapability::Kind(kind) => Some(*kind), - TextDocumentSyncCapability::Options(options) => options.change, - }) - }); - - let save_include_text = dyn_caps - .text_document_sync_did_save - .as_ref() - .and_then(|m| { - if m.is_empty() { - None - } else { - Some(m.values().any(|opts| opts.include_text.unwrap_or(false))) - } - }) - .or_else(|| match static_caps.text_document_sync.as_ref()? { - TextDocumentSyncCapability::Options(opts) => match opts.save.as_ref()? { - TextDocumentSyncSaveOptions::Supported(true) => Some(false), - TextDocumentSyncSaveOptions::Supported(false) => None, - TextDocumentSyncSaveOptions::SaveOptions(save_opts) => { - Some(save_opts.include_text.unwrap_or(false)) - } - }, - TextDocumentSyncCapability::Kind(_) => None, - }); - - EffectiveTextDocumentSync { - change, - save_include_text, - } + pub fn effective_capability(&self) -> Cap::Value { + let static_capabilities = self.capabilities(); + let dynamic_capabilities = self.dynamic_capabilities.read().clone(); + Cap::compute(&static_capabilities, &dynamic_capabilities) } /// Get the reported capabilities of the running language server and diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index 5baa9f06fb..389010a2ce 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -74,9 +74,8 @@ use lsp::{ FileOperationPatternKind, FileOperationRegistrationOptions, FileRename, FileSystemWatcher, LSP_REQUEST_TIMEOUT, LanguageServer, LanguageServerBinary, LanguageServerBinaryOptions, LanguageServerId, LanguageServerName, LanguageServerSelector, LspRequestFuture, - MessageActionItem, MessageType, OneOf, RenameFilesParams, SymbolKind, - TextEdit, WillRenameFiles, WorkDoneProgressCancelParams, - WorkspaceFolder, notification::DidRenameFiles, + MessageActionItem, MessageType, OneOf, RenameFilesParams, SymbolKind, TextEdit, + WillRenameFiles, WorkDoneProgressCancelParams, WorkspaceFolder, notification::DidRenameFiles, }; use node_runtime::read_package_installed_version; use parking_lot::Mutex; @@ -7208,9 +7207,8 @@ impl LspStore { .collect() }; - let document_sync_kind = language_server - .effective_text_document_sync() - .change; + let document_sync_kind = + language_server.effective_capability::(); let content_changes: Vec<_> = match document_sync_kind { Some(lsp::TextDocumentSyncKind::FULL) => { @@ -7271,7 +7269,9 @@ impl LspStore { let local = self.as_local()?; for server in local.language_servers_for_worktree(worktree_id) { - if let Some(include_text) = server.effective_text_document_sync().save_include_text { + if let Some(include_text) = + server.effective_capability::() + { let text = if include_text { Some(buffer.read(cx).text()) } else { @@ -11842,7 +11842,7 @@ impl LspStore { let map = dyn_caps .text_document_sync_did_change .get_or_insert_with(HashMap::default); - map.insert(reg.id.clone(), sync_kind); + map.insert(reg.id, sync_kind); }); notify_server_capabilities_updated(&server, cx); } @@ -11867,7 +11867,7 @@ impl LspStore { let map = dyn_caps .text_document_sync_did_save .get_or_insert_with(HashMap::default); - map.insert(reg.id.clone(), lsp::SaveOptions { include_text }); + map.insert(reg.id, lsp::SaveOptions { include_text }); }); notify_server_capabilities_updated(&server, cx); } @@ -13253,8 +13253,6 @@ async fn populate_labels_for_symbols( } } -// include_text logic moved into lsp::LanguageServer::effective_text_document_sync() - /// Completion items are displayed in a `UniformList`. /// Usually, those items are single-line strings, but in LSP responses, /// completion items `label`, `detail` and `label_details.description` may contain newlines or long spaces.