Detailed changes
@@ -7610,36 +7610,52 @@ impl Editor {
}
}
- pub fn go_to_definition(&mut self, _: &GoToDefinition, cx: &mut ViewContext<Self>) {
- self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, cx);
+ pub fn go_to_definition(
+ &mut self,
+ _: &GoToDefinition,
+ cx: &mut ViewContext<Self>,
+ ) -> Task<Result<bool>> {
+ self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, cx)
}
- pub fn go_to_implementation(&mut self, _: &GoToImplementation, cx: &mut ViewContext<Self>) {
- self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, cx);
+ pub fn go_to_implementation(
+ &mut self,
+ _: &GoToImplementation,
+ cx: &mut ViewContext<Self>,
+ ) -> Task<Result<bool>> {
+ self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, cx)
}
pub fn go_to_implementation_split(
&mut self,
_: &GoToImplementationSplit,
cx: &mut ViewContext<Self>,
- ) {
- self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, cx);
+ ) -> Task<Result<bool>> {
+ self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, cx)
}
- pub fn go_to_type_definition(&mut self, _: &GoToTypeDefinition, cx: &mut ViewContext<Self>) {
- self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, cx);
+ pub fn go_to_type_definition(
+ &mut self,
+ _: &GoToTypeDefinition,
+ cx: &mut ViewContext<Self>,
+ ) -> Task<Result<bool>> {
+ self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, cx)
}
- pub fn go_to_definition_split(&mut self, _: &GoToDefinitionSplit, cx: &mut ViewContext<Self>) {
- self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, cx);
+ pub fn go_to_definition_split(
+ &mut self,
+ _: &GoToDefinitionSplit,
+ cx: &mut ViewContext<Self>,
+ ) -> Task<Result<bool>> {
+ self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, cx)
}
pub fn go_to_type_definition_split(
&mut self,
_: &GoToTypeDefinitionSplit,
cx: &mut ViewContext<Self>,
- ) {
- self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, cx);
+ ) -> Task<Result<bool>> {
+ self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, cx)
}
fn go_to_definition_of_kind(
@@ -7647,16 +7663,16 @@ impl Editor {
kind: GotoDefinitionKind,
split: bool,
cx: &mut ViewContext<Self>,
- ) {
+ ) -> Task<Result<bool>> {
let Some(workspace) = self.workspace() else {
- return;
+ return Task::ready(Ok(false));
};
let buffer = self.buffer.read(cx);
let head = self.selections.newest::<usize>(cx).head();
let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
text_anchor
} else {
- return;
+ return Task::ready(Ok(false));
};
let project = workspace.read(cx).project().clone();
@@ -7668,17 +7684,18 @@ impl Editor {
cx.spawn(|editor, mut cx| async move {
let definitions = definitions.await?;
- editor.update(&mut cx, |editor, cx| {
- editor.navigate_to_hover_links(
- Some(kind),
- definitions.into_iter().map(HoverLink::Text).collect(),
- split,
- cx,
- );
- })?;
- Ok::<(), anyhow::Error>(())
+ let navigated = editor
+ .update(&mut cx, |editor, cx| {
+ editor.navigate_to_hover_links(
+ Some(kind),
+ definitions.into_iter().map(HoverLink::Text).collect(),
+ split,
+ cx,
+ )
+ })?
+ .await?;
+ anyhow::Ok(navigated)
})
- .detach_and_log_err(cx);
}
pub fn open_url(&mut self, _: &OpenUrl, cx: &mut ViewContext<Self>) {
@@ -7707,7 +7724,7 @@ impl Editor {
mut definitions: Vec<HoverLink>,
split: bool,
cx: &mut ViewContext<Editor>,
- ) {
+ ) -> Task<Result<bool>> {
// If there is one definition, just open it directly
if definitions.len() == 1 {
let definition = definitions.pop().unwrap();
@@ -7726,7 +7743,7 @@ impl Editor {
if let Some(target) = target {
editor.update(&mut cx, |editor, cx| {
let Some(workspace) = editor.workspace() else {
- return;
+ return false;
};
let pane = workspace.read(cx).active_pane().clone();
@@ -7763,12 +7780,12 @@ impl Editor {
});
});
}
+ true
})
} else {
- Ok(())
+ Ok(false)
}
})
- .detach_and_log_err(cx);
} else if !definitions.is_empty() {
let replica_id = self.replica_id(cx);
cx.spawn(|editor, mut cx| async move {
@@ -7817,9 +7834,9 @@ impl Editor {
.context("location tasks")?;
let Some(workspace) = workspace else {
- return Ok(());
+ return Ok(false);
};
- workspace
+ let opened = workspace
.update(&mut cx, |workspace, cx| {
Self::open_locations_in_multibuffer(
workspace, locations, replica_id, title, split, cx,
@@ -7827,9 +7844,10 @@ impl Editor {
})
.ok();
- anyhow::Ok(())
+ anyhow::Ok(opened.is_some())
})
- .detach_and_log_err(cx);
+ } else {
+ Task::ready(Ok(false))
}
}
@@ -258,12 +258,28 @@ impl EditorElement {
register_action(view, cx, Editor::go_to_prev_diagnostic);
register_action(view, cx, Editor::go_to_hunk);
register_action(view, cx, Editor::go_to_prev_hunk);
- register_action(view, cx, Editor::go_to_definition);
- register_action(view, cx, Editor::go_to_definition_split);
- register_action(view, cx, Editor::go_to_implementation);
- register_action(view, cx, Editor::go_to_implementation_split);
- register_action(view, cx, Editor::go_to_type_definition);
- register_action(view, cx, Editor::go_to_type_definition_split);
+ register_action(view, cx, |editor, a, cx| {
+ editor.go_to_definition(a, cx).detach_and_log_err(cx);
+ });
+ register_action(view, cx, |editor, a, cx| {
+ editor.go_to_definition_split(a, cx).detach_and_log_err(cx);
+ });
+ register_action(view, cx, |editor, a, cx| {
+ editor.go_to_implementation(a, cx).detach_and_log_err(cx);
+ });
+ register_action(view, cx, |editor, a, cx| {
+ editor
+ .go_to_implementation_split(a, cx)
+ .detach_and_log_err(cx);
+ });
+ register_action(view, cx, |editor, a, cx| {
+ editor.go_to_type_definition(a, cx).detach_and_log_err(cx);
+ });
+ register_action(view, cx, |editor, a, cx| {
+ editor
+ .go_to_type_definition_split(a, cx)
+ .detach_and_log_err(cx);
+ });
register_action(view, cx, Editor::open_url);
register_action(view, cx, Editor::fold);
register_action(view, cx, Editor::fold_at);
@@ -1,7 +1,7 @@
use crate::{
hover_popover::{self, InlayHover},
- Anchor, Editor, EditorSnapshot, GoToDefinition, GoToTypeDefinition, InlayId, PointForPosition,
- SelectPhase,
+ Anchor, Editor, EditorSnapshot, FindAllReferences, GoToDefinition, GoToTypeDefinition, InlayId,
+ PointForPosition, SelectPhase,
};
use gpui::{px, AsyncWindowContext, Model, Modifiers, Task, ViewContext};
use language::{Bias, ToOffset};
@@ -11,9 +11,10 @@ use project::{
HoverBlock, HoverBlockKind, InlayHintLabelPartTooltip, InlayHintTooltip, LocationLink,
ResolveState,
};
-use std::ops::Range;
+use std::{cmp, ops::Range};
+use text::Point;
use theme::ActiveTheme as _;
-use util::{maybe, TryFutureExt};
+use util::{maybe, ResultExt, TryFutureExt};
#[derive(Debug)]
pub struct HoveredLinkState {
@@ -131,6 +132,47 @@ impl Editor {
modifiers: Modifiers,
cx: &mut ViewContext<Editor>,
) {
+ let selection_before_revealing = self.selections.newest::<Point>(cx);
+ let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
+ let before_revealing_head = selection_before_revealing.head();
+ let before_revealing_tail = selection_before_revealing.tail();
+ let before_revealing = match before_revealing_tail.cmp(&before_revealing_head) {
+ cmp::Ordering::Equal | cmp::Ordering::Less => {
+ multi_buffer_snapshot.anchor_after(before_revealing_head)
+ ..multi_buffer_snapshot.anchor_before(before_revealing_tail)
+ }
+ cmp::Ordering::Greater => {
+ multi_buffer_snapshot.anchor_before(before_revealing_tail)
+ ..multi_buffer_snapshot.anchor_after(before_revealing_head)
+ }
+ };
+ drop(multi_buffer_snapshot);
+
+ let reveal_task = self.cmd_click_reveal_task(point, modifiers, cx);
+ cx.spawn(|editor, mut cx| async move {
+ let definition_revealed = reveal_task.await.log_err().unwrap_or(false);
+ let find_references = editor
+ .update(&mut cx, |editor, cx| {
+ if definition_revealed && revealed_elsewhere(editor, before_revealing, cx) {
+ return None;
+ }
+ editor.find_all_references(&FindAllReferences, cx)
+ })
+ .ok()
+ .flatten();
+ if let Some(find_references) = find_references {
+ find_references.await.log_err();
+ }
+ })
+ .detach();
+ }
+
+ fn cmd_click_reveal_task(
+ &mut self,
+ point: PointForPosition,
+ modifiers: Modifiers,
+ cx: &mut ViewContext<Editor>,
+ ) -> Task<anyhow::Result<bool>> {
if let Some(hovered_link_state) = self.hovered_link_state.take() {
self.hide_hovered_link(cx);
if !hovered_link_state.links.is_empty() {
@@ -138,8 +180,12 @@ impl Editor {
cx.focus(&self.focus_handle);
}
- self.navigate_to_hover_links(None, hovered_link_state.links, modifiers.alt, cx);
- return;
+ return self.navigate_to_hover_links(
+ None,
+ hovered_link_state.links,
+ modifiers.alt,
+ cx,
+ );
}
}
@@ -160,10 +206,52 @@ impl Editor {
} else {
self.go_to_definition(&GoToDefinition, cx)
}
+ } else {
+ Task::ready(Ok(false))
}
}
}
+fn revealed_elsewhere(
+ editor: &mut Editor,
+ before_revealing: Range<Anchor>,
+ cx: &mut ViewContext<'_, Editor>,
+) -> bool {
+ let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
+
+ let selection_after_revealing = editor.selections.newest::<Point>(cx);
+ let after_revealing_head = selection_after_revealing.head();
+ let after_revealing_tail = selection_after_revealing.tail();
+ let after_revealing = match after_revealing_tail.cmp(&after_revealing_head) {
+ cmp::Ordering::Equal | cmp::Ordering::Less => {
+ multi_buffer_snapshot.anchor_after(after_revealing_tail)
+ ..multi_buffer_snapshot.anchor_before(after_revealing_head)
+ }
+ cmp::Ordering::Greater => {
+ multi_buffer_snapshot.anchor_after(after_revealing_head)
+ ..multi_buffer_snapshot.anchor_before(after_revealing_tail)
+ }
+ };
+
+ let before_intersects_after_range = (before_revealing
+ .start
+ .cmp(&after_revealing.start, &multi_buffer_snapshot)
+ .is_ge()
+ && before_revealing
+ .start
+ .cmp(&after_revealing.end, &multi_buffer_snapshot)
+ .is_le())
+ || (before_revealing
+ .end
+ .cmp(&after_revealing.start, &multi_buffer_snapshot)
+ .is_ge()
+ && before_revealing
+ .end
+ .cmp(&after_revealing.end, &multi_buffer_snapshot)
+ .is_le());
+ !before_intersects_after_range
+}
+
pub fn update_inlay_link_and_hover_points(
snapshot: &EditorSnapshot,
point_for_position: PointForPosition,
@@ -473,10 +561,11 @@ pub fn show_link_definition(
)),
};
- this.update(&mut cx, |this, cx| {
+ this.update(&mut cx, |editor, cx| {
// Clear any existing highlights
- this.clear_highlights::<HoveredLinkState>(cx);
- let Some(hovered_link_state) = this.hovered_link_state.as_mut() else {
+ editor.clear_highlights::<HoveredLinkState>(cx);
+ let Some(hovered_link_state) = editor.hovered_link_state.as_mut() else {
+ editor.hide_hovered_link(cx);
return;
};
hovered_link_state.preferred_kind = preferred_kind;
@@ -485,43 +574,12 @@ pub fn show_link_definition(
.and_then(|(symbol_range, _)| symbol_range.clone());
if let Some((symbol_range, definitions)) = result {
- hovered_link_state.links = definitions.clone();
-
- let buffer_snapshot = buffer.read(cx).snapshot();
-
- // Only show highlight if there exists a definition to jump to that doesn't contain
- // the current location.
- let any_definition_does_not_contain_current_location =
- definitions.iter().any(|definition| {
- match &definition {
- HoverLink::Text(link) => {
- if link.target.buffer == buffer {
- let range = &link.target.range;
- // Expand range by one character as lsp definition ranges include positions adjacent
- // but not contained by the symbol range
- let start = buffer_snapshot.clip_offset(
- range
- .start
- .to_offset(&buffer_snapshot)
- .saturating_sub(1),
- Bias::Left,
- );
- let end = buffer_snapshot.clip_offset(
- range.end.to_offset(&buffer_snapshot) + 1,
- Bias::Right,
- );
- let offset = buffer_position.to_offset(&buffer_snapshot);
- !(start <= offset && end >= offset)
- } else {
- true
- }
- }
- HoverLink::InlayHint(_, _) => true,
- HoverLink::Url(_) => true,
- }
- });
+ hovered_link_state.links = definitions;
- if any_definition_does_not_contain_current_location {
+ let underline_hovered_link = hovered_link_state.links.len() > 0
+ || hovered_link_state.symbol_range.is_some();
+
+ if underline_hovered_link {
let style = gpui::HighlightStyle {
underline: Some(gpui::UnderlineStyle {
thickness: px(1.),
@@ -547,15 +605,14 @@ pub fn show_link_definition(
});
match highlight_range {
- RangeInEditor::Text(text_range) => {
- this.highlight_text::<HoveredLinkState>(vec![text_range], style, cx)
- }
- RangeInEditor::Inlay(highlight) => this
+ RangeInEditor::Text(text_range) => editor
+ .highlight_text::<HoveredLinkState>(vec![text_range], style, cx),
+ RangeInEditor::Inlay(highlight) => editor
.highlight_inlays::<HoveredLinkState>(vec![highlight], style, cx),
}
- } else {
- this.hide_hovered_link(cx);
}
+ } else {
+ editor.hide_hovered_link(cx);
}
})?;
@@ -643,7 +700,10 @@ mod tests {
use gpui::Modifiers;
use indoc::indoc;
use language::language_settings::InlayHintSettings;
- use lsp::request::{GotoDefinition, GotoTypeDefinition};
+ use lsp::{
+ request::{GotoDefinition, GotoTypeDefinition},
+ References,
+ };
use util::assert_set_eq;
use workspace::item::Item;
@@ -1208,4 +1268,147 @@ mod tests {
cx.simulate_click(screen_coord, Modifiers::command());
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::command());
+ 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::command());
+ cx.background_executor.run_until_parked();
+ cx.assert_editor_state(indoc! {"
+ fn «testˇ»() {
+ do_work();
+ }
+
+ fn do_work() {
+ test();
+ }
+ "});
+ }
+ }
}
@@ -97,7 +97,7 @@ pub(crate) struct PerformRename {
pub push_to_history: bool,
}
-pub(crate) struct GetDefinition {
+pub struct GetDefinition {
pub position: PointUtf16,
}