diff --git a/helix-core/src/syntax.rs b/helix-core/src/syntax.rs index 8c7fc4e15c613..1421d0620aace 100644 --- a/helix-core/src/syntax.rs +++ b/helix-core/src/syntax.rs @@ -251,6 +251,9 @@ pub enum LanguageServerFeature { Diagnostics, RenameSymbol, InlayHints, + // DocumentSynchronization + Save, + WillSave, } impl Display for LanguageServerFeature { @@ -274,6 +277,8 @@ impl Display for LanguageServerFeature { Diagnostics => "diagnostics", RenameSymbol => "rename-symbol", InlayHints => "inlay-hints", + Save => "save", + WillSave => "will-save", }; write!(f, "{feature}",) } diff --git a/helix-lsp/src/client.rs b/helix-lsp/src/client.rs index e6e1f8a033010..f1bc850c43da7 100644 --- a/helix-lsp/src/client.rs +++ b/helix-lsp/src/client.rs @@ -8,8 +8,8 @@ use helix_core::{find_workspace, path, syntax::LanguageServerFeature, ChangeSet, use helix_loader::{self, VERSION_AND_GIT_HASH}; use lsp::{ notification::DidChangeWorkspaceFolders, CodeActionCapabilityResolveSupport, - DidChangeWorkspaceFoldersParams, OneOf, PositionEncodingKind, WorkspaceFolder, - WorkspaceFoldersChangeEvent, + DidChangeWorkspaceFoldersParams, OneOf, PositionEncodingKind, TextDocumentSaveReason, TextEdit, + WorkspaceFolder, WorkspaceFoldersChangeEvent, }; use lsp_types as lsp; use parking_lot::Mutex; @@ -288,6 +288,27 @@ impl Client { capabilities.document_formatting_provider, Some(OneOf::Left(true) | OneOf::Right(_)) ), + LanguageServerFeature::Save => matches!( + capabilities.text_document_sync, + Some(TextDocumentSyncCapability::Options( + TextDocumentSyncOptions { + save: Some( + TextDocumentSyncSaveOptions::Supported(true) + | TextDocumentSyncSaveOptions::SaveOptions(SaveOptions { .. }) + ), + .. + } + )) + ), + LanguageServerFeature::WillSave => matches!( + capabilities.text_document_sync, + Some(TextDocumentSyncCapability::Options( + TextDocumentSyncOptions { + will_save: Some(true), + .. + } + )) + ), LanguageServerFeature::GotoDeclaration => matches!( capabilities.declaration_provider, Some( @@ -776,6 +797,7 @@ impl Client { // ------------------------------------------------------------------------------------------- // Text document + // https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_synchronization // ------------------------------------------------------------------------------------------- pub fn text_document_did_open( @@ -960,7 +982,34 @@ impl Client { }) } - // will_save / will_save_wait_until + pub fn test_document_will_save( + &self, + text_document: lsp::TextDocumentIdentifier, + ) -> Option>> { + Some(self.notify::( + lsp::WillSaveTextDocumentParams { + text_document, + reason: TextDocumentSaveReason::MANUAL, + }, + )) + } + + pub fn text_document_will_save_wait_until( + &self, + text_document: lsp::TextDocumentIdentifier, + ) -> Option>>> { + let request = + self.call::(lsp::WillSaveTextDocumentParams { + text_document, + reason: TextDocumentSaveReason::MANUAL, + }); + + Some(async move { + let json = request.await?; + let response: Option> = serde_json::from_value(json)?; + Ok(response.unwrap_or_default()) + }) + } pub fn text_document_did_save( &self, @@ -993,6 +1042,11 @@ impl Client { )) } + // ------------------------------------------------------------------------------------------- + // Language features + // https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#languageFeatures + // ------------------------------------------------------------------------------------------- + pub fn completion( &self, text_document: lsp::TextDocumentIdentifier, diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index bb61eaa6aae68..fc5f2857d0c67 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -875,6 +875,25 @@ impl Document { // We encode the file according to the `Document`'s encoding. let future = async move { use tokio::{fs, fs::File}; + + if let Some(identifier) = &identifier { + for language_server in language_servers.values() { + if language_server.is_initialized() { + let Some(notification) = + language_server.test_document_will_save(identifier.clone()) + else { + continue + }; + + if let Err(err) = helix_lsp::block_on(notification) { + log::error!( + "failed to send textDocument/willSave notification: {err:?}" + ); + } + } + } + }; + if let Some(parent) = path.parent() { // TODO: display a prompt asking the user if the directories should be created if !parent.exists() { @@ -907,15 +926,20 @@ impl Document { text: text.clone(), }; - for (_, language_server) in language_servers { - if !language_server.is_initialized() { - return Ok(event); - } - if let Some(identifier) = &identifier { - if let Some(notification) = - language_server.text_document_did_save(identifier.clone(), &text) - { - notification.await?; + if let Some(identifier) = &identifier { + for language_server in language_servers.values() { + if language_server.is_initialized() { + let Some(notification) = + language_server.text_document_did_save(identifier.clone(), &text) + else { + continue + }; + + if let Err(err) = helix_lsp::block_on(notification) { + log::error!( + "failed to send textDocument/didSave notification: {err:?}" + ); + } } } }