From c94852b8434e5330be44424f5e639d333d247b78 Mon Sep 17 00:00:00 2001 From: Yangze Luo Date: Thu, 29 Feb 2024 17:11:16 +0800 Subject: [PATCH] Go to reference when there's only one (#6924) Fixes #4796 - Improved Go To Definition usability when there's a single reference ([4796](https://github.com/zed-industries/zed/issues/4796)) --------- Co-authored-by: Kirill Bulatov --- crates/editor/src/editor.rs | 56 ++++++++++++++++- crates/editor/src/editor_tests.rs | 99 +++++++++++++++++++++++++++++++ 2 files changed, 153 insertions(+), 2 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index e51e5e12f6495837d753c1d6f28e3070031a9778..69247e4d99b2eea01fbf1663db81c76501b57ade 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -7663,12 +7663,64 @@ impl Editor { let workspace = self.workspace()?; let project = workspace.read(cx).project().clone(); let references = project.update(cx, |project, cx| project.references(&buffer, head, cx)); - Some(cx.spawn(|_, mut cx| async move { - let locations = references.await?; + Some(cx.spawn(|editor, mut cx| async move { + let mut locations = references.await?; + let snapshot = buffer.update(&mut cx, |buffer, _| buffer.snapshot())?; + let head_offset = text::ToOffset::to_offset(&head, &snapshot); + + // LSP may return references that contain the item itself we requested `find_all_references` for (eg. rust-analyzer) + // So we will remove it from locations + // If there is only one reference, we will not do this filter cause it may make locations empty + if locations.len() > 1 { + cx.update(|cx| { + locations.retain(|location| { + // fn foo(x : i64) { + // ^ + // println!(x); + // } + // It is ok to find reference when caret being at ^ (the end of the word) + // So we turn offset into inclusive to include the end of the word + !location + .range + .to_offset(location.buffer.read(cx)) + .to_inclusive() + .contains(&head_offset) + }); + })?; + } + if locations.is_empty() { return Ok(()); } + // If there is one reference, just open it directly + if locations.len() == 1 { + let target = locations.pop().unwrap(); + + return editor.update(&mut cx, |editor, cx| { + let range = target.range.to_offset(target.buffer.read(cx)); + let range = editor.range_for_match(&range); + + if Some(&target.buffer) == editor.buffer().read(cx).as_singleton().as_ref() { + editor.change_selections(Some(Autoscroll::fit()), cx, |s| { + s.select_ranges([range]); + }); + } else { + cx.window_context().defer(move |cx| { + let target_editor: View = + workspace.update(cx, |workspace, cx| { + workspace.open_project_item(target.buffer.clone(), cx) + }); + target_editor.update(cx, |target_editor, cx| { + target_editor.change_selections(Some(Autoscroll::fit()), cx, |s| { + s.select_ranges([range]); + }) + }) + }) + } + }); + } + workspace.update(&mut cx, |workspace, cx| { let title = locations .first() diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index a942686fc7449923128a6b25a8415a8dea51c97d..e826037eebf806602f9adfa3593197d0371342c8 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -8430,6 +8430,105 @@ async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) { ); } +#[gpui::test] +async fn test_find_all_references(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + + let mut cx = EditorLspTestContext::new_rust( + lsp::ServerCapabilities { + document_formatting_provider: Some(lsp::OneOf::Left(true)), + ..Default::default() + }, + cx, + ) + .await; + + cx.set_state(indoc! {" + fn foo(«paramˇ»: i64) { + println!(param); + } + "}); + + cx.lsp + .handle_request::(move |_, _| async move { + Ok(Some(vec![ + lsp::Location { + uri: lsp::Url::from_file_path("/root/dir/file.rs").unwrap(), + range: lsp::Range::new(lsp::Position::new(0, 7), lsp::Position::new(0, 12)), + }, + lsp::Location { + uri: lsp::Url::from_file_path("/root/dir/file.rs").unwrap(), + range: lsp::Range::new(lsp::Position::new(1, 13), lsp::Position::new(1, 18)), + }, + ])) + }); + + let references = cx + .update_editor(|editor, cx| editor.find_all_references(&FindAllReferences, cx)) + .unwrap(); + + cx.executor().run_until_parked(); + + cx.executor().start_waiting(); + references.await.unwrap(); + + cx.assert_editor_state(indoc! {" + fn foo(param: i64) { + println!(«paramˇ»); + } + "}); + + let references = cx + .update_editor(|editor, cx| editor.find_all_references(&FindAllReferences, cx)) + .unwrap(); + + cx.executor().run_until_parked(); + + cx.executor().start_waiting(); + references.await.unwrap(); + + cx.assert_editor_state(indoc! {" + fn foo(«paramˇ»: i64) { + println!(param); + } + "}); + + cx.set_state(indoc! {" + fn foo(param: i64) { + let a = param; + let aˇ = param; + let a = param; + println!(param); + } + "}); + + cx.lsp + .handle_request::(move |_, _| async move { + Ok(Some(vec![lsp::Location { + uri: lsp::Url::from_file_path("/root/dir/file.rs").unwrap(), + range: lsp::Range::new(lsp::Position::new(2, 8), lsp::Position::new(2, 9)), + }])) + }); + + let references = cx + .update_editor(|editor, cx| editor.find_all_references(&FindAllReferences, cx)) + .unwrap(); + + cx.executor().run_until_parked(); + + cx.executor().start_waiting(); + references.await.unwrap(); + + cx.assert_editor_state(indoc! {" + fn foo(param: i64) { + let a = param; + let «aˇ» = param; + let a = param; + println!(param); + } + "}); +} + fn empty_range(row: usize, column: usize) -> Range { let point = DisplayPoint::new(row as u32, column as u32); point..point