project: Take 2 on Handle textDocument/didSave and textDocument/didChange (un)registration and usage correctly (#36485)

Relands https://github.com/zed-industries/zed/pull/36441 with a
deserialization fix.

Previously, deserializing `"includeText"` into
`lsp::TextDocumentSyncSaveOptions` resulted in a `Supported(false)` type
instead of `SaveOptions(SaveOptions { include_text: Option<bool> })`.

```rs
impl From<bool> for TextDocumentSyncSaveOptions {
    fn from(from: bool) -> Self {
        Self::Supported(from)
    }
}
```

Looks like, while dynamic registartion we only get `SaveOptions` type
and never `Supported` type.
(https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocumentSaveRegistrationOptions)

Release Notes:

- N/A

---------

Co-authored-by: Lukas Wirth <lukas@zed.dev>
This commit is contained in:
Smit Barmase 2025-08-19 19:04:48 +05:30 committed by GitHub
parent 8f567383e4
commit e3b593efbd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -74,8 +74,8 @@ use lsp::{
FileOperationPatternKind, FileOperationRegistrationOptions, FileRename, FileSystemWatcher, FileOperationPatternKind, FileOperationRegistrationOptions, FileRename, FileSystemWatcher,
LanguageServer, LanguageServerBinary, LanguageServerBinaryOptions, LanguageServerId, LanguageServer, LanguageServerBinary, LanguageServerBinaryOptions, LanguageServerId,
LanguageServerName, LanguageServerSelector, LspRequestFuture, MessageActionItem, MessageType, LanguageServerName, LanguageServerSelector, LspRequestFuture, MessageActionItem, MessageType,
OneOf, RenameFilesParams, SymbolKind, TextEdit, WillRenameFiles, WorkDoneProgressCancelParams, OneOf, RenameFilesParams, SymbolKind, TextDocumentSyncSaveOptions, TextEdit, WillRenameFiles,
WorkspaceFolder, notification::DidRenameFiles, WorkDoneProgressCancelParams, WorkspaceFolder, notification::DidRenameFiles,
}; };
use node_runtime::read_package_installed_version; use node_runtime::read_package_installed_version;
use parking_lot::Mutex; use parking_lot::Mutex;
@ -11800,8 +11800,40 @@ impl LspStore {
.transpose()? .transpose()?
{ {
server.update_capabilities(|capabilities| { 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 = capabilities.text_document_sync =
Some(lsp::TextDocumentSyncCapability::Kind(sync_kind)); Some(lsp::TextDocumentSyncCapability::Options(sync_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::<Option<bool>>)
.transpose();
match transpose {
Ok(value) => Ok(value.flatten()),
Err(e) => Err(e),
}
})
.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));
}); });
notify_server_capabilities_updated(&server, cx); notify_server_capabilities_updated(&server, cx);
} }
@ -11953,7 +11985,19 @@ impl LspStore {
} }
"textDocument/didChange" => { "textDocument/didChange" => {
server.update_capabilities(|capabilities| { server.update_capabilities(|capabilities| {
capabilities.text_document_sync = None; 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));
});
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));
}); });
notify_server_capabilities_updated(&server, cx); notify_server_capabilities_updated(&server, cx);
} }
@ -11981,6 +12025,20 @@ impl LspStore {
Ok(()) 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(),
}
}
} }
// Registration with empty capabilities should be ignored. // Registration with empty capabilities should be ignored.
@ -13083,24 +13141,18 @@ async fn populate_labels_for_symbols(
fn include_text(server: &lsp::LanguageServer) -> Option<bool> { fn include_text(server: &lsp::LanguageServer) -> Option<bool> {
match server.capabilities().text_document_sync.as_ref()? { match server.capabilities().text_document_sync.as_ref()? {
lsp::TextDocumentSyncCapability::Kind(kind) => match *kind { lsp::TextDocumentSyncCapability::Options(opts) => match opts.save.as_ref()? {
lsp::TextDocumentSyncKind::NONE => None, // Server wants didSave but didn't specify includeText.
lsp::TextDocumentSyncKind::FULL => Some(true), lsp::TextDocumentSyncSaveOptions::Supported(true) => Some(false),
lsp::TextDocumentSyncKind::INCREMENTAL => Some(false), // Server doesn't want didSave at all.
_ => None, lsp::TextDocumentSyncSaveOptions::Supported(false) => None,
}, // Server provided SaveOptions.
lsp::TextDocumentSyncCapability::Options(options) => match options.save.as_ref()? {
lsp::TextDocumentSyncSaveOptions::Supported(supported) => {
if *supported {
Some(true)
} else {
None
}
}
lsp::TextDocumentSyncSaveOptions::SaveOptions(save_options) => { lsp::TextDocumentSyncSaveOptions::SaveOptions(save_options) => {
Some(save_options.include_text.unwrap_or(false)) Some(save_options.include_text.unwrap_or(false))
} }
}, },
// We do not have any save info. Kind affects didChange only.
lsp::TextDocumentSyncCapability::Kind(_) => None,
} }
} }