@@ -123,8 +123,15 @@ impl EditorView {
123123 Self::highlight_cursorcolumn(doc, view, surface, theme);
124124 }
125125
126− let highlights = Self::doc_syntax_highlights(doc, view.offset, inner.height, theme);
127− let highlights = syntax::merge(highlights, Self::doc_diagnostics_highlights(doc, theme));
126+ let mut highlights = Self::doc_syntax_highlights(doc, view.offset, inner.height, theme);
127+ for diagnostic in Self::doc_diagnostics_highlights(doc, theme) {
128+ // Most of the `diagnostic` Vecs are empty most of the time. Skipping
129+ // a merge for any empty Vec saves a significant amount of work.
130+ if diagnostic.is_empty() {
131+ continue;
132+ }
133+ highlights = Box::new(syntax::merge(highlights, diagnostic));
134+ }
128135 let highlights: Box<dyn Iterator<Item = HighlightEvent>> = if is_focused {
129136 Box::new(syntax::merge(
130137 highlights,
@@ -268,7 +275,7 @@ impl EditorView {
268275 pub fn doc_diagnostics_highlights(
269276 doc: &Document,
270277 theme: &Theme,
271− ) -> Vec<(usize, std::ops::Range<usize>)> {
278+ ) -> [Vec<(usize, std::ops::Range<usize>)>; 5] {
272279 use helix_core::diagnostic::Severity;
273280 let get_scope_of = |scope| {
274281 theme
@@ -289,22 +296,42 @@ impl EditorView {
289296 let error = get_scope_of("diagnostic.error");
290297 let r#default = get_scope_of("diagnostic"); // this is a bit redundant but should be fine
291298
292− doc.diagnostics()
293− .iter()
294− .map(|diagnostic| {
295− let diagnostic_scope = match diagnostic.severity {
296− Some(Severity::Info) => info,
297− Some(Severity::Hint) => hint,
298− Some(Severity::Warning) => warning,
299− Some(Severity::Error) => error,
300− _ => r#default,
301− };
302− (
303− diagnostic_scope,
304− diagnostic.range.start..diagnostic.range.end,
305− )
306− })
307− .collect()
299+ let mut default_vec: Vec<(usize, std::ops::Range<usize>)> = Vec::new();
300+ let mut info_vec = Vec::new();
301+ let mut hint_vec = Vec::new();
302+ let mut warning_vec = Vec::new();
303+ let mut error_vec = Vec::new();
304+
305+ let diagnostics = doc.diagnostics();
306+
307+ // Diagnostics must be sorted by range. Otherwise, the merge strategy
308+ // below would not be accurate.
309+ debug_assert!(diagnostics
310+ .windows(2)
311+ .all(|window| window[0].range.start <= window[1].range.start
312+ && window[0].range.end <= window[1].range.end));
313+
314+ for diagnostic in diagnostics {
315+ // Separate diagnostics into different Vecs by severity.
316+ let (vec, scope) = match diagnostic.severity {
317+ Some(Severity::Info) => (&mut info_vec, info),
318+ Some(Severity::Hint) => (&mut hint_vec, hint),
319+ Some(Severity::Warning) => (&mut warning_vec, warning),
320+ Some(Severity::Error) => (&mut error_vec, error),
321+ _ => (&mut default_vec, r#default),
322+ };
323+
324+ // If any diagnostic overlaps ranges with the prior diagnostic,
325+ // merge the two together. Otherwise push a new span.
326+ match vec.last_mut() {
327+ Some((_, range)) if diagnostic.range.start <= range.end => {
328+ range.end = diagnostic.range.end.max(range.end)
329+ }
330+ _ => vec.push((scope, diagnostic.range.start..diagnostic.range.end)),
331+ }
332+ }
333+
334+ [default_vec, info_vec, hint_vec, warning_vec, error_vec]
308335 }
309336
310337 /// Get highlight spans for selections in a document view.