Revert PR #6924 - go to reference when there's only one (#10094)

Max Brunsfeld , Marshall , and Marshall Bowers created

This PR reverts #6924, for the reasons stated in
https://github.com/zed-industries/zed/pull/6924#issuecomment-2033076300.

It also fixes an issue were the `find_all_references_task_sources`
wasn't cleaned up in all cases.

Release Notes:

- N/A

---------

Co-authored-by: Marshall <marshall@zed.dev>
Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>

Change summary

crates/editor/src/editor.rs       |  99 ++++-----------------
crates/editor/src/editor_tests.rs |  99 ----------------------
crates/editor/src/hover_links.rs  | 148 --------------------------------
crates/util/src/util.rs           |   1 
4 files changed, 24 insertions(+), 323 deletions(-)

Detailed changes

crates/editor/src/editor.rs 🔗

@@ -125,7 +125,7 @@ use ui::{
     h_flex, prelude::*, ButtonSize, ButtonStyle, IconButton, IconName, IconSize, ListItem, Popover,
     Tooltip,
 };
-use util::{maybe, post_inc, RangeExt, ResultExt, TryFutureExt};
+use util::{defer, maybe, post_inc, RangeExt, ResultExt, TryFutureExt};
 use workspace::Toast;
 use workspace::{
     searchable::SearchEvent, ItemNavHistory, SplitDirection, ViewId, Workspace, WorkspaceId,
@@ -7853,9 +7853,10 @@ impl Editor {
                 Bias::Left
             },
         );
+
         match self
             .find_all_references_task_sources
-            .binary_search_by(|task_anchor| task_anchor.cmp(&head_anchor, &multi_buffer_snapshot))
+            .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
         {
             Ok(_) => {
                 log::info!(
@@ -7873,66 +7874,27 @@ 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));
-        let open_task = 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)
+        Some(cx.spawn(|editor, mut cx| async move {
+            let _cleanup = defer({
+                let mut cx = cx.clone();
+                move || {
+                    let _ = editor.update(&mut cx, |editor, _| {
+                        if let Ok(i) =
+                            editor
+                                .find_all_references_task_sources
+                                .binary_search_by(|anchor| {
+                                    anchor.cmp(&head_anchor, &multi_buffer_snapshot)
+                                })
+                        {
+                            editor.find_all_references_task_sources.remove(i);
+                        }
                     });
-                })?;
-            }
+                }
+            });
 
+            let locations = references.await?;
             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<Self> =
-                                workspace.update(cx, |workspace, cx| {
-                                    workspace.open_project_item(
-                                        workspace.active_pane().clone(),
-                                        target.buffer.clone(),
-                                        cx,
-                                    )
-                                });
-                            target_editor.update(cx, |target_editor, cx| {
-                                target_editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
-                                    s.select_ranges([range]);
-                                })
-                            })
-                        })
-                    }
-                });
+                return anyhow::Ok(());
             }
 
             workspace.update(&mut cx, |workspace, cx| {
@@ -7952,24 +7914,7 @@ impl Editor {
                 Self::open_locations_in_multibuffer(
                     workspace, locations, replica_id, title, false, cx,
                 );
-            })?;
-
-            Ok(())
-        });
-        Some(cx.spawn(|editor, mut cx| async move {
-            open_task.await?;
-            editor.update(&mut cx, |editor, _| {
-                if let Ok(i) =
-                    editor
-                        .find_all_references_task_sources
-                        .binary_search_by(|task_anchor| {
-                            task_anchor.cmp(&head_anchor, &multi_buffer_snapshot)
-                        })
-                {
-                    editor.find_all_references_task_sources.remove(i);
-                }
-            })?;
-            anyhow::Ok(())
+            })
         }))
     }
 

crates/editor/src/editor_tests.rs 🔗

@@ -8498,105 +8498,6 @@ 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::<lsp::request::References, _, _>(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::<lsp::request::References, _, _>(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);
-        }
-    "});
-}
-
 #[gpui::test]
 async fn test_addition_reverts(cx: &mut gpui::TestAppContext) {
     init_test(cx, |_| {});

crates/editor/src/hover_links.rs 🔗

@@ -702,10 +702,7 @@ mod tests {
     use gpui::Modifiers;
     use indoc::indoc;
     use language::language_settings::InlayHintSettings;
-    use lsp::{
-        request::{GotoDefinition, GotoTypeDefinition},
-        References,
-    };
+    use lsp::request::{GotoDefinition, GotoTypeDefinition};
     use util::assert_set_eq;
     use workspace::item::Item;
 
@@ -1270,147 +1267,4 @@ mod tests {
         cx.simulate_click(screen_coord, Modifiers::secondary_key());
         assert_eq!(cx.opened_url(), Some("https://zed.dev/releases".into()));
     }
-
-    #[gpui::test]
-    async fn test_cmd_click_back_and_forth(cx: &mut gpui::TestAppContext) {
-        init_test(cx, |_| {});
-        let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
-        cx.set_state(indoc! {"
-            fn test() {
-                do_work();
-            }ˇ
-
-            fn do_work() {
-                test();
-            }
-        "});
-
-        // cmd-click on `test` definition and usage, and expect Zed to allow going back and forth,
-        // because cmd-click first searches for definitions to go to, and then fall backs to symbol usages to go to.
-        let definition_hover_point = cx.pixel_position(indoc! {"
-            fn testˇ() {
-                do_work();
-            }
-
-            fn do_work() {
-                test();
-            }
-        "});
-        let definition_display_point = cx.display_point(indoc! {"
-            fn testˇ() {
-                do_work();
-            }
-
-            fn do_work() {
-                test();
-            }
-        "});
-        let definition_range = cx.lsp_range(indoc! {"
-            fn «test»() {
-                do_work();
-            }
-
-            fn do_work() {
-                test();
-            }
-        "});
-        let reference_hover_point = cx.pixel_position(indoc! {"
-            fn test() {
-                do_work();
-            }
-
-            fn do_work() {
-                testˇ();
-            }
-        "});
-        let reference_display_point = cx.display_point(indoc! {"
-            fn test() {
-                do_work();
-            }
-
-            fn do_work() {
-                testˇ();
-            }
-        "});
-        let reference_range = cx.lsp_range(indoc! {"
-            fn test() {
-                do_work();
-            }
-
-            fn do_work() {
-                «test»();
-            }
-        "});
-        let expected_uri = cx.buffer_lsp_url.clone();
-        cx.lsp
-            .handle_request::<GotoDefinition, _, _>(move |params, _| {
-                let expected_uri = expected_uri.clone();
-                async move {
-                    assert_eq!(
-                        params.text_document_position_params.text_document.uri,
-                        expected_uri
-                    );
-                    let position = params.text_document_position_params.position;
-                    Ok(Some(lsp::GotoDefinitionResponse::Link(
-                        if position.line == reference_display_point.row()
-                            && position.character == reference_display_point.column()
-                        {
-                            vec![lsp::LocationLink {
-                                origin_selection_range: None,
-                                target_uri: params.text_document_position_params.text_document.uri,
-                                target_range: definition_range,
-                                target_selection_range: definition_range,
-                            }]
-                        } else {
-                            // We cannot navigate to the definition outside of its reference point
-                            Vec::new()
-                        },
-                    )))
-                }
-            });
-        let expected_uri = cx.buffer_lsp_url.clone();
-        cx.lsp.handle_request::<References, _, _>(move |params, _| {
-            let expected_uri = expected_uri.clone();
-            async move {
-                assert_eq!(
-                    params.text_document_position.text_document.uri,
-                    expected_uri
-                );
-                let position = params.text_document_position.position;
-                // Zed should not look for references if GotoDefinition works or returns non-empty result
-                assert_eq!(position.line, definition_display_point.row());
-                assert_eq!(position.character, definition_display_point.column());
-                Ok(Some(vec![lsp::Location {
-                    uri: params.text_document_position.text_document.uri,
-                    range: reference_range,
-                }]))
-            }
-        });
-
-        for _ in 0..5 {
-            cx.simulate_click(definition_hover_point, Modifiers::secondary_key());
-            cx.background_executor.run_until_parked();
-            cx.assert_editor_state(indoc! {"
-                fn test() {
-                    do_work();
-                }
-
-                fn do_work() {
-                    «testˇ»();
-                }
-            "});
-
-            cx.simulate_click(reference_hover_point, Modifiers::secondary_key());
-            cx.background_executor.run_until_parked();
-            cx.assert_editor_state(indoc! {"
-                fn «testˇ»() {
-                    do_work();
-                }
-
-                fn do_work() {
-                    test();
-                }
-            "});
-        }
-    }
 }

crates/util/src/util.rs 🔗

@@ -367,6 +367,7 @@ impl<F: FnOnce()> Drop for Deferred<F> {
 }
 
 /// Run the given function when the returned value is dropped (unless it's cancelled).
+#[must_use]
 pub fn defer<F: FnOnce()>(f: F) -> Deferred<F> {
     Deferred(Some(f))
 }