effective caps

Smit Barmase created

Change summary

crates/lsp/src/lsp.rs           | 71 +++++++++++++++++++++++++++++++++++
crates/project/src/lsp_store.rs | 65 ++-----------------------------
2 files changed, 77 insertions(+), 59 deletions(-)

Detailed changes

crates/lsp/src/lsp.rs 🔗

@@ -308,6 +308,15 @@ pub struct DynamicCapabilities {
     pub text_document_sync_did_save: Option<HashMap<String, SaveOptions>>,
 }
 
+/// 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<TextDocumentSyncKind>,
+    /// Whether to include text on didSave, or None if didSave is not supported.
+    pub save_include_text: Option<bool>,
+}
+
 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<TextDocumentSyncKind> = 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 {

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<lsp::TextDocumentSyncKind> = 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<bool> {
-    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,