Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'helix-view/src/editor.rs')
| -rw-r--r-- | helix-view/src/editor.rs | 149 |
1 files changed, 114 insertions, 35 deletions
diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 194285a8..d6efd25f 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -1,10 +1,12 @@ use crate::{ annotations::diagnostics::{DiagnosticFilter, InlineDiagnosticsConfig}, clipboard::ClipboardProvider, + diagnostic::DiagnosticProvider, document::{ - DocumentOpenError, DocumentSavedEventFuture, DocumentSavedEventResult, Mode, SavePoint, + self, DocumentOpenError, DocumentSavedEventFuture, DocumentSavedEventResult, Mode, + SavePoint, }, - events::{DocumentDidClose, DocumentDidOpen, DocumentFocusLost}, + events::{DiagnosticsDidChange, DocumentDidClose, DocumentDidOpen, DocumentFocusLost}, graphics::{CursorKind, Rect}, handlers::Handlers, info::Info, @@ -12,7 +14,7 @@ use crate::{ register::Registers, theme::{self, Theme}, tree::{self, Tree}, - Document, DocumentId, View, ViewId, + Diagnostic, Document, DocumentId, View, ViewId, }; use dap::StackFrame; use helix_event::dispatch; @@ -45,7 +47,6 @@ use anyhow::{anyhow, bail, Error}; pub use helix_core::diagnostic::Severity; use helix_core::{ auto_pairs::AutoPairs, - diagnostic::DiagnosticProvider, syntax::{ self, config::{AutoPairConfig, IndentationHeuristic, LanguageServerFeature, SoftWrap}, @@ -53,7 +54,6 @@ use helix_core::{ Change, LineEnding, Position, Range, Selection, Uri, NATIVE_LINE_ENDING, }; use helix_dap as dap; -use helix_lsp::lsp; use helix_stdx::path::canonicalize; use serde::{ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer}; @@ -1059,7 +1059,7 @@ pub struct Breakpoint { use futures_util::stream::{Flatten, Once}; -type Diagnostics = BTreeMap<Uri, Vec<(lsp::Diagnostic, DiagnosticProvider)>>; +type Diagnostics = BTreeMap<Uri, Vec<Diagnostic>>; pub struct Editor { /// Current editing mode. @@ -1756,7 +1756,7 @@ impl Editor { } pub fn new_file_from_stdin(&mut self, action: Action) -> Result<DocumentId, Error> { - let (stdin, encoding, has_bom) = crate::document::read_to_string(&mut stdin(), None)?; + let (stdin, encoding, has_bom) = document::read_to_string(&mut stdin(), None)?; let doc = Document::from( helix_core::Rope::default(), Some((encoding, has_bom)), @@ -2045,8 +2045,8 @@ impl Editor { language_servers: &'a helix_lsp::Registry, diagnostics: &'a Diagnostics, document: &Document, - ) -> impl Iterator<Item = helix_core::Diagnostic> + 'a { - Editor::doc_diagnostics_with_filter(language_servers, diagnostics, document, |_, _| true) + ) -> impl Iterator<Item = document::Diagnostic> + 'a { + Editor::doc_diagnostics_with_filter(language_servers, diagnostics, document, |_| true) } /// Returns all supported diagnostics for the document @@ -2055,37 +2055,38 @@ impl Editor { language_servers: &'a helix_lsp::Registry, diagnostics: &'a Diagnostics, document: &Document, - filter: impl Fn(&lsp::Diagnostic, &DiagnosticProvider) -> bool + 'a, - ) -> impl Iterator<Item = helix_core::Diagnostic> + 'a { + filter: impl Fn(&Diagnostic) -> bool + 'a, + ) -> impl Iterator<Item = document::Diagnostic> + 'a { let text = document.text().clone(); let language_config = document.language.clone(); diagnostics .get(&document.uri()) .map(|diags| { - diags.iter().filter_map(move |(diagnostic, provider)| { - let server_id = provider.language_server_id()?; - let ls = language_servers.get_by_id(server_id)?; - language_config - .as_ref() - .and_then(|c| { - c.language_servers.iter().find(|features| { - features.name == ls.name() - && features.has_feature(LanguageServerFeature::Diagnostics) - }) - }) - .and_then(|_| { - if filter(diagnostic, provider) { - Document::lsp_diagnostic_to_diagnostic( - &text, - language_config.as_deref(), - diagnostic, - provider.clone(), - ls.offset_encoding(), - ) - } else { - None - } - }) + diags.iter().filter_map(move |diagnostic| { + let language_server = diagnostic + .provider + .language_server_id() + .and_then(|id| language_servers.get_by_id(id)); + + if let Some((config, server)) = language_config.as_ref().zip(language_server) { + config.language_servers.iter().find(|features| { + features.name == server.name() + && features.has_feature(LanguageServerFeature::Diagnostics) + })?; + } + if diagnostic.severity.is_some_and(|severity| { + language_config + .as_ref() + .is_some_and(|config| severity < config.diagnostic_severity) + }) { + return None; + } + + if filter(diagnostic) { + diagnostic.to_document_diagnostic(&text) + } else { + None + } }) }) .into_iter() @@ -2266,6 +2267,84 @@ impl Editor { pub fn get_last_cwd(&mut self) -> Option<&Path> { self.last_cwd.as_deref() } + + pub fn handle_diagnostics( + &mut self, + provider: &DiagnosticProvider, + uri: Uri, + version: Option<i32>, + mut diagnostics: Vec<Diagnostic>, + ) { + use std::collections::btree_map::Entry; + + let doc = self.documents.values_mut().find(|doc| doc.uri() == uri); + + if let Some((version, doc)) = version.zip(doc.as_ref()) { + if version != doc.version() { + log::info!("Version ({version}) is out of date for {uri:?} (expected ({})), dropping diagnostics", doc.version()); + return; + } + } + + 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() { + diagnostics.sort(); + } + 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.provider == provider && d.source.as_ref() == Some(source)); + if new_diagnostics.eq(old_diagnostics) { + unchanged_diag_sources.push(source.clone()) + } + } + } + + // 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(|diagnostic| &diagnostic.provider != provider); + current_diagnostics.extend(diagnostics); + current_diagnostics + // Sort diagnostics first by severity and then by line numbers. + } + Entry::Vacant(v) => v.insert(diagnostics), + }; + + diagnostics.sort(); + + if let Some(doc) = doc { + let diagnostic_of_language_server_and_not_in_unchanged_sources = + |diagnostic: &crate::Diagnostic| { + &diagnostic.provider == provider + && 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(provider)); + + let doc = doc.id(); + helix_event::dispatch(DiagnosticsDidChange { editor: self, doc }); + } + } } fn try_restore_indent(doc: &mut Document, view: &mut View) { |