editor: Implement Go to next/prev Document Highlight (#35994)

Closes #21193
Closes #14703 

Having the ability to navigate directly to the next
symbolHighlight/reference lets you follow the data flow of a variable.
If you highlight the function itself (depending on the LSP), you can
also navigate to all returns.

Note that this is a different feature from navigating to the next match,
as that is not language-context aware. For example, if you have a var
named foo it would also navigate to an unrelated variable fooBar.

Here's how this patch works:

- The editor struct has a background_highlights.
- Collect all highlights with the keys [DocumentHighlightRead,
DocumentHighlightWrite]
- Depending on the direction, move the cursor to the next or previous
highlight relative to the current position.

Release Notes:

- Added `editor::GoToNextDocumentHighlight` and
`editor::GoToPreviousDocumentHighlight` to navigate to the next LSP
document highlight. Useful for navigating to the next usage of a certain
symbol.
This commit is contained in:
Marco Munizaga
2025-09-09 18:38:38 +00:00
committed by GitHub
parent 9431c65733
commit 14ffd7b53f
4 changed files with 182 additions and 0 deletions
+81
View File
@@ -15948,6 +15948,87 @@ impl Editor {
}
}
pub fn go_to_next_document_highlight(
&mut self,
_: &GoToNextDocumentHighlight,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
}
pub fn go_to_prev_document_highlight(
&mut self,
_: &GoToPreviousDocumentHighlight,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
}
pub fn go_to_document_highlight_before_or_after_position(
&mut self,
direction: Direction,
window: &mut Window,
cx: &mut Context<Editor>,
) {
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
let snapshot = self.snapshot(window, cx);
let buffer = &snapshot.buffer_snapshot;
let position = self.selections.newest::<Point>(cx).head();
let anchor_position = buffer.anchor_after(position);
// Get all document highlights (both read and write)
let mut all_highlights = Vec::new();
if let Some((_, read_highlights)) = self
.background_highlights
.get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
{
all_highlights.extend(read_highlights.iter());
}
if let Some((_, write_highlights)) = self
.background_highlights
.get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
{
all_highlights.extend(write_highlights.iter());
}
if all_highlights.is_empty() {
return;
}
// Sort highlights by position
all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
let target_highlight = match direction {
Direction::Next => {
// Find the first highlight after the current position
all_highlights
.iter()
.find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
}
Direction::Prev => {
// Find the last highlight before the current position
all_highlights
.iter()
.rev()
.find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
}
};
if let Some(highlight) = target_highlight {
let destination = highlight.start.to_point(buffer);
let autoscroll = Autoscroll::center();
self.unfold_ranges(&[destination..destination], false, false, cx);
self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
s.select_ranges([destination..destination]);
});
}
}
fn go_to_line<T: 'static>(
&mut self,
position: Anchor,