diff --git a/crates/editor/src/actions.rs b/crates/editor/src/actions.rs index e823b06910fba67a38754ece6ad746f5f632e613..0bf236769efd17c26ebd55d4dda010d454ba71e4 100644 --- a/crates/editor/src/actions.rs +++ b/crates/editor/src/actions.rs @@ -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, + } + } +} diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 12b591e78054678d7ef4e8735d625e55ae96bc50..d5ff85e5714a5efe255144fb46fbf7a7be29d5e8 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -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, ) -> Option>> { - let selection = self - .selections - .newest::(&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 = + 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, Vec>>, diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index d2653ef3d08916da23248917c727c40860ab129c..2e064ca9de14779b1df73c0887d166f5d6b76c7e 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -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::(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); +} diff --git a/crates/editor/src/hover_links.rs b/crates/editor/src/hover_links.rs index 5ef52f36dfd609a49d93eb77b74b2df3287df30a..9d0261f00f8f7258023b092d4f55d40ac8abcf40 100644 --- a/crates/editor/src/hover_links.rs +++ b/crates/editor/src/hover_links.rs @@ -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) } } }) diff --git a/crates/editor/src/mouse_context_menu.rs b/crates/editor/src/mouse_context_menu.rs index 39ad8a3672511724d69ba59a366513a24edb2198..e868b105fac8a8fa87601e2d5bc8578c94bd1940 100644 --- a/crates/editor/src/mouse_context_menu.rs +++ b/crates/editor/src/mouse_context_menu.rs @@ -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)) diff --git a/crates/zed/src/zed/app_menus.rs b/crates/zed/src/zed/app_menus.rs index 9f91b237101044a870dda66d38edaeee08bc733d..e4355636c74e1bfd126c3b74057827f9a1fa9c0e 100644 --- a/crates/zed/src/zed/app_menus.rs +++ b/crates/zed/src/zed/app_menus.rs @@ -247,7 +247,10 @@ pub fn app_menus(cx: &mut App) -> Vec { 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(