Detailed changes
@@ -493,6 +493,10 @@ actions!(
GoToTypeDefinition,
/// Goes to type definition in a split pane.
GoToTypeDefinitionSplit,
+ /// Goes to the next document highlight.
+ GoToNextDocumentHighlight,
+ /// Goes to the previous document highlight.
+ GoToPreviousDocumentHighlight,
/// Scrolls down by half a page.
HalfPageDown,
/// Scrolls up by half a page.
@@ -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,
@@ -25603,6 +25603,101 @@ async fn test_select_next_prev_syntax_node(cx: &mut TestAppContext) {
});
}
+#[gpui::test]
+async fn test_next_prev_document_highlight(cx: &mut TestAppContext) {
+ init_test(cx, |_| {});
+
+ let mut cx = EditorTestContext::new(cx).await;
+ cx.set_state(
+ "let ˇvariable = 42;
+let another = variable + 1;
+let result = variable * 2;",
+ );
+
+ // Set up document highlights manually (simulating LSP response)
+ cx.update_editor(|editor, _window, cx| {
+ let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
+
+ // Create highlights for "variable" occurrences
+ let highlight_ranges = [
+ Point::new(0, 4)..Point::new(0, 12), // First "variable"
+ Point::new(1, 14)..Point::new(1, 22), // Second "variable"
+ Point::new(2, 13)..Point::new(2, 21), // Third "variable"
+ ];
+
+ let anchor_ranges: Vec<_> = highlight_ranges
+ .iter()
+ .map(|range| range.clone().to_anchors(&buffer_snapshot))
+ .collect();
+
+ editor.highlight_background::<DocumentHighlightRead>(
+ &anchor_ranges,
+ |theme| theme.colors().editor_document_highlight_read_background,
+ cx,
+ );
+ });
+
+ // Go to next highlight - should move to second "variable"
+ cx.update_editor(|editor, window, cx| {
+ editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
+ });
+ cx.assert_editor_state(
+ "let variable = 42;
+let another = ˇvariable + 1;
+let result = variable * 2;",
+ );
+
+ // Go to next highlight - should move to third "variable"
+ cx.update_editor(|editor, window, cx| {
+ editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
+ });
+ cx.assert_editor_state(
+ "let variable = 42;
+let another = variable + 1;
+let result = ˇvariable * 2;",
+ );
+
+ // Go to next highlight - should stay at third "variable" (no wrap-around)
+ cx.update_editor(|editor, window, cx| {
+ editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
+ });
+ cx.assert_editor_state(
+ "let variable = 42;
+let another = variable + 1;
+let result = ˇvariable * 2;",
+ );
+
+ // Now test going backwards from third position
+ cx.update_editor(|editor, window, cx| {
+ editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
+ });
+ cx.assert_editor_state(
+ "let variable = 42;
+let another = ˇvariable + 1;
+let result = variable * 2;",
+ );
+
+ // Go to previous highlight - should move to first "variable"
+ cx.update_editor(|editor, window, cx| {
+ editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
+ });
+ cx.assert_editor_state(
+ "let ˇvariable = 42;
+let another = variable + 1;
+let result = variable * 2;",
+ );
+
+ // Go to previous highlight - should stay on first "variable"
+ cx.update_editor(|editor, window, cx| {
+ editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
+ });
+ cx.assert_editor_state(
+ "let ˇvariable = 42;
+let another = variable + 1;
+let result = variable * 2;",
+ );
+}
+
#[track_caller]
fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
editor
@@ -381,6 +381,8 @@ impl EditorElement {
register_action(editor, window, Editor::go_to_prev_diagnostic);
register_action(editor, window, Editor::go_to_next_hunk);
register_action(editor, window, Editor::go_to_prev_hunk);
+ register_action(editor, window, Editor::go_to_next_document_highlight);
+ register_action(editor, window, Editor::go_to_prev_document_highlight);
register_action(editor, window, |editor, action, window, cx| {
editor
.go_to_definition(action, window, cx)