From cdb4db55da5242c304d2cc48c9e0a0faeee48c9d Mon Sep 17 00:00:00 2001 From: Smit Barmase Date: Sat, 23 Aug 2025 20:12:12 +0530 Subject: [PATCH 01/10] remove redudant Option --- crates/project/src/lsp_store.rs | 83 +++++++++++++++------------------ 1 file changed, 38 insertions(+), 45 deletions(-) diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index d2958dce01..853490ddac 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -11706,12 +11706,11 @@ impl LspStore { // Ignore payload since we notify clients of setting changes unconditionally, relying on them pulling the latest settings. } "workspace/symbol" => { - if let Some(options) = parse_register_capabilities(reg)? { - server.update_capabilities(|capabilities| { - capabilities.workspace_symbol_provider = Some(options); - }); - notify_server_capabilities_updated(&server, cx); - } + let options = parse_register_capabilities(reg)?; + server.update_capabilities(|capabilities| { + capabilities.workspace_symbol_provider = Some(options); + }); + notify_server_capabilities_updated(&server, cx); } "workspace/fileOperations" => { if let Some(options) = reg.register_options { @@ -11735,12 +11734,11 @@ impl LspStore { } } "textDocument/rangeFormatting" => { - if let Some(options) = parse_register_capabilities(reg)? { - server.update_capabilities(|capabilities| { - capabilities.document_range_formatting_provider = Some(options); - }); - notify_server_capabilities_updated(&server, cx); - } + let options = parse_register_capabilities(reg)?; + server.update_capabilities(|capabilities| { + capabilities.document_range_formatting_provider = Some(options); + }); + notify_server_capabilities_updated(&server, cx); } "textDocument/onTypeFormatting" => { if let Some(options) = reg @@ -11755,36 +11753,32 @@ impl LspStore { } } "textDocument/formatting" => { - if let Some(options) = parse_register_capabilities(reg)? { - server.update_capabilities(|capabilities| { - capabilities.document_formatting_provider = Some(options); - }); - notify_server_capabilities_updated(&server, cx); - } + let options = parse_register_capabilities(reg)?; + server.update_capabilities(|capabilities| { + capabilities.document_formatting_provider = Some(options); + }); + notify_server_capabilities_updated(&server, cx); } "textDocument/rename" => { - if let Some(options) = parse_register_capabilities(reg)? { - server.update_capabilities(|capabilities| { - capabilities.rename_provider = Some(options); - }); - notify_server_capabilities_updated(&server, cx); - } + let options = parse_register_capabilities(reg)?; + server.update_capabilities(|capabilities| { + capabilities.rename_provider = Some(options); + }); + notify_server_capabilities_updated(&server, cx); } "textDocument/inlayHint" => { - if let Some(options) = parse_register_capabilities(reg)? { - server.update_capabilities(|capabilities| { - capabilities.inlay_hint_provider = Some(options); - }); - notify_server_capabilities_updated(&server, cx); - } + let options = parse_register_capabilities(reg)?; + server.update_capabilities(|capabilities| { + capabilities.inlay_hint_provider = Some(options); + }); + notify_server_capabilities_updated(&server, cx); } "textDocument/documentSymbol" => { - if let Some(options) = parse_register_capabilities(reg)? { - server.update_capabilities(|capabilities| { - capabilities.document_symbol_provider = Some(options); - }); - notify_server_capabilities_updated(&server, cx); - } + let options = parse_register_capabilities(reg)?; + server.update_capabilities(|capabilities| { + capabilities.document_symbol_provider = Some(options); + }); + notify_server_capabilities_updated(&server, cx); } "textDocument/codeAction" => { if let Some(options) = reg @@ -11800,12 +11794,11 @@ impl LspStore { } } "textDocument/definition" => { - if let Some(options) = parse_register_capabilities(reg)? { - server.update_capabilities(|capabilities| { - capabilities.definition_provider = Some(options); - }); - notify_server_capabilities_updated(&server, cx); - } + let options = parse_register_capabilities(reg)?; + server.update_capabilities(|capabilities| { + capabilities.definition_provider = Some(options); + }); + notify_server_capabilities_updated(&server, cx); } "textDocument/completion" => { if let Some(caps) = reg @@ -12184,10 +12177,10 @@ impl LspStore { // https://github.com/microsoft/vscode-languageserver-node/blob/d90a87f9557a0df9142cfb33e251cfa6fe27d970/client/src/common/client.ts#L2133 fn parse_register_capabilities( reg: lsp::Registration, -) -> anyhow::Result>> { +) -> Result> { Ok(match reg.register_options { - Some(options) => Some(OneOf::Right(serde_json::from_value::(options)?)), - None => Some(OneOf::Left(true)), + Some(options) => OneOf::Right(serde_json::from_value::(options)?), + None => OneOf::Left(true), }) } From 7684bc265b8660ba2c668ca7ea07ee0b938ad225 Mon Sep 17 00:00:00 2001 From: Smit Barmase Date: Sun, 24 Aug 2025 03:34:28 +0530 Subject: [PATCH 02/10] static --- crates/lsp/src/lsp.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index 942225d098..6030882f2b 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -86,7 +86,7 @@ pub struct LanguageServer { name: LanguageServerName, process_name: Arc, binary: LanguageServerBinary, - capabilities: RwLock, + static_capabilities: RwLock, /// 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). @@ -484,7 +484,7 @@ impl LanguageServer { .map(|name| Arc::from(name.to_string_lossy())) .unwrap_or_default(), binary, - capabilities: Default::default(), + static_capabilities: Default::default(), configuration, code_action_kinds, next_id: Default::default(), @@ -898,7 +898,7 @@ impl LanguageServer { if let Some(info) = response.server_info { self.process_name = info.name.into(); } - self.capabilities = RwLock::new(response.capabilities); + self.static_capabilities = RwLock::new(response.capabilities); self.configuration = configuration; self.notify::(&InitializedParams {})?; @@ -1130,7 +1130,7 @@ impl LanguageServer { /// Get the reported capabilities of the running language server. pub fn capabilities(&self) -> ServerCapabilities { - self.capabilities.read().clone() + self.static_capabilities.read().clone() } /// Get the reported capabilities of the running language server and @@ -1143,7 +1143,7 @@ impl LanguageServer { } pub fn update_capabilities(&self, update: impl FnOnce(&mut ServerCapabilities)) { - update(self.capabilities.write().deref_mut()); + update(self.static_capabilities.write().deref_mut()); } pub fn configuration(&self) -> &Value { From 06170981754ad333601e4d944bd3c4a835815c0b Mon Sep 17 00:00:00 2001 From: Smit Barmase Date: Mon, 25 Aug 2025 12:47:36 +0530 Subject: [PATCH 03/10] text document sync dynamic caps --- crates/lsp/src/lsp.rs | 16 ++++++ crates/project/src/lsp_store.rs | 92 +++++++++++++++++++++------------ 2 files changed, 75 insertions(+), 33 deletions(-) diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index 6030882f2b..0b0e7a1e19 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -87,6 +87,7 @@ pub struct LanguageServer { process_name: Arc, binary: LanguageServerBinary, static_capabilities: RwLock, + dynamic_capabilities: RwLock, /// 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>, } +#[derive(Debug, Default, Clone)] +pub struct DynamicCapabilities { + pub text_document_sync_did_change: Option>, + pub text_document_sync_did_save: Option>, +} + 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 { diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index 853490ddac..75a480d20c 100644 --- a/crates/project/src/lsp_store.rs +++ b/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 = 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::) .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 { + 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. From 3410652c719f5c83d8c4468faa70ced91e3d18d0 Mon Sep 17 00:00:00 2001 From: Smit Barmase Date: Mon, 25 Aug 2025 13:31:45 +0530 Subject: [PATCH 04/10] effective caps --- crates/lsp/src/lsp.rs | 71 +++++++++++++++++++++++++++++++++ crates/project/src/lsp_store.rs | 65 +++--------------------------- 2 files changed, 77 insertions(+), 59 deletions(-) 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, From 6f6976517263e576c3600c4e7cab60f1cd156ab9 Mon Sep 17 00:00:00 2001 From: Smit Barmase Date: Mon, 25 Aug 2025 17:02:23 +0530 Subject: [PATCH 05/10] move effective caps --- crates/lsp/src/capabilities.rs | 90 +++++++++++++++++++++++++++++++++ crates/lsp/src/lsp.rs | 80 +++-------------------------- crates/project/src/lsp_store.rs | 20 ++++---- 3 files changed, 106 insertions(+), 84 deletions(-) create mode 100644 crates/lsp/src/capabilities.rs 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. From e047398756d9a6d4a4112f7e70fefa015120797f Mon Sep 17 00:00:00 2001 From: Smit Barmase Date: Mon, 25 Aug 2025 17:58:01 +0530 Subject: [PATCH 06/10] fix sync did change effective caps --- crates/lsp/src/capabilities.rs | 27 ++++++++++++++------------- crates/lsp/src/lsp.rs | 1 + 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/crates/lsp/src/capabilities.rs b/crates/lsp/src/capabilities.rs index d8026df027..ae803e4269 100644 --- a/crates/lsp/src/capabilities.rs +++ b/crates/lsp/src/capabilities.rs @@ -27,20 +27,21 @@ impl EffectiveCapability for cap::DidChangeTextDocument { .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, - }); + return None; + } + let mut has_incremental = false; + for &kind in id_to_sync_kind_map.values() { + if kind == TextDocumentSyncKind::FULL { + return Some(TextDocumentSyncKind::FULL); } - best + if kind == TextDocumentSyncKind::INCREMENTAL { + has_incremental = true; + } + } + if has_incremental { + Some(TextDocumentSyncKind::INCREMENTAL) + } else { + None } }) .or_else(|| { diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index 9c2eb02120..13990d4533 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -1148,6 +1148,7 @@ impl LanguageServer { update(self.dynamic_capabilities.write().deref_mut()); } + /// Get effective capabilities by combining static and dynamic capabilities. pub fn effective_capability(&self) -> Cap::Value { let static_capabilities = self.capabilities(); let dynamic_capabilities = self.dynamic_capabilities.read().clone(); From ae6b79a398bcb62077e358d2171d16eb8584c4a2 Mon Sep 17 00:00:00 2001 From: Smit Barmase Date: Mon, 25 Aug 2025 17:58:50 +0530 Subject: [PATCH 07/10] remove redudant method --- crates/project/src/lsp_store.rs | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index 389010a2ce..7a21b3f7db 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -12140,20 +12140,6 @@ impl LspStore { Ok(()) } - fn take_text_document_sync_options( - capabilities: &mut lsp::ServerCapabilities, - ) -> lsp::TextDocumentSyncOptions { - match capabilities.text_document_sync.take() { - Some(lsp::TextDocumentSyncCapability::Options(sync_options)) => sync_options, - Some(lsp::TextDocumentSyncCapability::Kind(sync_kind)) => { - let mut sync_options = lsp::TextDocumentSyncOptions::default(); - sync_options.change = Some(sync_kind); - sync_options - } - None => lsp::TextDocumentSyncOptions::default(), - } - } - #[cfg(any(test, feature = "test-support"))] pub fn forget_code_lens_task(&mut self, buffer_id: BufferId) -> Option { let data = self.lsp_code_lens.get_mut(&buffer_id)?; From 31076619b30107a47d2c55bc93c1ff76db6b25ff Mon Sep 17 00:00:00 2001 From: Smit Barmase Date: Mon, 25 Aug 2025 18:49:50 +0530 Subject: [PATCH 08/10] store document selector --- crates/lsp/src/capabilities.rs | 11 ++++++----- crates/lsp/src/lsp.rs | 5 +++-- crates/project/src/lsp_store.rs | 31 ++++++++----------------------- 3 files changed, 17 insertions(+), 30 deletions(-) diff --git a/crates/lsp/src/capabilities.rs b/crates/lsp/src/capabilities.rs index ae803e4269..14053f8e4b 100644 --- a/crates/lsp/src/capabilities.rs +++ b/crates/lsp/src/capabilities.rs @@ -1,9 +1,10 @@ -use super::DynamicCapabilities; use lsp_types::{ ServerCapabilities, TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncSaveOptions, }; +use super::DynamicCapabilities; + pub mod cap { pub struct DidChangeTextDocument; pub struct DidSaveTextDocument; @@ -30,11 +31,11 @@ impl EffectiveCapability for cap::DidChangeTextDocument { return None; } let mut has_incremental = false; - for &kind in id_to_sync_kind_map.values() { - if kind == TextDocumentSyncKind::FULL { + for data in id_to_sync_kind_map.values() { + if data.sync_kind == TextDocumentSyncKind::FULL { return Some(TextDocumentSyncKind::FULL); } - if kind == TextDocumentSyncKind::INCREMENTAL { + if data.sync_kind == TextDocumentSyncKind::INCREMENTAL { has_incremental = true; } } @@ -73,7 +74,7 @@ impl EffectiveCapability for cap::DidSaveTextDocument { Some( id_to_save_options_map .values() - .any(|opts| opts.include_text.unwrap_or(false)), + .any(|data| data.include_text), ) } }) diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index 13990d4533..b618f7e6c0 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -307,8 +307,9 @@ pub struct AdapterServerCapabilities { #[derive(Debug, Default, Clone)] pub struct DynamicCapabilities { - pub text_document_sync_did_change: Option>, - pub text_document_sync_did_save: Option>, + pub text_document_sync_did_change: + Option>, + pub text_document_sync_did_save: Option>, } impl LanguageServer { diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index 7a21b3f7db..7cd9cb9a25 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -11832,42 +11832,27 @@ impl LspStore { } } "textDocument/didChange" => { - if let Some(sync_kind) = reg - .register_options - .and_then(|opts| opts.get("syncKind").cloned()) - .map(serde_json::from_value::) - .transpose()? - { + if let Some(options) = reg.register_options { + let options: lsp::TextDocumentChangeRegistrationOptions = + serde_json::from_value(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, sync_kind); + map.insert(reg.id, options); }); notify_server_capabilities_updated(&server, cx); } } "textDocument/didSave" => { - if let Some(include_text) = reg - .register_options - .map(|opts| { - let transpose = opts - .get("includeText") - .cloned() - .map(serde_json::from_value::>) - .transpose(); - match transpose { - Ok(value) => Ok(value.flatten()), - Err(e) => Err(e), - } - }) - .transpose()? - { + if let Some(options) = reg.register_options { + let options: lsp::TextDocumentSaveRegistrationOptions = + serde_json::from_value(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, lsp::SaveOptions { include_text }); + map.insert(reg.id, options); }); notify_server_capabilities_updated(&server, cx); } From 11a5d86999b7d86f6f81e1e7c2a00e26cd8cf56e Mon Sep 17 00:00:00 2001 From: Smit Barmase Date: Mon, 25 Aug 2025 19:07:44 +0530 Subject: [PATCH 09/10] fix sync kind --- crates/lsp/src/capabilities.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/crates/lsp/src/capabilities.rs b/crates/lsp/src/capabilities.rs index 14053f8e4b..c4e30b71a6 100644 --- a/crates/lsp/src/capabilities.rs +++ b/crates/lsp/src/capabilities.rs @@ -32,10 +32,16 @@ impl EffectiveCapability for cap::DidChangeTextDocument { } let mut has_incremental = false; for data in id_to_sync_kind_map.values() { - if data.sync_kind == TextDocumentSyncKind::FULL { + let sync_kind = match data.sync_kind { + 0 => Some(TextDocumentSyncKind::NONE), + 1 => Some(TextDocumentSyncKind::FULL), + 2 => Some(TextDocumentSyncKind::INCREMENTAL), + _ => None, + }; + if sync_kind == Some(TextDocumentSyncKind::FULL) { return Some(TextDocumentSyncKind::FULL); } - if data.sync_kind == TextDocumentSyncKind::INCREMENTAL { + if sync_kind == Some(TextDocumentSyncKind::INCREMENTAL) { has_incremental = true; } } From fa2e24fcc7f4aa26d0e221a7e0116643496d182f Mon Sep 17 00:00:00 2001 From: Smit Barmase Date: Mon, 25 Aug 2025 19:19:49 +0530 Subject: [PATCH 10/10] fix include text --- crates/lsp/src/capabilities.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/lsp/src/capabilities.rs b/crates/lsp/src/capabilities.rs index c4e30b71a6..76ce4d739b 100644 --- a/crates/lsp/src/capabilities.rs +++ b/crates/lsp/src/capabilities.rs @@ -80,7 +80,7 @@ impl EffectiveCapability for cap::DidSaveTextDocument { Some( id_to_save_options_map .values() - .any(|data| data.include_text), + .any(|data| data.include_text.unwrap_or(false)), ) } })