effective caps

This commit is contained in:
Smit Barmase 2025-08-25 13:31:45 +05:30
parent 0617098175
commit 3410652c71
No known key found for this signature in database
2 changed files with 77 additions and 59 deletions

View file

@ -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 {

View file

@ -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,