Improve performance of go-to-diagnostic when many diagnostics are present (#23166)

Instead of eagerly calling `to_offset` on the anchor ranges for each
diagnostic in the direction of the search, work lazily in terms of
anchors and convert to offsets at the very end.

Release Notes:

- N/A
This commit is contained in:
Cole Miller 2025-01-15 10:02:35 -05:00 committed by GitHub
parent 9d3a0594f9
commit 74620e611e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -99,8 +99,8 @@ use itertools::Itertools;
use language::{
language_settings::{self, all_language_settings, language_settings, InlayHintSettings},
markdown, point_from_lsp, AutoindentMode, BracketPair, Buffer, Capability, CharKind, CodeLabel,
CursorShape, Diagnostic, DiagnosticEntry, Documentation, IndentKind, IndentSize, Language,
OffsetRangeExt, Point, Selection, SelectionGoal, TransactionId,
CursorShape, Diagnostic, Documentation, IndentKind, IndentSize, Language, OffsetRangeExt,
Point, Selection, SelectionGoal, TransactionId,
};
use language::{point_to_lsp, BufferRow, CharClassifier, Runnable, RunnableRange};
use linked_editing_ranges::refresh_linked_ranges;
@ -9272,41 +9272,35 @@ impl Editor {
let snapshot = self.snapshot(cx);
loop {
let diagnostics = if direction == Direction::Prev {
buffer
.diagnostics_in_range(0..search_start, true)
.map(|DiagnosticEntry { diagnostic, range }| DiagnosticEntry {
diagnostic,
range: range.to_offset(&buffer),
})
.collect::<Vec<_>>()
buffer.diagnostics_in_range(0..search_start, true)
} else {
buffer
.diagnostics_in_range(search_start..buffer.len(), false)
.map(|DiagnosticEntry { diagnostic, range }| DiagnosticEntry {
diagnostic,
range: range.to_offset(&buffer),
})
.collect::<Vec<_>>()
buffer.diagnostics_in_range(search_start..buffer.len(), false)
}
.into_iter()
.filter(|diagnostic| !snapshot.intersects_fold(diagnostic.range.start));
let search_start_anchor = buffer.anchor_after(search_start);
let group = diagnostics
// relies on diagnostics_in_range to return diagnostics with the same starting range to
// be sorted in a stable way
// skip until we are at current active diagnostic, if it exists
.skip_while(|entry| {
(match direction {
Direction::Prev => entry.range.start >= search_start,
Direction::Next => entry.range.start <= search_start,
}) && self
.active_diagnostics
.as_ref()
.is_some_and(|a| a.group_id != entry.diagnostic.group_id)
let is_in_range = match direction {
Direction::Prev => {
entry.range.start.cmp(&search_start_anchor, &buffer).is_ge()
}
Direction::Next => {
entry.range.start.cmp(&search_start_anchor, &buffer).is_le()
}
};
is_in_range
&& self
.active_diagnostics
.as_ref()
.is_some_and(|a| a.group_id != entry.diagnostic.group_id)
})
.find_map(|entry| {
if entry.diagnostic.is_primary
&& entry.diagnostic.severity <= DiagnosticSeverity::WARNING
&& !entry.range.is_empty()
&& !(entry.range.start == entry.range.end)
// if we match with the active diagnostic, skip it
&& Some(entry.diagnostic.group_id)
!= self.active_diagnostics.as_ref().map(|d| d.group_id)
@ -9319,6 +9313,7 @@ impl Editor {
if let Some((primary_range, group_id)) = group {
self.activate_diagnostics(group_id, cx);
let primary_range = primary_range.to_offset(&buffer);
if self.active_diagnostics.is_some() {
self.change_selections(Some(Autoscroll::fit()), cx, |s| {
s.select(vec![Selection {