Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'helix-view/src/handlers/lsp.rs')
| -rw-r--r-- | helix-view/src/handlers/lsp.rs | 92 |
1 files changed, 91 insertions, 1 deletions
diff --git a/helix-view/src/handlers/lsp.rs b/helix-view/src/handlers/lsp.rs index 39e7dba9..3f0cfbfc 100644 --- a/helix-view/src/handlers/lsp.rs +++ b/helix-view/src/handlers/lsp.rs @@ -1,10 +1,12 @@ +use std::collections::btree_map::Entry; use std::fmt::Display; use crate::editor::Action; +use crate::events::DiagnosticsDidChange; use crate::Editor; use helix_core::Uri; use helix_lsp::util::generate_transaction_from_edits; -use helix_lsp::{lsp, OffsetEncoding}; +use helix_lsp::{lsp, LanguageServerId, OffsetEncoding}; #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum SignatureHelpInvoked { @@ -271,4 +273,92 @@ impl Editor { } Ok(()) } + + pub fn handle_lsp_diagnostics( + &mut self, + server_id: LanguageServerId, + uri: Uri, + version: Option<i32>, + mut diagnostics: Vec<lsp::Diagnostic>, + ) { + let doc = self.documents.values_mut() + .find(|doc| doc.uri().is_some_and(|u| u == uri)) + .filter(|doc| { + if let Some(version) = version { + if version != doc.version() { + log::info!("Version ({version}) is out of date for {uri:?} (expected ({}), dropping PublishDiagnostic notification", doc.version()); + return false; + } + } + true + }); + + let mut unchanged_diag_sources = Vec::new(); + if let Some((lang_conf, old_diagnostics)) = doc + .as_ref() + .and_then(|doc| Some((doc.language_config()?, self.diagnostics.get(&uri)?))) + { + if !lang_conf.persistent_diagnostic_sources.is_empty() { + // Sort diagnostics first by severity and then by line numbers. + // Note: The `lsp::DiagnosticSeverity` enum is already defined in decreasing order + diagnostics.sort_by_key(|d| (d.severity, d.range.start)); + } + for source in &lang_conf.persistent_diagnostic_sources { + let new_diagnostics = diagnostics + .iter() + .filter(|d| d.source.as_ref() == Some(source)); + let old_diagnostics = old_diagnostics + .iter() + .filter(|(d, d_server)| { + *d_server == server_id && d.source.as_ref() == Some(source) + }) + .map(|(d, _)| d); + if new_diagnostics.eq(old_diagnostics) { + unchanged_diag_sources.push(source.clone()) + } + } + } + + let diagnostics = diagnostics.into_iter().map(|d| (d, server_id)); + + // Insert the original lsp::Diagnostics here because we may have no open document + // for diagnostic message and so we can't calculate the exact position. + // When using them later in the diagnostics picker, we calculate them on-demand. + let diagnostics = match self.diagnostics.entry(uri) { + Entry::Occupied(o) => { + let current_diagnostics = o.into_mut(); + // there may entries of other language servers, which is why we can't overwrite the whole entry + current_diagnostics.retain(|(_, lsp_id)| *lsp_id != server_id); + current_diagnostics.extend(diagnostics); + current_diagnostics + // Sort diagnostics first by severity and then by line numbers. + } + Entry::Vacant(v) => v.insert(diagnostics.collect()), + }; + + // Sort diagnostics first by severity and then by line numbers. + // Note: The `lsp::DiagnosticSeverity` enum is already defined in decreasing order + diagnostics.sort_by_key(|(d, server_id)| (d.severity, d.range.start, *server_id)); + + if let Some(doc) = doc { + let diagnostic_of_language_server_and_not_in_unchanged_sources = + |diagnostic: &lsp::Diagnostic, ls_id| { + ls_id == server_id + && diagnostic + .source + .as_ref() + .map_or(true, |source| !unchanged_diag_sources.contains(source)) + }; + let diagnostics = Self::doc_diagnostics_with_filter( + &self.language_servers, + &self.diagnostics, + doc, + diagnostic_of_language_server_and_not_in_unchanged_sources, + ); + doc.replace_diagnostics(diagnostics, &unchanged_diag_sources, Some(server_id)); + + let doc = doc.id(); + helix_event::dispatch(DiagnosticsDidChange { editor: self, doc }); + } + } } |