use crate::{ display_map::DisplaySnapshot, element::PointForPosition, hover_popover::{self, InlayHover}, Anchor, DisplayPoint, Editor, EditorSnapshot, GoToDefinition, GoToTypeDefinition, InlayId, SelectPhase, }; use gpui::{px, Task, ViewContext}; use language::{Bias, ToOffset}; use lsp::LanguageServerId; use project::{ HoverBlock, HoverBlockKind, InlayHintLabelPartTooltip, InlayHintTooltip, LocationLink, ResolveState, }; use std::ops::Range; use theme::ActiveTheme as _; use util::TryFutureExt; #[derive(Debug, Default)] pub struct LinkGoToDefinitionState { pub last_trigger_point: Option, pub symbol_range: Option, pub kind: Option, pub definitions: Vec, pub task: Option>>, } #[derive(Debug, Eq, PartialEq, Clone)] pub enum RangeInEditor { Text(Range), Inlay(InlayHighlight), } impl RangeInEditor { pub fn as_text_range(&self) -> Option> { match self { Self::Text(range) => Some(range.clone()), Self::Inlay(_) => None, } } fn point_within_range(&self, trigger_point: &TriggerPoint, snapshot: &EditorSnapshot) -> bool { match (self, trigger_point) { (Self::Text(range), TriggerPoint::Text(point)) => { let point_after_start = range.start.cmp(point, &snapshot.buffer_snapshot).is_le(); point_after_start && range.end.cmp(point, &snapshot.buffer_snapshot).is_ge() } (Self::Inlay(highlight), TriggerPoint::InlayHint(point, _, _)) => { highlight.inlay == point.inlay && highlight.range.contains(&point.range.start) && highlight.range.contains(&point.range.end) } (Self::Inlay(_), TriggerPoint::Text(_)) | (Self::Text(_), TriggerPoint::InlayHint(_, _, _)) => false, } } } #[derive(Debug)] pub enum GoToDefinitionTrigger { Text(DisplayPoint), InlayHint(InlayHighlight, lsp::Location, LanguageServerId), } #[derive(Debug, Clone)] pub enum GoToDefinitionLink { Text(LocationLink), InlayHint(lsp::Location, LanguageServerId), } #[derive(Debug, Clone, PartialEq, Eq)] pub struct InlayHighlight { pub inlay: InlayId, pub inlay_position: Anchor, pub range: Range, } #[derive(Debug, Clone)] pub enum TriggerPoint { Text(Anchor), InlayHint(InlayHighlight, lsp::Location, LanguageServerId), } impl TriggerPoint { pub fn definition_kind(&self, shift: bool) -> LinkDefinitionKind { match self { TriggerPoint::Text(_) => { if shift { LinkDefinitionKind::Type } else { LinkDefinitionKind::Symbol } } TriggerPoint::InlayHint(_, _, _) => LinkDefinitionKind::Type, } } fn anchor(&self) -> &Anchor { match self { TriggerPoint::Text(anchor) => anchor, TriggerPoint::InlayHint(inlay_range, _, _) => &inlay_range.inlay_position, } } } pub fn update_go_to_definition_link( editor: &mut Editor, origin: Option, cmd_held: bool, shift_held: bool, cx: &mut ViewContext, ) { let pending_nonempty_selection = editor.has_pending_nonempty_selection(); // Store new mouse point as an anchor let snapshot = editor.snapshot(cx); let trigger_point = match origin { Some(GoToDefinitionTrigger::Text(p)) => { Some(TriggerPoint::Text(snapshot.buffer_snapshot.anchor_before( p.to_offset(&snapshot.display_snapshot, Bias::Left), ))) } Some(GoToDefinitionTrigger::InlayHint(p, lsp_location, language_server_id)) => { Some(TriggerPoint::InlayHint(p, lsp_location, language_server_id)) } None => None, }; // If the new point is the same as the previously stored one, return early if let (Some(a), Some(b)) = ( &trigger_point, &editor.link_go_to_definition_state.last_trigger_point, ) { match (a, b) { (TriggerPoint::Text(anchor_a), TriggerPoint::Text(anchor_b)) => { if anchor_a.cmp(anchor_b, &snapshot.buffer_snapshot).is_eq() { return; } } (TriggerPoint::InlayHint(range_a, _, _), TriggerPoint::InlayHint(range_b, _, _)) => { if range_a == range_b { return; } } _ => {} } } editor.link_go_to_definition_state.last_trigger_point = trigger_point.clone(); if pending_nonempty_selection { hide_link_definition(editor, cx); return; } if cmd_held { if let Some(trigger_point) = trigger_point { let kind = trigger_point.definition_kind(shift_held); show_link_definition(kind, editor, trigger_point, snapshot, cx); return; } } hide_link_definition(editor, cx); } pub fn update_inlay_link_and_hover_points( snapshot: &DisplaySnapshot, point_for_position: PointForPosition, editor: &mut Editor, cmd_held: bool, shift_held: bool, cx: &mut ViewContext<'_, Editor>, ) { let hovered_offset = if point_for_position.column_overshoot_after_line_end == 0 { Some(snapshot.display_point_to_inlay_offset(point_for_position.exact_unclipped, Bias::Left)) } else { None }; let mut go_to_definition_updated = false; let mut hover_updated = false; if let Some(hovered_offset) = hovered_offset { let buffer_snapshot = editor.buffer().read(cx).snapshot(cx); let previous_valid_anchor = buffer_snapshot.anchor_at( point_for_position.previous_valid.to_point(snapshot), Bias::Left, ); let next_valid_anchor = buffer_snapshot.anchor_at( point_for_position.next_valid.to_point(snapshot), Bias::Right, ); if let Some(hovered_hint) = editor .visible_inlay_hints(cx) .into_iter() .skip_while(|hint| { hint.position .cmp(&previous_valid_anchor, &buffer_snapshot) .is_lt() }) .take_while(|hint| { hint.position .cmp(&next_valid_anchor, &buffer_snapshot) .is_le() }) .max_by_key(|hint| hint.id) { let inlay_hint_cache = editor.inlay_hint_cache(); let excerpt_id = previous_valid_anchor.excerpt_id; if let Some(cached_hint) = inlay_hint_cache.hint_by_id(excerpt_id, hovered_hint.id) { match cached_hint.resolve_state { ResolveState::CanResolve(_, _) => { if let Some(buffer_id) = previous_valid_anchor.buffer_id { inlay_hint_cache.spawn_hint_resolve( buffer_id, excerpt_id, hovered_hint.id, cx, ); } } ResolveState::Resolved => { let mut extra_shift_left = 0; let mut extra_shift_right = 0; if cached_hint.padding_left { extra_shift_left += 1; extra_shift_right += 1; } if cached_hint.padding_right { extra_shift_right += 1; } match cached_hint.label { project::InlayHintLabel::String(_) => { if let Some(tooltip) = cached_hint.tooltip { hover_popover::hover_at_inlay( editor, InlayHover { excerpt: excerpt_id, tooltip: match tooltip { InlayHintTooltip::String(text) => HoverBlock { text, kind: HoverBlockKind::PlainText, }, InlayHintTooltip::MarkupContent(content) => { HoverBlock { text: content.value, kind: content.kind, } } }, range: InlayHighlight { inlay: hovered_hint.id, inlay_position: hovered_hint.position, range: extra_shift_left ..hovered_hint.text.len() + extra_shift_right, }, }, cx, ); hover_updated = true; } } project::InlayHintLabel::LabelParts(label_parts) => { let hint_start = snapshot.anchor_to_inlay_offset(hovered_hint.position); if let Some((hovered_hint_part, part_range)) = hover_popover::find_hovered_hint_part( label_parts, hint_start, hovered_offset, ) { let highlight_start = (part_range.start - hint_start).0 + extra_shift_left; let highlight_end = (part_range.end - hint_start).0 + extra_shift_right; let highlight = InlayHighlight { inlay: hovered_hint.id, inlay_position: hovered_hint.position, range: highlight_start..highlight_end, }; if let Some(tooltip) = hovered_hint_part.tooltip { hover_popover::hover_at_inlay( editor, InlayHover { excerpt: excerpt_id, tooltip: match tooltip { InlayHintLabelPartTooltip::String(text) => { HoverBlock { text, kind: HoverBlockKind::PlainText, } } InlayHintLabelPartTooltip::MarkupContent( content, ) => HoverBlock { text: content.value, kind: content.kind, }, }, range: highlight.clone(), }, cx, ); hover_updated = true; } if let Some((language_server_id, location)) = hovered_hint_part.location { go_to_definition_updated = true; update_go_to_definition_link( editor, Some(GoToDefinitionTrigger::InlayHint( highlight, location, language_server_id, )), cmd_held, shift_held, cx, ); } } } }; } ResolveState::Resolving => {} } } } } if !go_to_definition_updated { update_go_to_definition_link(editor, None, cmd_held, shift_held, cx); } if !hover_updated { hover_popover::hover_at(editor, None, cx); } } #[derive(Debug, Clone, Copy, PartialEq)] pub enum LinkDefinitionKind { Symbol, Type, } pub fn show_link_definition( definition_kind: LinkDefinitionKind, editor: &mut Editor, trigger_point: TriggerPoint, snapshot: EditorSnapshot, cx: &mut ViewContext, ) { let same_kind = editor.link_go_to_definition_state.kind == Some(definition_kind); if !same_kind { hide_link_definition(editor, cx); } if editor.pending_rename.is_some() { return; } let trigger_anchor = trigger_point.anchor(); let (buffer, buffer_position) = if let Some(output) = editor .buffer .read(cx) .text_anchor_for_position(trigger_anchor.clone(), cx) { output } else { return; }; let excerpt_id = if let Some((excerpt_id, _, _)) = editor .buffer() .read(cx) .excerpt_containing(trigger_anchor.clone(), cx) { excerpt_id } else { return; }; let project = if let Some(project) = editor.project.clone() { project } else { return; }; // Don't request again if the location is within the symbol region of a previous request with the same kind if let Some(symbol_range) = &editor.link_go_to_definition_state.symbol_range { if same_kind && symbol_range.point_within_range(&trigger_point, &snapshot) { return; } } let task = cx.spawn(|this, mut cx| { async move { let result = match &trigger_point { TriggerPoint::Text(_) => { // query the LSP for definition info project .update(&mut cx, |project, cx| match definition_kind { LinkDefinitionKind::Symbol => { project.definition(&buffer, buffer_position, cx) } LinkDefinitionKind::Type => { project.type_definition(&buffer, buffer_position, cx) } })? .await .ok() .map(|definition_result| { ( definition_result.iter().find_map(|link| { link.origin.as_ref().map(|origin| { let start = snapshot.buffer_snapshot.anchor_in_excerpt( excerpt_id.clone(), origin.range.start, ); let end = snapshot.buffer_snapshot.anchor_in_excerpt( excerpt_id.clone(), origin.range.end, ); RangeInEditor::Text(start..end) }) }), definition_result .into_iter() .map(GoToDefinitionLink::Text) .collect(), ) }) } TriggerPoint::InlayHint(highlight, lsp_location, server_id) => Some(( Some(RangeInEditor::Inlay(highlight.clone())), vec![GoToDefinitionLink::InlayHint( lsp_location.clone(), *server_id, )], )), }; this.update(&mut cx, |this, cx| { // Clear any existing highlights this.clear_highlights::(cx); this.link_go_to_definition_state.kind = Some(definition_kind); this.link_go_to_definition_state.symbol_range = result .as_ref() .and_then(|(symbol_range, _)| symbol_range.clone()); if let Some((symbol_range, definitions)) = result { this.link_go_to_definition_state.definitions = 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 { GoToDefinitionLink::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 } } GoToDefinitionLink::InlayHint(_, _) => true, } }); if any_definition_does_not_contain_current_location { let style = gpui::HighlightStyle { underline: Some(gpui::UnderlineStyle { thickness: px(1.), ..Default::default() }), color: Some(gpui::red()), ..Default::default() }; let highlight_range = symbol_range.unwrap_or_else(|| match &trigger_point { TriggerPoint::Text(trigger_anchor) => { let snapshot = &snapshot.buffer_snapshot; // If no symbol range returned from language server, use the surrounding word. let (offset_range, _) = snapshot.surrounding_word(*trigger_anchor); RangeInEditor::Text( snapshot.anchor_before(offset_range.start) ..snapshot.anchor_after(offset_range.end), ) } TriggerPoint::InlayHint(highlight, _, _) => { RangeInEditor::Inlay(highlight.clone()) } }); match highlight_range { RangeInEditor::Text(text_range) => this .highlight_text::( vec![text_range], style, cx, ), RangeInEditor::Inlay(highlight) => this .highlight_inlays::( vec![highlight], style, cx, ), } } else { hide_link_definition(this, cx); } } })?; Ok::<_, anyhow::Error>(()) } .log_err() }); editor.link_go_to_definition_state.task = Some(task); } pub fn hide_link_definition(editor: &mut Editor, cx: &mut ViewContext) { if editor.link_go_to_definition_state.symbol_range.is_some() || !editor.link_go_to_definition_state.definitions.is_empty() { editor.link_go_to_definition_state.symbol_range.take(); editor.link_go_to_definition_state.definitions.clear(); cx.notify(); } editor.link_go_to_definition_state.task = None; editor.clear_highlights::(cx); } pub fn go_to_fetched_definition( editor: &mut Editor, point: PointForPosition, split: bool, cx: &mut ViewContext, ) { go_to_fetched_definition_of_kind(LinkDefinitionKind::Symbol, editor, point, split, cx); } pub fn go_to_fetched_type_definition( editor: &mut Editor, point: PointForPosition, split: bool, cx: &mut ViewContext, ) { go_to_fetched_definition_of_kind(LinkDefinitionKind::Type, editor, point, split, cx); } fn go_to_fetched_definition_of_kind( kind: LinkDefinitionKind, editor: &mut Editor, point: PointForPosition, split: bool, cx: &mut ViewContext, ) { let cached_definitions = editor.link_go_to_definition_state.definitions.clone(); hide_link_definition(editor, cx); let cached_definitions_kind = editor.link_go_to_definition_state.kind; let is_correct_kind = cached_definitions_kind == Some(kind); if !cached_definitions.is_empty() && is_correct_kind { if !editor.focus_handle.is_focused(cx) { cx.focus(&editor.focus_handle); } editor.navigate_to_definitions(cached_definitions, split, cx); } else { editor.select( SelectPhase::Begin { position: point.next_valid, add: false, click_count: 1, }, cx, ); if point.as_valid().is_some() { match kind { LinkDefinitionKind::Symbol => editor.go_to_definition(&GoToDefinition, cx), LinkDefinitionKind::Type => editor.go_to_type_definition(&GoToTypeDefinition, cx), } } } } // #[cfg(test)] // mod tests { // use super::*; // use crate::{ // display_map::ToDisplayPoint, // editor_tests::init_test, // inlay_hint_cache::tests::{cached_hint_labels, visible_hint_labels}, // test::editor_lsp_test_context::EditorLspTestContext, // }; // use futures::StreamExt; // use gpui::{ // platform::{self, Modifiers, ModifiersChangedEvent}, // View, // }; // use indoc::indoc; // use language::language_settings::InlayHintSettings; // use lsp::request::{GotoDefinition, GotoTypeDefinition}; // use util::assert_set_eq; // #[gpui::test] // async fn test_link_go_to_type_definition(cx: &mut gpui::TestAppContext) { // init_test(cx, |_| {}); // let mut cx = EditorLspTestContext::new_rust( // lsp::ServerCapabilities { // hover_provider: Some(lsp::HoverProviderCapability::Simple(true)), // type_definition_provider: Some(lsp::TypeDefinitionProviderCapability::Simple(true)), // ..Default::default() // }, // cx, // ) // .await; // cx.set_state(indoc! {" // struct A; // let vˇariable = A; // "}); // // Basic hold cmd+shift, expect highlight in region if response contains type definition // let hover_point = cx.display_point(indoc! {" // struct A; // let vˇariable = A; // "}); // let symbol_range = cx.lsp_range(indoc! {" // struct A; // let «variable» = A; // "}); // let target_range = cx.lsp_range(indoc! {" // struct «A»; // let variable = A; // "}); // let mut requests = // cx.handle_request::(move |url, _, _| async move { // Ok(Some(lsp::GotoTypeDefinitionResponse::Link(vec![ // lsp::LocationLink { // origin_selection_range: Some(symbol_range), // target_uri: url.clone(), // target_range, // target_selection_range: target_range, // }, // ]))) // }); // // Press cmd+shift to trigger highlight // cx.update_editor(|editor, cx| { // update_go_to_definition_link( // editor, // Some(GoToDefinitionTrigger::Text(hover_point)), // true, // true, // cx, // ); // }); // requests.next().await; // cx.foreground().run_until_parked(); // cx.assert_editor_text_highlights::(indoc! {" // struct A; // let «variable» = A; // "}); // // Unpress shift causes highlight to go away (normal goto-definition is not valid here) // cx.update_editor(|editor, cx| { // editor.modifiers_changed( // &platform::ModifiersChangedEvent { // modifiers: Modifiers { // cmd: true, // ..Default::default() // }, // ..Default::default() // }, // cx, // ); // }); // // Assert no link highlights // cx.assert_editor_text_highlights::(indoc! {" // struct A; // let variable = A; // "}); // // Cmd+shift click without existing definition requests and jumps // let hover_point = cx.display_point(indoc! {" // struct A; // let vˇariable = A; // "}); // let target_range = cx.lsp_range(indoc! {" // struct «A»; // let variable = A; // "}); // let mut requests = // cx.handle_request::(move |url, _, _| async move { // Ok(Some(lsp::GotoTypeDefinitionResponse::Link(vec![ // lsp::LocationLink { // origin_selection_range: None, // target_uri: url, // target_range, // target_selection_range: target_range, // }, // ]))) // }); // cx.update_editor(|editor, cx| { // go_to_fetched_type_definition(editor, PointForPosition::valid(hover_point), false, cx); // }); // requests.next().await; // cx.foreground().run_until_parked(); // cx.assert_editor_state(indoc! {" // struct «Aˇ»; // let variable = A; // "}); // } // #[gpui::test] // async fn test_link_go_to_definition(cx: &mut gpui::TestAppContext) { // init_test(cx, |_| {}); // let mut cx = EditorLspTestContext::new_rust( // lsp::ServerCapabilities { // hover_provider: Some(lsp::HoverProviderCapability::Simple(true)), // ..Default::default() // }, // cx, // ) // .await; // cx.set_state(indoc! {" // fn ˇtest() { do_work(); } // fn do_work() { test(); } // "}); // // Basic hold cmd, expect highlight in region if response contains definition // let hover_point = cx.display_point(indoc! {" // fn test() { do_wˇork(); } // fn do_work() { test(); } // "}); // let symbol_range = cx.lsp_range(indoc! {" // fn test() { «do_work»(); } // fn do_work() { test(); } // "}); // let target_range = cx.lsp_range(indoc! {" // fn test() { do_work(); } // fn «do_work»() { test(); } // "}); // let mut requests = cx.handle_request::(move |url, _, _| async move { // Ok(Some(lsp::GotoDefinitionResponse::Link(vec![ // lsp::LocationLink { // origin_selection_range: Some(symbol_range), // target_uri: url.clone(), // target_range, // target_selection_range: target_range, // }, // ]))) // }); // cx.update_editor(|editor, cx| { // update_go_to_definition_link( // editor, // Some(GoToDefinitionTrigger::Text(hover_point)), // true, // false, // cx, // ); // }); // requests.next().await; // cx.foreground().run_until_parked(); // cx.assert_editor_text_highlights::(indoc! {" // fn test() { «do_work»(); } // fn do_work() { test(); } // "}); // // Unpress cmd causes highlight to go away // cx.update_editor(|editor, cx| { // editor.modifiers_changed(&Default::default(), cx); // }); // // Assert no link highlights // cx.assert_editor_text_highlights::(indoc! {" // fn test() { do_work(); } // fn do_work() { test(); } // "}); // // Response without source range still highlights word // cx.update_editor(|editor, _| editor.link_go_to_definition_state.last_trigger_point = None); // let mut requests = cx.handle_request::(move |url, _, _| async move { // Ok(Some(lsp::GotoDefinitionResponse::Link(vec![ // lsp::LocationLink { // // No origin range // origin_selection_range: None, // target_uri: url.clone(), // target_range, // target_selection_range: target_range, // }, // ]))) // }); // cx.update_editor(|editor, cx| { // update_go_to_definition_link( // editor, // Some(GoToDefinitionTrigger::Text(hover_point)), // true, // false, // cx, // ); // }); // requests.next().await; // cx.foreground().run_until_parked(); // cx.assert_editor_text_highlights::(indoc! {" // fn test() { «do_work»(); } // fn do_work() { test(); } // "}); // // Moving mouse to location with no response dismisses highlight // let hover_point = cx.display_point(indoc! {" // fˇn test() { do_work(); } // fn do_work() { test(); } // "}); // let mut requests = cx // .lsp // .handle_request::(move |_, _| async move { // // No definitions returned // Ok(Some(lsp::GotoDefinitionResponse::Link(vec![]))) // }); // cx.update_editor(|editor, cx| { // update_go_to_definition_link( // editor, // Some(GoToDefinitionTrigger::Text(hover_point)), // true, // false, // cx, // ); // }); // requests.next().await; // cx.foreground().run_until_parked(); // // Assert no link highlights // cx.assert_editor_text_highlights::(indoc! {" // fn test() { do_work(); } // fn do_work() { test(); } // "}); // // Move mouse without cmd and then pressing cmd triggers highlight // let hover_point = cx.display_point(indoc! {" // fn test() { do_work(); } // fn do_work() { teˇst(); } // "}); // cx.update_editor(|editor, cx| { // update_go_to_definition_link( // editor, // Some(GoToDefinitionTrigger::Text(hover_point)), // false, // false, // cx, // ); // }); // cx.foreground().run_until_parked(); // // Assert no link highlights // cx.assert_editor_text_highlights::(indoc! {" // fn test() { do_work(); } // fn do_work() { test(); } // "}); // let symbol_range = cx.lsp_range(indoc! {" // fn test() { do_work(); } // fn do_work() { «test»(); } // "}); // let target_range = cx.lsp_range(indoc! {" // fn «test»() { do_work(); } // fn do_work() { test(); } // "}); // let mut requests = cx.handle_request::(move |url, _, _| async move { // Ok(Some(lsp::GotoDefinitionResponse::Link(vec![ // lsp::LocationLink { // origin_selection_range: Some(symbol_range), // target_uri: url, // target_range, // target_selection_range: target_range, // }, // ]))) // }); // cx.update_editor(|editor, cx| { // editor.modifiers_changed( // &ModifiersChangedEvent { // modifiers: Modifiers { // cmd: true, // ..Default::default() // }, // }, // cx, // ); // }); // requests.next().await; // cx.foreground().run_until_parked(); // cx.assert_editor_text_highlights::(indoc! {" // fn test() { do_work(); } // fn do_work() { «test»(); } // "}); // // Deactivating the window dismisses the highlight // cx.update_workspace(|workspace, cx| { // workspace.on_window_activation_changed(false, cx); // }); // cx.assert_editor_text_highlights::(indoc! {" // fn test() { do_work(); } // fn do_work() { test(); } // "}); // // Moving the mouse restores the highlights. // cx.update_editor(|editor, cx| { // update_go_to_definition_link( // editor, // Some(GoToDefinitionTrigger::Text(hover_point)), // true, // false, // cx, // ); // }); // cx.foreground().run_until_parked(); // cx.assert_editor_text_highlights::(indoc! {" // fn test() { do_work(); } // fn do_work() { «test»(); } // "}); // // Moving again within the same symbol range doesn't re-request // let hover_point = cx.display_point(indoc! {" // fn test() { do_work(); } // fn do_work() { tesˇt(); } // "}); // cx.update_editor(|editor, cx| { // update_go_to_definition_link( // editor, // Some(GoToDefinitionTrigger::Text(hover_point)), // true, // false, // cx, // ); // }); // cx.foreground().run_until_parked(); // cx.assert_editor_text_highlights::(indoc! {" // fn test() { do_work(); } // fn do_work() { «test»(); } // "}); // // Cmd click with existing definition doesn't re-request and dismisses highlight // cx.update_editor(|editor, cx| { // go_to_fetched_definition(editor, PointForPosition::valid(hover_point), false, cx); // }); // // Assert selection moved to to definition // cx.lsp // .handle_request::(move |_, _| async move { // // Empty definition response to make sure we aren't hitting the lsp and using // // the cached location instead // Ok(Some(lsp::GotoDefinitionResponse::Link(vec![]))) // }); // cx.foreground().run_until_parked(); // cx.assert_editor_state(indoc! {" // fn «testˇ»() { do_work(); } // fn do_work() { test(); } // "}); // // Assert no link highlights after jump // cx.assert_editor_text_highlights::(indoc! {" // fn test() { do_work(); } // fn do_work() { test(); } // "}); // // Cmd click without existing definition requests and jumps // let hover_point = cx.display_point(indoc! {" // fn test() { do_wˇork(); } // fn do_work() { test(); } // "}); // let target_range = cx.lsp_range(indoc! {" // fn test() { do_work(); } // fn «do_work»() { test(); } // "}); // let mut requests = cx.handle_request::(move |url, _, _| async move { // Ok(Some(lsp::GotoDefinitionResponse::Link(vec![ // lsp::LocationLink { // origin_selection_range: None, // target_uri: url, // target_range, // target_selection_range: target_range, // }, // ]))) // }); // cx.update_editor(|editor, cx| { // go_to_fetched_definition(editor, PointForPosition::valid(hover_point), false, cx); // }); // requests.next().await; // cx.foreground().run_until_parked(); // cx.assert_editor_state(indoc! {" // fn test() { do_work(); } // fn «do_workˇ»() { test(); } // "}); // // 1. We have a pending selection, mouse point is over a symbol that we have a response for, hitting cmd and nothing happens // // 2. Selection is completed, hovering // let hover_point = cx.display_point(indoc! {" // fn test() { do_wˇork(); } // fn do_work() { test(); } // "}); // let target_range = cx.lsp_range(indoc! {" // fn test() { do_work(); } // fn «do_work»() { test(); } // "}); // let mut requests = cx.handle_request::(move |url, _, _| async move { // Ok(Some(lsp::GotoDefinitionResponse::Link(vec![ // lsp::LocationLink { // origin_selection_range: None, // target_uri: url, // target_range, // target_selection_range: target_range, // }, // ]))) // }); // // create a pending selection // let selection_range = cx.ranges(indoc! {" // fn «test() { do_w»ork(); } // fn do_work() { test(); } // "})[0] // .clone(); // cx.update_editor(|editor, cx| { // let snapshot = editor.buffer().read(cx).snapshot(cx); // let anchor_range = snapshot.anchor_before(selection_range.start) // ..snapshot.anchor_after(selection_range.end); // editor.change_selections(Some(crate::Autoscroll::fit()), cx, |s| { // s.set_pending_anchor_range(anchor_range, crate::SelectMode::Character) // }); // }); // cx.update_editor(|editor, cx| { // update_go_to_definition_link( // editor, // Some(GoToDefinitionTrigger::Text(hover_point)), // true, // false, // cx, // ); // }); // cx.foreground().run_until_parked(); // assert!(requests.try_next().is_err()); // cx.assert_editor_text_highlights::(indoc! {" // fn test() { do_work(); } // fn do_work() { test(); } // "}); // cx.foreground().run_until_parked(); // } // #[gpui::test] // async fn test_link_go_to_inlay(cx: &mut gpui::TestAppContext) { // init_test(cx, |settings| { // settings.defaults.inlay_hints = Some(InlayHintSettings { // enabled: true, // show_type_hints: true, // show_parameter_hints: true, // show_other_hints: true, // }) // }); // let mut cx = EditorLspTestContext::new_rust( // lsp::ServerCapabilities { // inlay_hint_provider: Some(lsp::OneOf::Left(true)), // ..Default::default() // }, // cx, // ) // .await; // cx.set_state(indoc! {" // struct TestStruct; // fn main() { // let variableˇ = TestStruct; // } // "}); // let hint_start_offset = cx.ranges(indoc! {" // struct TestStruct; // fn main() { // let variableˇ = TestStruct; // } // "})[0] // .start; // let hint_position = cx.to_lsp(hint_start_offset); // let target_range = cx.lsp_range(indoc! {" // struct «TestStruct»; // fn main() { // let variable = TestStruct; // } // "}); // let expected_uri = cx.buffer_lsp_url.clone(); // let hint_label = ": TestStruct"; // cx.lsp // .handle_request::(move |params, _| { // let expected_uri = expected_uri.clone(); // async move { // assert_eq!(params.text_document.uri, expected_uri); // Ok(Some(vec![lsp::InlayHint { // position: hint_position, // label: lsp::InlayHintLabel::LabelParts(vec![lsp::InlayHintLabelPart { // value: hint_label.to_string(), // location: Some(lsp::Location { // uri: params.text_document.uri, // range: target_range, // }), // ..Default::default() // }]), // kind: Some(lsp::InlayHintKind::TYPE), // text_edits: None, // tooltip: None, // padding_left: Some(false), // padding_right: Some(false), // data: None, // }])) // } // }) // .next() // .await; // cx.foreground().run_until_parked(); // cx.update_editor(|editor, cx| { // let expected_layers = vec![hint_label.to_string()]; // assert_eq!(expected_layers, cached_hint_labels(editor)); // assert_eq!(expected_layers, visible_hint_labels(editor, cx)); // }); // let inlay_range = cx // .ranges(indoc! {" // struct TestStruct; // fn main() { // let variable« »= TestStruct; // } // "}) // .get(0) // .cloned() // .unwrap(); // let hint_hover_position = cx.update_editor(|editor, cx| { // let snapshot = editor.snapshot(cx); // let previous_valid = inlay_range.start.to_display_point(&snapshot); // let next_valid = inlay_range.end.to_display_point(&snapshot); // assert_eq!(previous_valid.row(), next_valid.row()); // assert!(previous_valid.column() < next_valid.column()); // let exact_unclipped = DisplayPoint::new( // previous_valid.row(), // previous_valid.column() + (hint_label.len() / 2) as u32, // ); // PointForPosition { // previous_valid, // next_valid, // exact_unclipped, // column_overshoot_after_line_end: 0, // } // }); // // Press cmd to trigger highlight // cx.update_editor(|editor, cx| { // update_inlay_link_and_hover_points( // &editor.snapshot(cx), // hint_hover_position, // editor, // true, // false, // cx, // ); // }); // cx.foreground().run_until_parked(); // cx.update_editor(|editor, cx| { // let snapshot = editor.snapshot(cx); // let actual_highlights = snapshot // .inlay_highlights::() // .into_iter() // .flat_map(|highlights| highlights.values().map(|(_, highlight)| highlight)) // .collect::>(); // let buffer_snapshot = editor.buffer().update(cx, |buffer, cx| buffer.snapshot(cx)); // let expected_highlight = InlayHighlight { // inlay: InlayId::Hint(0), // inlay_position: buffer_snapshot.anchor_at(inlay_range.start, Bias::Right), // range: 0..hint_label.len(), // }; // assert_set_eq!(actual_highlights, vec![&expected_highlight]); // }); // // Unpress cmd causes highlight to go away // cx.update_editor(|editor, cx| { // editor.modifiers_changed( // &platform::ModifiersChangedEvent { // modifiers: Modifiers { // cmd: false, // ..Default::default() // }, // ..Default::default() // }, // cx, // ); // }); // // Assert no link highlights // cx.update_editor(|editor, cx| { // let snapshot = editor.snapshot(cx); // let actual_ranges = snapshot // .text_highlight_ranges::() // .map(|ranges| ranges.as_ref().clone().1) // .unwrap_or_default(); // assert!(actual_ranges.is_empty(), "When no cmd is pressed, should have no hint label selected, but got: {actual_ranges:?}"); // }); // // Cmd+click without existing definition requests and jumps // cx.update_editor(|editor, cx| { // editor.modifiers_changed( // &platform::ModifiersChangedEvent { // modifiers: Modifiers { // cmd: true, // ..Default::default() // }, // ..Default::default() // }, // cx, // ); // update_inlay_link_and_hover_points( // &editor.snapshot(cx), // hint_hover_position, // editor, // true, // false, // cx, // ); // }); // cx.foreground().run_until_parked(); // cx.update_editor(|editor, cx| { // go_to_fetched_type_definition(editor, hint_hover_position, false, cx); // }); // cx.foreground().run_until_parked(); // cx.assert_editor_state(indoc! {" // struct «TestStructˇ»; // fn main() { // let variable = TestStruct; // } // "}); // } // }