text document sync dynamic caps

Smit Barmase created

Change summary

crates/lsp/src/lsp.rs           | 16 ++++++
crates/project/src/lsp_store.rs | 92 ++++++++++++++++++++++------------
2 files changed, 75 insertions(+), 33 deletions(-)

Detailed changes

crates/lsp/src/lsp.rs 🔗

@@ -87,6 +87,7 @@ pub struct LanguageServer {
     process_name: Arc<str>,
     binary: LanguageServerBinary,
     static_capabilities: RwLock<ServerCapabilities>,
+    dynamic_capabilities: RwLock<DynamicCapabilities>,
     /// Configuration sent to the server, stored for display in the language server logs
     /// buffer. This is represented as the message sent to the LSP in order to avoid cloning it (can
     /// be large in cases like sending schemas to the json server).
@@ -301,6 +302,12 @@ pub struct AdapterServerCapabilities {
     pub code_action_kinds: Option<Vec<CodeActionKind>>,
 }
 
+#[derive(Debug, Default, Clone)]
+pub struct DynamicCapabilities {
+    pub text_document_sync_did_change: Option<HashMap<String, TextDocumentSyncKind>>,
+    pub text_document_sync_did_save: Option<HashMap<String, SaveOptions>>,
+}
+
 impl LanguageServer {
     /// Starts a language server process.
     pub fn new(
@@ -485,6 +492,7 @@ impl LanguageServer {
                 .unwrap_or_default(),
             binary,
             static_capabilities: Default::default(),
+            dynamic_capabilities: Default::default(),
             configuration,
             code_action_kinds,
             next_id: Default::default(),
@@ -1133,6 +1141,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());
+    }
+
     /// 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 🔗

@@ -7208,14 +7208,40 @@ impl LspStore {
                     .collect()
             };
 
-            let document_sync_kind = 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 = {
+                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 content_changes: Vec<_> = match document_sync_kind {
                 Some(lsp::TextDocumentSyncKind::FULL) => {
@@ -11843,12 +11869,11 @@ impl LspStore {
                         .map(serde_json::from_value::<lsp::TextDocumentSyncKind>)
                         .transpose()?
                     {
-                        server.update_capabilities(|capabilities| {
-                            let mut sync_options =
-                                Self::take_text_document_sync_options(capabilities);
-                            sync_options.change = Some(sync_kind);
-                            capabilities.text_document_sync =
-                                Some(lsp::TextDocumentSyncCapability::Options(sync_options));
+                        server.update_dynamic_capabilities(|dyn_caps| {
+                            let map = dyn_caps
+                                .text_document_sync_did_change
+                                .get_or_insert_with(HashMap::default);
+                            map.insert(reg.id.clone(), sync_kind);
                         });
                         notify_server_capabilities_updated(&server, cx);
                     }
@@ -11869,15 +11894,11 @@ impl LspStore {
                         })
                         .transpose()?
                     {
-                        server.update_capabilities(|capabilities| {
-                            let mut sync_options =
-                                Self::take_text_document_sync_options(capabilities);
-                            sync_options.save =
-                                Some(TextDocumentSyncSaveOptions::SaveOptions(lsp::SaveOptions {
-                                    include_text,
-                                }));
-                            capabilities.text_document_sync =
-                                Some(lsp::TextDocumentSyncCapability::Options(sync_options));
+                        server.update_dynamic_capabilities(|dyn_caps| {
+                            let map = dyn_caps
+                                .text_document_sync_did_save
+                                .get_or_insert_with(HashMap::default);
+                            map.insert(reg.id.clone(), lsp::SaveOptions { include_text });
                         });
                         notify_server_capabilities_updated(&server, cx);
                     }
@@ -12028,20 +12049,18 @@ impl LspStore {
                     notify_server_capabilities_updated(&server, cx);
                 }
                 "textDocument/didChange" => {
-                    server.update_capabilities(|capabilities| {
-                        let mut sync_options = Self::take_text_document_sync_options(capabilities);
-                        sync_options.change = None;
-                        capabilities.text_document_sync =
-                            Some(lsp::TextDocumentSyncCapability::Options(sync_options));
+                    server.update_dynamic_capabilities(|dyn_caps| {
+                        if let Some(map) = dyn_caps.text_document_sync_did_change.as_mut() {
+                            map.remove(&unreg.id);
+                        }
                     });
                     notify_server_capabilities_updated(&server, cx);
                 }
                 "textDocument/didSave" => {
-                    server.update_capabilities(|capabilities| {
-                        let mut sync_options = Self::take_text_document_sync_options(capabilities);
-                        sync_options.save = None;
-                        capabilities.text_document_sync =
-                            Some(lsp::TextDocumentSyncCapability::Options(sync_options));
+                    server.update_dynamic_capabilities(|dyn_caps| {
+                        if let Some(map) = dyn_caps.text_document_sync_did_save.as_mut() {
+                            map.remove(&unreg.id);
+                        }
                     });
                     notify_server_capabilities_updated(&server, cx);
                 }
@@ -13266,6 +13285,13 @@ 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.