diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index 0b0e7a1e19..494904cc5d 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -308,6 +308,15 @@ 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( @@ -1149,6 +1158,68 @@ impl LanguageServer { 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, + } + } + /// Get the reported capabilities of the running language server and /// what we know on the client/adapter-side of its capabilities. pub fn adapter_server_capabilities(&self) -> AdapterServerCapabilities { diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index 75a480d20c..5baa9f06fb 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -75,7 +75,7 @@ use lsp::{ LSP_REQUEST_TIMEOUT, LanguageServer, LanguageServerBinary, LanguageServerBinaryOptions, LanguageServerId, LanguageServerName, LanguageServerSelector, LspRequestFuture, MessageActionItem, MessageType, OneOf, RenameFilesParams, SymbolKind, - TextDocumentSyncSaveOptions, TextEdit, WillRenameFiles, WorkDoneProgressCancelParams, + TextEdit, WillRenameFiles, WorkDoneProgressCancelParams, WorkspaceFolder, notification::DidRenameFiles, }; use node_runtime::read_package_installed_version; @@ -7208,40 +7208,9 @@ impl LspStore { .collect() }; - let document_sync_kind = { - let dyn_caps = language_server.dynamic_capabilities(); - let dynamic = 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(lsp::TextDocumentSyncKind::FULL), - lsp::TextDocumentSyncKind::INCREMENTAL, - ) => lsp::TextDocumentSyncKind::INCREMENTAL, - (Some(curr), _) => curr, - }); - } - best - } - }); - dynamic.or_else(|| { - language_server - .capabilities() - .text_document_sync - .as_ref() - .and_then(|sync| match sync { - lsp::TextDocumentSyncCapability::Kind(kind) => Some(*kind), - lsp::TextDocumentSyncCapability::Options(options) => options.change, - }) - }) - }; + let document_sync_kind = language_server + .effective_text_document_sync() + .change; let content_changes: Vec<_> = match document_sync_kind { Some(lsp::TextDocumentSyncKind::FULL) => { @@ -7302,7 +7271,7 @@ impl LspStore { let local = self.as_local()?; for server in local.language_servers_for_worktree(worktree_id) { - if let Some(include_text) = include_text(server.as_ref()) { + if let Some(include_text) = server.effective_text_document_sync().save_include_text { let text = if include_text { Some(buffer.read(cx).text()) } else { @@ -13284,29 +13253,7 @@ async fn populate_labels_for_symbols( } } -fn include_text(server: &lsp::LanguageServer) -> Option { - let dyn_caps = server.dynamic_capabilities(); - if let Some(map) = dyn_caps.text_document_sync_did_save.as_ref() { - if !map.is_empty() { - let any_true = map.values().any(|opts| opts.include_text.unwrap_or(false)); - return Some(any_true); - } - } - match server.capabilities().text_document_sync.as_ref()? { - lsp::TextDocumentSyncCapability::Options(opts) => match opts.save.as_ref()? { - // Server wants didSave but didn't specify includeText. - lsp::TextDocumentSyncSaveOptions::Supported(true) => Some(false), - // Server doesn't want didSave at all. - lsp::TextDocumentSyncSaveOptions::Supported(false) => None, - // Server provided SaveOptions. - lsp::TextDocumentSyncSaveOptions::SaveOptions(save_options) => { - Some(save_options.include_text.unwrap_or(false)) - } - }, - // We do not have any save info. Kind affects didChange only. - lsp::TextDocumentSyncCapability::Kind(_) => None, - } -} +// 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,