on master help sign in
Add config to mark diagnostic sources as persistent
commit8653e1b02f6193330f8b0f2afff0a2ff103bd6a4 authorPascal Kuthe <pascal.kuthe@semimod.de> dateSun 26 Mar 2023, 18:10 (4 years ago) parentc874a89
5 files changed+90−40
book/src/languages.md+1 helix-core/src/syntax.rs+2 helix-term/src/application.rs+68−37 helix-view/src/document.rs+18−3 languages.toml+1
book/src/languages.md+1
@@ -69,6 +69,7 @@ These configuration keys are available:
6969| `soft-wrap` | [editor.softwrap](./configuration.md#editorsoft-wrap-section)
7070| `text-width` | Maximum line length. Used for the `:reflow` command and soft-wrapping if `soft-wrap.wrap-at-text-width` is set, defaults to `editor.text-width` |
7171| `workspace-lsp-roots` | Directories relative to the workspace root that are treated as LSP roots. Should only be set in `.helix/config.toml`. Overwrites the setting of the same name in `config.toml` if set. |
72+| `persistent-diagnostic-sources` | An array of LSP diagnostic sources assumed unchanged when the language server resends the same set of diagnostics. Helix can track the position for these diagnostics internally instead. Useful for diagnostics that are recomputed on save.
7273
7374### File-type detection and the `file-types` key
7475
helix-core/src/syntax.rs+2
@@ -155,6 +155,8 @@ pub struct LanguageConfiguration {
155155 /// Hardcoded LSP root directories relative to the workspace root, like `examples` or `tools/fuzz`.
156156 /// Falling back to the current working directory if none are configured.
157157 pub workspace_lsp_roots: Option<Vec<PathBuf>>,
158+ #[serde(default)]
159+ pub persistent_diagnostic_sources: Vec<String>,
158160}
159161
160162#[derive(Debug, PartialEq, Eq, Hash)]
helix-term/src/application.rs+68−37
@@ -717,7 +717,7 @@ impl Application {
717717 ));
718718 }
719719 }
720− Notification::PublishDiagnostics(params) => {
720+ Notification::PublishDiagnostics(mut params) => {
721721 let path = match params.uri.to_file_path() {
722722 Ok(path) => path,
723723 Err(_) => {
@@ -731,31 +731,69 @@ impl Application {
731731 return;
732732 }
733733 let offset_encoding = language_server.offset_encoding();
734− let doc = self.editor.document_by_path_mut(&path).filter(|doc| {
735− if let Some(version) = params.version {
736− if version != doc.version() {
737− log::info!("Version ({version}) is out of date for {path:?} (expected ({}), dropping PublishDiagnostic notification", doc.version());
738− return false;
734+ // have to inline the function because of borrow checking...
735+ let doc = self.editor.documents.values_mut()
736+ .find(|doc| doc.path().map(|p| p == &path).unwrap_or(false))
737+ .filter(|doc| {
738+ if let Some(version) = params.version {
739+ if version != doc.version() {
740+ log::info!("Version ({version}) is out of date for {path:?} (expected ({}), dropping PublishDiagnostic notification", doc.version());
741+ return false;
742+ }
739743 }
740− }
741−
742− true
743− });
744+ true
745+ });
744746
745747 if let Some(doc) = doc {
746− let lang_conf = doc.language_config();
747− let text = doc.text();
748+ let lang_conf = doc.language.clone();
749+ let text = doc.text().clone();
750+
751+ let mut unchaged_diag_sources_ = Vec::new();
752+ if let Some(lang_conf) = &lang_conf {
753+ if let Some(old_diagnostics) =
754+ self.editor.diagnostics.get(&params.uri)
755+ {
756+ if !lang_conf.persistent_diagnostic_sources.is_empty() {
757+ // Sort diagnostics first by severity and then by line numbers.
758+ // Note: The `lsp::DiagnosticSeverity` enum is already defined in decreasing order
759+ params
760+ .diagnostics
761+ .sort_unstable_by_key(|d| (d.severity, d.range.start));
762+ }
763+ for source in &lang_conf.persistent_diagnostic_sources {
764+ let new_diagnostics = params
765+ .diagnostics
766+ .iter()
767+ .filter(|d| d.source.as_ref() == Some(source));
768+ let old_diagnostics = old_diagnostics
769+ .iter()
770+ .filter(|(d, d_server)| {
771+ *d_server == server_id
772+ && d.source.as_ref() == Some(source)
773+ })
774+ .map(|(d, _)| d);
775+ if new_diagnostics.eq(old_diagnostics) {
776+ unchaged_diag_sources_.push(source.clone())
777+ }
778+ }
779+ }
780+ }
748781
749− let diagnostics = params
750− .diagnostics
751− .iter()
752− .filter_map(|diagnostic| {
782+ let unchaged_diag_sources = &unchaged_diag_sources_;
783+ let diagnostics =
784+ params.diagnostics.iter().filter_map(move |diagnostic| {
753785 use helix_core::diagnostic::{Diagnostic, Range, Severity::*};
754786 use lsp::DiagnosticSeverity;
755787
788+ if diagnostic.source.as_ref().map_or(false, |source| {
789+ unchaged_diag_sources.contains(source)
790+ }) {
791+ return None;
792+ }
793+
756794 // TODO: convert inside server
757795 let start = if let Some(start) = lsp_pos_to_pos(
758− text,
796+ &text,
759797 diagnostic.range.start,
760798 offset_encoding,
761799 ) {
@@ -766,7 +804,7 @@ impl Application {
766804 };
767805
768806 let end = if let Some(end) =
769− lsp_pos_to_pos(text, diagnostic.range.end, offset_encoding)
807+ lsp_pos_to_pos(&text, diagnostic.range.end, offset_encoding)
770808 {
771809 end
772810 } else {
@@ -786,7 +824,7 @@ impl Application {
786824 ),
787825 });
788826
789− if let Some(lang_conf) = lang_conf {
827+ if let Some(lang_conf) = &lang_conf {
790828 if let Some(severity) = severity {
791829 if severity < lang_conf.diagnostic_severity {
792830 return None;
@@ -836,38 +874,31 @@ impl Application {
836874 data: diagnostic.data.clone(),
837875 language_server_id: server_id,
838876 })
839− })
840− .collect();
877+ });
841878
842− doc.replace_diagnostics(diagnostics, server_id);
879+ doc.replace_diagnostics(diagnostics, unchaged_diag_sources, server_id);
843880 }
844881
845− let mut diagnostics = params
846− .diagnostics
847− .into_iter()
848− .map(|d| (d, server_id))
849− .collect();
882+ let diagnostics = params.diagnostics.into_iter().map(|d| (d, server_id));
850883
851884 // Insert the original lsp::Diagnostics here because we may have no open document
852885 // for diagnosic message and so we can't calculate the exact position.
853886 // When using them later in the diagnostics picker, we calculate them on-demand.
854− match self.editor.diagnostics.entry(params.uri) {
887+ let diagnostics = match self.editor.diagnostics.entry(params.uri) {
855888 Entry::Occupied(o) => {
856889 let current_diagnostics = o.into_mut();
857890 // there may entries of other language servers, which is why we can't overwrite the whole entry
858891 current_diagnostics.retain(|(_, lsp_id)| *lsp_id != server_id);
859− current_diagnostics.append(&mut diagnostics);
860− // Sort diagnostics first by severity and then by line numbers.
861− // Note: The `lsp::DiagnosticSeverity` enum is already defined in decreasing order
892+ current_diagnostics.extend(diagnostics);
862893 current_diagnostics
863− .sort_unstable_by_key(|(d, _)| (d.severity, d.range.start));
864− }
865− Entry::Vacant(v) => {
866− diagnostics
867− .sort_unstable_by_key(|(d, _)| (d.severity, d.range.start));
868− v.insert(diagnostics);
894+ // Sort diagnostics first by severity and then by line numbers.
869895 }
896+ Entry::Vacant(v) => v.insert(diagnostics.collect()),
870897 };
898+
899+ // Sort diagnostics first by severity and then by line numbers.
900+ // Note: The `lsp::DiagnosticSeverity` enum is already defined in decreasing order
901+ diagnostics.sort_unstable_by_key(|(d, _)| (d.severity, d.range.start));
871902 }
872903 Notification::ShowMessage(params) => {
873904 log::warn!("unhandled window/showMessage: {:?}", params);
helix-view/src/document.rs+18−3
@@ -1706,11 +1706,26 @@ impl Document {
17061706
17071707 pub fn replace_diagnostics(
17081708 &mut self,
1709− mut diagnostics: Vec<Diagnostic>,
1709+ diagnostics: impl IntoIterator<Item = Diagnostic>,
1710+ unchanged_sources: &[String],
17101711 language_server_id: usize,
17111712 ) {
1712− self.clear_diagnostics(language_server_id);
1713− self.diagnostics.append(&mut diagnostics);
1713+ if unchanged_sources.is_empty() {
1714+ self.clear_diagnostics(language_server_id);
1715+ } else {
1716+ self.diagnostics.retain(|d| {
1717+ if d.language_server_id != language_server_id {
1718+ return true;
1719+ }
1720+
1721+ if let Some(source) = &d.source {
1722+ unchanged_sources.contains(source)
1723+ } else {
1724+ false
1725+ }
1726+ });
1727+ }
1728+ self.diagnostics.extend(diagnostics);
17141729 self.diagnostics
17151730 .sort_unstable_by_key(|diagnostic| diagnostic.range);
17161731 }
languages.toml+1
@@ -191,6 +191,7 @@ auto-format = true
191191comment-token = "//"
192192language-servers = [ "rust-analyzer" ]
193193indent = { tab-width = 4, unit = " " }
194+persistent-diagnostic-sources = ["rustc", "clippy"]
194195
195196[language.auto-pairs]
196197'(' = ')'