editor: Goto references skip multibuffer if single match (#43026)

Co-authored-by: Agus <agus@zed.dev>
This commit is contained in:
Cameron Mcloughlin
2025-12-08 14:38:19 +00:00
committed by GitHub
co-authored by Agus
parent 62c312b35f
commit 66789607c9
6 changed files with 164 additions and 17 deletions
+17 -2
View File
@@ -453,8 +453,6 @@ actions!(
CollapseAllDiffHunks,
/// Expands macros recursively at cursor position.
ExpandMacroRecursively,
/// Finds all references to the symbol at cursor.
FindAllReferences,
/// Finds the next match in the search.
FindNextMatch,
/// Finds the previous match in the search.
@@ -827,3 +825,20 @@ actions!(
WrapSelectionsInTag
]
);
/// Finds all references to the symbol at cursor.
#[derive(PartialEq, Clone, Deserialize, JsonSchema, Action)]
#[action(namespace = editor)]
#[serde(deny_unknown_fields)]
pub struct FindAllReferences {
#[serde(default = "default_true")]
pub always_open_multibuffer: bool,
}
impl Default for FindAllReferences {
fn default() -> Self {
Self {
always_open_multibuffer: true,
}
}
}
+73 -9
View File
@@ -16815,7 +16815,7 @@ impl Editor {
GoToDefinitionFallback::None => Ok(Navigated::No),
GoToDefinitionFallback::FindAllReferences => {
match editor.update_in(cx, |editor, window, cx| {
editor.find_all_references(&FindAllReferences, window, cx)
editor.find_all_references(&FindAllReferences::default(), window, cx)
})? {
Some(references) => references.await,
None => Ok(Navigated::No),
@@ -17371,20 +17371,21 @@ impl Editor {
pub fn find_all_references(
&mut self,
_: &FindAllReferences,
action: &FindAllReferences,
window: &mut Window,
cx: &mut Context<Self>,
) -> Option<Task<Result<Navigated>>> {
let selection = self
.selections
.newest::<MultiBufferOffset>(&self.display_snapshot(cx));
let always_open_multibuffer = action.always_open_multibuffer;
let selection = self.selections.newest_anchor();
let multi_buffer = self.buffer.read(cx);
let head = selection.head();
let multi_buffer_snapshot = multi_buffer.snapshot(cx);
let selection_offset = selection.map(|anchor| anchor.to_offset(&multi_buffer_snapshot));
let selection_point = selection.map(|anchor| anchor.to_point(&multi_buffer_snapshot));
let head = selection_offset.head();
let head_anchor = multi_buffer_snapshot.anchor_at(
head,
if head < selection.tail() {
if head < selection_offset.tail() {
Bias::Right
} else {
Bias::Left
@@ -17430,6 +17431,15 @@ impl Editor {
let buffer = location.buffer.read(cx);
(location.buffer, location.range.to_point(buffer))
})
// if special-casing the single-match case, remove ranges
// that intersect current selection
.filter(|(location_buffer, location)| {
if always_open_multibuffer || &buffer != location_buffer {
return true;
}
!location.contains_inclusive(&selection_point.range())
})
.into_group_map()
})?;
if locations.is_empty() {
@@ -17439,6 +17449,60 @@ impl Editor {
ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
ranges.dedup();
}
let mut num_locations = 0;
for ranges in locations.values_mut() {
ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
ranges.dedup();
num_locations += ranges.len();
}
if num_locations == 1 && !always_open_multibuffer {
let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
let target_range = target_ranges.first().unwrap().clone();
return editor.update_in(cx, |editor, window, cx| {
let range = target_range.to_point(target_buffer.read(cx));
let range = editor.range_for_match(&range);
let range = range.start..range.start;
if Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref() {
editor.go_to_singleton_buffer_range(range, window, cx);
} else {
let pane = workspace.read(cx).active_pane().clone();
window.defer(cx, move |window, cx| {
let target_editor: Entity<Self> =
workspace.update(cx, |workspace, cx| {
let pane = workspace.active_pane().clone();
let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
let keep_old_preview = preview_tabs_settings
.enable_keep_preview_on_code_navigation;
let allow_new_preview = preview_tabs_settings
.enable_preview_file_from_code_navigation;
workspace.open_project_item(
pane,
target_buffer.clone(),
true,
true,
keep_old_preview,
allow_new_preview,
window,
cx,
)
});
target_editor.update(cx, |target_editor, cx| {
// When selecting a definition in a different buffer, disable the nav history
// to avoid creating a history entry at the previous cursor location.
pane.update(cx, |pane, _| pane.disable_history());
target_editor.go_to_singleton_buffer_range(range, window, cx);
pane.update(cx, |pane, _| pane.enable_history());
});
});
}
Navigated::No
});
}
workspace.update_in(cx, |workspace, window, cx| {
let target = locations
@@ -17476,7 +17540,7 @@ impl Editor {
}))
}
/// Opens a multibuffer with the given project locations in it
/// Opens a multibuffer with the given project locations in it.
pub fn open_locations_in_multibuffer(
workspace: &mut Workspace,
locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
+65 -3
View File
@@ -22579,7 +22579,7 @@ async fn test_find_all_references_editor_reuse(cx: &mut TestAppContext) {
});
let navigated = cx
.update_editor(|editor, window, cx| {
editor.find_all_references(&FindAllReferences, window, cx)
editor.find_all_references(&FindAllReferences::default(), window, cx)
})
.unwrap()
.await
@@ -22615,7 +22615,7 @@ async fn test_find_all_references_editor_reuse(cx: &mut TestAppContext) {
);
let navigated = cx
.update_editor(|editor, window, cx| {
editor.find_all_references(&FindAllReferences, window, cx)
editor.find_all_references(&FindAllReferences::default(), window, cx)
})
.unwrap()
.await
@@ -22667,7 +22667,7 @@ async fn test_find_all_references_editor_reuse(cx: &mut TestAppContext) {
});
let navigated = cx
.update_editor(|editor, window, cx| {
editor.find_all_references(&FindAllReferences, window, cx)
editor.find_all_references(&FindAllReferences::default(), window, cx)
})
.unwrap()
.await
@@ -28916,3 +28916,65 @@ async fn test_multibuffer_scroll_cursor_top_margin(cx: &mut TestAppContext) {
);
});
}
#[gpui::test]
async fn test_find_references_single_case(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let mut cx = EditorLspTestContext::new_rust(
lsp::ServerCapabilities {
references_provider: Some(lsp::OneOf::Left(true)),
..lsp::ServerCapabilities::default()
},
cx,
)
.await;
let before = indoc!(
r#"
fn main() {
let aˇbc = 123;
let xyz = abc;
}
"#
);
let after = indoc!(
r#"
fn main() {
let abc = 123;
let xyz = ˇabc;
}
"#
);
cx.lsp
.set_request_handler::<lsp::request::References, _, _>(async move |params, _| {
Ok(Some(vec![
lsp::Location {
uri: params.text_document_position.text_document.uri.clone(),
range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 11)),
},
lsp::Location {
uri: params.text_document_position.text_document.uri,
range: lsp::Range::new(lsp::Position::new(2, 14), lsp::Position::new(2, 17)),
},
]))
});
cx.set_state(before);
let action = FindAllReferences {
always_open_multibuffer: false,
};
let navigated = cx
.update_editor(|editor, window, cx| editor.find_all_references(&action, window, cx))
.expect("should have spawned a task")
.await
.unwrap();
assert_eq!(navigated, Navigated::No);
cx.run_until_parked();
cx.assert_editor_state(after);
}
+1 -1
View File
@@ -168,7 +168,7 @@ impl Editor {
match EditorSettings::get_global(cx).go_to_definition_fallback {
GoToDefinitionFallback::None => None,
GoToDefinitionFallback::FindAllReferences => {
editor.find_all_references(&FindAllReferences, window, cx)
editor.find_all_references(&FindAllReferences::default(), window, cx)
}
}
})
+4 -1
View File
@@ -235,7 +235,10 @@ pub fn deploy_context_menu(
.action("Go to Declaration", Box::new(GoToDeclaration))
.action("Go to Type Definition", Box::new(GoToTypeDefinition))
.action("Go to Implementation", Box::new(GoToImplementation))
.action("Find All References", Box::new(FindAllReferences))
.action(
"Find All References",
Box::new(FindAllReferences::default()),
)
.separator()
.action("Rename Symbol", Box::new(Rename))
.action("Format Buffer", Box::new(Format))
+4 -1
View File
@@ -247,7 +247,10 @@ pub fn app_menus(cx: &mut App) -> Vec<Menu> {
MenuItem::action("Go to Definition", editor::actions::GoToDefinition),
MenuItem::action("Go to Declaration", editor::actions::GoToDeclaration),
MenuItem::action("Go to Type Definition", editor::actions::GoToTypeDefinition),
MenuItem::action("Find All References", editor::actions::FindAllReferences),
MenuItem::action(
"Find All References",
editor::actions::FindAllReferences::default(),
),
MenuItem::separator(),
MenuItem::action("Next Problem", editor::actions::GoToDiagnostic::default()),
MenuItem::action(