editor: Support walking through overlapping diagnostics (#11139)
While looking into how to implement #4901, noticed that the current `Goto next/previous diagnostic` behaved a bit weirdly. That is, when there are multiple errors that have overlapping ranges, only the first one can be chosen to be active by the `go_to_diagnostic_impl`. ### Previous behavior: https://github.com/zed-industries/zed/assets/71292737/95897675-f5ee-40e5-869f-0a40066eb8e3 Doesn't go through all the diagnostics, and going backwards and forwards doesn't show the same diagnostic always. ### New behavior: https://github.com/zed-industries/zed/assets/71292737/81f7945a-7ad8-4a34-b286-cc2799b10500 Should always go through the diagnostics in a consistent manner. Release Notes: * Improved the behavioral consistency of "Go to Next/Previous Diagnostic"
This commit is contained in:
parent
c71cfd5da4
commit
b8a83443ac
2 changed files with 45 additions and 15 deletions
|
@ -1447,6 +1447,7 @@ impl CodeActionsMenu {
|
||||||
struct ActiveDiagnosticGroup {
|
struct ActiveDiagnosticGroup {
|
||||||
primary_range: Range<Anchor>,
|
primary_range: Range<Anchor>,
|
||||||
primary_message: String,
|
primary_message: String,
|
||||||
|
group_id: usize,
|
||||||
blocks: HashMap<BlockId, Diagnostic>,
|
blocks: HashMap<BlockId, Diagnostic>,
|
||||||
is_valid: bool,
|
is_valid: bool,
|
||||||
}
|
}
|
||||||
|
@ -8015,7 +8016,7 @@ impl Editor {
|
||||||
});
|
});
|
||||||
let mut search_start = if let Some(active_primary_range) = active_primary_range.as_ref() {
|
let mut search_start = if let Some(active_primary_range) = active_primary_range.as_ref() {
|
||||||
if active_primary_range.contains(&selection.head()) {
|
if active_primary_range.contains(&selection.head()) {
|
||||||
*active_primary_range.end()
|
*active_primary_range.start()
|
||||||
} else {
|
} else {
|
||||||
selection.head()
|
selection.head()
|
||||||
}
|
}
|
||||||
|
@ -8024,24 +8025,38 @@ impl Editor {
|
||||||
};
|
};
|
||||||
let snapshot = self.snapshot(cx);
|
let snapshot = self.snapshot(cx);
|
||||||
loop {
|
loop {
|
||||||
let mut diagnostics = if direction == Direction::Prev {
|
let diagnostics = if direction == Direction::Prev {
|
||||||
buffer.diagnostics_in_range::<_, usize>(0..search_start, true)
|
buffer.diagnostics_in_range::<_, usize>(0..search_start, true)
|
||||||
} else {
|
} else {
|
||||||
buffer.diagnostics_in_range::<_, usize>(search_start..buffer.len(), false)
|
buffer.diagnostics_in_range::<_, usize>(search_start..buffer.len(), false)
|
||||||
}
|
}
|
||||||
.filter(|diagnostic| !snapshot.intersects_fold(diagnostic.range.start));
|
.filter(|diagnostic| !snapshot.intersects_fold(diagnostic.range.start));
|
||||||
let group = diagnostics.find_map(|entry| {
|
let group = diagnostics
|
||||||
if entry.diagnostic.is_primary
|
// relies on diagnostics_in_range to return diagnostics with the same starting range to
|
||||||
&& entry.diagnostic.severity <= DiagnosticSeverity::WARNING
|
// be sorted in a stable way
|
||||||
&& !entry.range.is_empty()
|
// skip until we are at current active diagnostic, if it exists
|
||||||
&& Some(entry.range.end) != active_primary_range.as_ref().map(|r| *r.end())
|
.skip_while(|entry| {
|
||||||
&& !entry.range.contains(&search_start)
|
(match direction {
|
||||||
{
|
Direction::Prev => entry.range.start >= search_start,
|
||||||
Some((entry.range, entry.diagnostic.group_id))
|
Direction::Next => entry.range.start <= search_start,
|
||||||
} else {
|
}) && self
|
||||||
None
|
.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()
|
||||||
|
// if we match with the active diagnostic, skip it
|
||||||
|
&& Some(entry.diagnostic.group_id)
|
||||||
|
!= self.active_diagnostics.as_ref().map(|d| d.group_id)
|
||||||
|
{
|
||||||
|
Some((entry.range, entry.diagnostic.group_id))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if let Some((primary_range, group_id)) = group {
|
if let Some((primary_range, group_id)) = group {
|
||||||
if self.activate_diagnostics(group_id, cx) {
|
if self.activate_diagnostics(group_id, cx) {
|
||||||
|
@ -9042,6 +9057,7 @@ impl Editor {
|
||||||
Some(ActiveDiagnosticGroup {
|
Some(ActiveDiagnosticGroup {
|
||||||
primary_range,
|
primary_range,
|
||||||
primary_message,
|
primary_message,
|
||||||
|
group_id,
|
||||||
blocks,
|
blocks,
|
||||||
is_valid: true,
|
is_valid: true,
|
||||||
})
|
})
|
||||||
|
|
|
@ -3159,7 +3159,21 @@ impl BufferSnapshot {
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.flat_map(|(ix, iter)| Some((ix, iter.peek()?)))
|
.flat_map(|(ix, iter)| Some((ix, iter.peek()?)))
|
||||||
.min_by(|(_, a), (_, b)| a.range.start.cmp(&b.range.start))?;
|
.min_by(|(_, a), (_, b)| {
|
||||||
|
let cmp = a
|
||||||
|
.range
|
||||||
|
.start
|
||||||
|
.cmp(&b.range.start)
|
||||||
|
// when range is equal, sort by diagnostic severity
|
||||||
|
.then(a.diagnostic.severity.cmp(&b.diagnostic.severity))
|
||||||
|
// and stabilize order with group_id
|
||||||
|
.then(a.diagnostic.group_id.cmp(&b.diagnostic.group_id));
|
||||||
|
if reversed {
|
||||||
|
cmp.reverse()
|
||||||
|
} else {
|
||||||
|
cmp
|
||||||
|
}
|
||||||
|
})?;
|
||||||
iterators[next_ix].next()
|
iterators[next_ix].next()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue