mirror of
https://github.com/zed-industries/zed.git
synced 2025-01-28 21:32:39 +00:00
Add link_go_to_definition test for inlays
This commit is contained in:
parent
abd2d012b1
commit
f19c659ed6
4 changed files with 447 additions and 235 deletions
|
@ -4,16 +4,16 @@ use super::{
|
|||
MAX_LINE_LEN,
|
||||
};
|
||||
use crate::{
|
||||
display_map::{BlockStyle, DisplaySnapshot, FoldStatus, InlayOffset, TransformBlock},
|
||||
display_map::{BlockStyle, DisplaySnapshot, FoldStatus, TransformBlock},
|
||||
editor_settings::ShowScrollbar,
|
||||
git::{diff_hunk_to_display, DisplayDiffHunk},
|
||||
hover_popover::{
|
||||
hide_hover, hover_at, hover_at_inlay, InlayHover, HOVER_POPOVER_GAP,
|
||||
MIN_POPOVER_CHARACTER_WIDTH, MIN_POPOVER_LINE_HEIGHT,
|
||||
hide_hover, hover_at, HOVER_POPOVER_GAP, MIN_POPOVER_CHARACTER_WIDTH,
|
||||
MIN_POPOVER_LINE_HEIGHT,
|
||||
},
|
||||
link_go_to_definition::{
|
||||
go_to_fetched_definition, go_to_fetched_type_definition, update_go_to_definition_link,
|
||||
GoToDefinitionTrigger, InlayRange,
|
||||
update_inlay_link_and_hover_points, GoToDefinitionTrigger,
|
||||
},
|
||||
mouse_context_menu, EditorSettings, EditorStyle, GutterHover, UnfoldAt,
|
||||
};
|
||||
|
@ -43,8 +43,7 @@ use language::{
|
|||
};
|
||||
use project::{
|
||||
project_settings::{GitGutterSetting, ProjectSettings},
|
||||
HoverBlock, HoverBlockKind, InlayHintLabelPart, InlayHintLabelPartTooltip, InlayHintTooltip,
|
||||
Location, LocationLink, ProjectPath, ResolveState,
|
||||
ProjectPath,
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
use std::{
|
||||
|
@ -478,10 +477,11 @@ impl EditorElement {
|
|||
}
|
||||
None => {
|
||||
update_inlay_link_and_hover_points(
|
||||
position_map,
|
||||
&position_map.snapshot,
|
||||
point_for_position,
|
||||
editor,
|
||||
(cmd, shift),
|
||||
cmd,
|
||||
shift,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
|
@ -1835,214 +1835,6 @@ impl EditorElement {
|
|||
}
|
||||
}
|
||||
|
||||
fn update_inlay_link_and_hover_points(
|
||||
position_map: &PositionMap,
|
||||
point_for_position: PointForPosition,
|
||||
editor: &mut Editor,
|
||||
(cmd_held, shift_held): (bool, bool),
|
||||
cx: &mut ViewContext<'_, '_, Editor>,
|
||||
) {
|
||||
let hint_start_offset = position_map
|
||||
.snapshot
|
||||
.display_point_to_inlay_offset(point_for_position.previous_valid, Bias::Left);
|
||||
let hint_end_offset = position_map
|
||||
.snapshot
|
||||
.display_point_to_inlay_offset(point_for_position.next_valid, Bias::Right);
|
||||
let offset_overshoot = point_for_position.column_overshoot_after_line_end as usize;
|
||||
let hovered_offset = if offset_overshoot == 0 {
|
||||
Some(
|
||||
position_map
|
||||
.snapshot
|
||||
.display_point_to_inlay_offset(point_for_position.exact_unclipped, Bias::Left),
|
||||
)
|
||||
} else if (hint_end_offset - hint_start_offset).0 >= offset_overshoot {
|
||||
Some(InlayOffset(hint_start_offset.0 + offset_overshoot))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if let Some(hovered_offset) = hovered_offset {
|
||||
let snapshot = editor.buffer().read(cx).snapshot(cx);
|
||||
let previous_valid_anchor = snapshot.anchor_at(
|
||||
point_for_position
|
||||
.previous_valid
|
||||
.to_point(&position_map.snapshot.display_snapshot),
|
||||
Bias::Left,
|
||||
);
|
||||
let next_valid_anchor = snapshot.anchor_at(
|
||||
point_for_position
|
||||
.next_valid
|
||||
.to_point(&position_map.snapshot.display_snapshot),
|
||||
Bias::Right,
|
||||
);
|
||||
|
||||
let mut go_to_definition_updated = false;
|
||||
let mut hover_updated = false;
|
||||
if let Some(hovered_hint) = editor
|
||||
.visible_inlay_hints(cx)
|
||||
.into_iter()
|
||||
.skip_while(|hint| hint.position.cmp(&previous_valid_anchor, &snapshot).is_lt())
|
||||
.take_while(|hint| hint.position.cmp(&next_valid_anchor, &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 => {
|
||||
match cached_hint.label {
|
||||
project::InlayHintLabel::String(_) => {
|
||||
if let Some(tooltip) = cached_hint.tooltip {
|
||||
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,
|
||||
}
|
||||
}
|
||||
},
|
||||
triggered_from: hovered_offset,
|
||||
range: InlayRange {
|
||||
inlay_position: hovered_hint.position,
|
||||
highlight_start: hint_start_offset,
|
||||
highlight_end: hint_end_offset,
|
||||
},
|
||||
},
|
||||
cx,
|
||||
);
|
||||
hover_updated = true;
|
||||
}
|
||||
}
|
||||
project::InlayHintLabel::LabelParts(label_parts) => {
|
||||
if let Some((hovered_hint_part, part_range)) =
|
||||
find_hovered_hint_part(
|
||||
label_parts,
|
||||
hint_start_offset..hint_end_offset,
|
||||
hovered_offset,
|
||||
)
|
||||
{
|
||||
if let Some(tooltip) = hovered_hint_part.tooltip {
|
||||
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,
|
||||
},
|
||||
},
|
||||
triggered_from: hovered_offset,
|
||||
range: InlayRange {
|
||||
inlay_position: hovered_hint.position,
|
||||
highlight_start: part_range.start,
|
||||
highlight_end: part_range.end,
|
||||
},
|
||||
},
|
||||
cx,
|
||||
);
|
||||
hover_updated = true;
|
||||
}
|
||||
if let Some(location) = hovered_hint_part.location {
|
||||
if let Some(buffer) =
|
||||
cached_hint.position.buffer_id.and_then(|buffer_id| {
|
||||
editor.buffer().read(cx).buffer(buffer_id)
|
||||
})
|
||||
{
|
||||
go_to_definition_updated = true;
|
||||
update_go_to_definition_link(
|
||||
editor,
|
||||
GoToDefinitionTrigger::InlayHint(
|
||||
InlayRange {
|
||||
inlay_position: hovered_hint.position,
|
||||
highlight_start: part_range.start,
|
||||
highlight_end: part_range.end,
|
||||
},
|
||||
LocationLink {
|
||||
origin: Some(Location {
|
||||
buffer,
|
||||
range: cached_hint.position
|
||||
..cached_hint.position,
|
||||
}),
|
||||
target: location,
|
||||
},
|
||||
),
|
||||
cmd_held,
|
||||
shift_held,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
ResolveState::Resolving => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !go_to_definition_updated {
|
||||
update_go_to_definition_link(
|
||||
editor,
|
||||
GoToDefinitionTrigger::None,
|
||||
cmd_held,
|
||||
shift_held,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
if !hover_updated {
|
||||
hover_at(editor, None, cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn find_hovered_hint_part(
|
||||
label_parts: Vec<InlayHintLabelPart>,
|
||||
hint_range: Range<InlayOffset>,
|
||||
hovered_offset: InlayOffset,
|
||||
) -> Option<(InlayHintLabelPart, Range<InlayOffset>)> {
|
||||
if hovered_offset >= hint_range.start && hovered_offset <= hint_range.end {
|
||||
let mut hovered_character = (hovered_offset - hint_range.start).0;
|
||||
let mut part_start = hint_range.start;
|
||||
for part in label_parts {
|
||||
let part_len = part.value.chars().count();
|
||||
if hovered_character >= part_len {
|
||||
hovered_character -= part_len;
|
||||
part_start.0 += part_len;
|
||||
} else {
|
||||
return Some((part, part_start..InlayOffset(part_start.0 + part_len)));
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
struct HighlightedChunk<'a> {
|
||||
chunk: &'a str,
|
||||
style: Option<HighlightStyle>,
|
||||
|
@ -2871,12 +2663,12 @@ struct PositionMap {
|
|||
snapshot: EditorSnapshot,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct PointForPosition {
|
||||
previous_valid: DisplayPoint,
|
||||
pub previous_valid: DisplayPoint,
|
||||
pub next_valid: DisplayPoint,
|
||||
exact_unclipped: DisplayPoint,
|
||||
column_overshoot_after_line_end: u32,
|
||||
pub exact_unclipped: DisplayPoint,
|
||||
pub column_overshoot_after_line_end: u32,
|
||||
}
|
||||
|
||||
impl PointForPosition {
|
||||
|
|
|
@ -13,7 +13,7 @@ use gpui::{
|
|||
AnyElement, AppContext, CursorRegion, Element, ModelHandle, MouseRegion, Task, ViewContext,
|
||||
};
|
||||
use language::{Bias, DiagnosticEntry, DiagnosticSeverity, Language, LanguageRegistry};
|
||||
use project::{HoverBlock, HoverBlockKind, Project};
|
||||
use project::{HoverBlock, HoverBlockKind, InlayHintLabelPart, Project};
|
||||
use std::{ops::Range, sync::Arc, time::Duration};
|
||||
use util::TryFutureExt;
|
||||
|
||||
|
@ -55,6 +55,27 @@ pub struct InlayHover {
|
|||
pub tooltip: HoverBlock,
|
||||
}
|
||||
|
||||
pub fn find_hovered_hint_part(
|
||||
label_parts: Vec<InlayHintLabelPart>,
|
||||
hint_range: Range<InlayOffset>,
|
||||
hovered_offset: InlayOffset,
|
||||
) -> Option<(InlayHintLabelPart, Range<InlayOffset>)> {
|
||||
if hovered_offset >= hint_range.start && hovered_offset <= hint_range.end {
|
||||
let mut hovered_character = (hovered_offset - hint_range.start).0;
|
||||
let mut part_start = hint_range.start;
|
||||
for part in label_parts {
|
||||
let part_len = part.value.chars().count();
|
||||
if hovered_character >= part_len {
|
||||
hovered_character -= part_len;
|
||||
part_start.0 += part_len;
|
||||
} else {
|
||||
return Some((part, part_start..InlayOffset(part_start.0 + part_len)));
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn hover_at_inlay(editor: &mut Editor, inlay_hover: InlayHover, cx: &mut ViewContext<Editor>) {
|
||||
if settings::get::<EditorSettings>(cx).hover_popover_enabled {
|
||||
if editor.pending_rename.is_some() {
|
||||
|
|
|
@ -904,7 +904,7 @@ fn apply_hint_update(
|
|||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
pub mod tests {
|
||||
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
|
||||
|
||||
use crate::{
|
||||
|
@ -2989,15 +2989,11 @@ all hints should be invalidated and requeried for all of its visible excerpts"
|
|||
("/a/main.rs", editor, fake_server)
|
||||
}
|
||||
|
||||
fn cached_hint_labels(editor: &Editor) -> Vec<String> {
|
||||
pub fn cached_hint_labels(editor: &Editor) -> Vec<String> {
|
||||
let mut labels = Vec::new();
|
||||
for (_, excerpt_hints) in &editor.inlay_hint_cache().hints {
|
||||
let excerpt_hints = excerpt_hints.read();
|
||||
for (_, inlay) in excerpt_hints.hints.iter() {
|
||||
match &inlay.label {
|
||||
project::InlayHintLabel::String(s) => labels.push(s.to_string()),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
for (_, inlay) in &excerpt_hints.read().hints {
|
||||
labels.push(inlay.text());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3005,7 +3001,7 @@ all hints should be invalidated and requeried for all of its visible excerpts"
|
|||
labels
|
||||
}
|
||||
|
||||
fn visible_hint_labels(editor: &Editor, cx: &ViewContext<'_, '_, Editor>) -> Vec<String> {
|
||||
pub fn visible_hint_labels(editor: &Editor, cx: &ViewContext<'_, '_, Editor>) -> Vec<String> {
|
||||
let mut hints = editor
|
||||
.visible_inlay_hints(cx)
|
||||
.into_iter()
|
||||
|
|
|
@ -1,10 +1,15 @@
|
|||
use crate::{
|
||||
display_map::InlayOffset, element::PointForPosition, Anchor, DisplayPoint, Editor,
|
||||
EditorSnapshot, SelectPhase,
|
||||
display_map::{DisplaySnapshot, InlayOffset},
|
||||
element::PointForPosition,
|
||||
hover_popover::{self, InlayHover},
|
||||
Anchor, DisplayPoint, Editor, EditorSnapshot, SelectPhase,
|
||||
};
|
||||
use gpui::{Task, ViewContext};
|
||||
use language::{Bias, ToOffset};
|
||||
use project::LocationLink;
|
||||
use project::{
|
||||
HoverBlock, HoverBlockKind, InlayHintLabelPartTooltip, InlayHintTooltip, Location,
|
||||
LocationLink, ResolveState,
|
||||
};
|
||||
use std::ops::Range;
|
||||
use util::TryFutureExt;
|
||||
|
||||
|
@ -23,7 +28,7 @@ pub enum GoToDefinitionTrigger {
|
|||
None,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct InlayRange {
|
||||
pub inlay_position: Anchor,
|
||||
pub highlight_start: InlayOffset,
|
||||
|
@ -140,6 +145,192 @@ pub fn update_go_to_definition_link(
|
|||
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 hint_start_offset =
|
||||
snapshot.display_point_to_inlay_offset(point_for_position.previous_valid, Bias::Left);
|
||||
let hint_end_offset =
|
||||
snapshot.display_point_to_inlay_offset(point_for_position.next_valid, Bias::Right);
|
||||
let offset_overshoot = point_for_position.column_overshoot_after_line_end as usize;
|
||||
let hovered_offset = if offset_overshoot == 0 {
|
||||
Some(snapshot.display_point_to_inlay_offset(point_for_position.exact_unclipped, Bias::Left))
|
||||
} else if (hint_end_offset - hint_start_offset).0 >= offset_overshoot {
|
||||
Some(InlayOffset(hint_start_offset.0 + offset_overshoot))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
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,
|
||||
);
|
||||
|
||||
let mut go_to_definition_updated = false;
|
||||
let mut hover_updated = false;
|
||||
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 => {
|
||||
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,
|
||||
}
|
||||
}
|
||||
},
|
||||
triggered_from: hovered_offset,
|
||||
range: InlayRange {
|
||||
inlay_position: hovered_hint.position,
|
||||
highlight_start: hint_start_offset,
|
||||
highlight_end: hint_end_offset,
|
||||
},
|
||||
},
|
||||
cx,
|
||||
);
|
||||
hover_updated = true;
|
||||
}
|
||||
}
|
||||
project::InlayHintLabel::LabelParts(label_parts) => {
|
||||
if let Some((hovered_hint_part, part_range)) =
|
||||
hover_popover::find_hovered_hint_part(
|
||||
label_parts,
|
||||
hint_start_offset..hint_end_offset,
|
||||
hovered_offset,
|
||||
)
|
||||
{
|
||||
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,
|
||||
},
|
||||
},
|
||||
triggered_from: hovered_offset,
|
||||
range: InlayRange {
|
||||
inlay_position: hovered_hint.position,
|
||||
highlight_start: part_range.start,
|
||||
highlight_end: part_range.end,
|
||||
},
|
||||
},
|
||||
cx,
|
||||
);
|
||||
hover_updated = true;
|
||||
}
|
||||
if let Some(location) = hovered_hint_part.location {
|
||||
if let Some(buffer) =
|
||||
cached_hint.position.buffer_id.and_then(|buffer_id| {
|
||||
editor.buffer().read(cx).buffer(buffer_id)
|
||||
})
|
||||
{
|
||||
go_to_definition_updated = true;
|
||||
update_go_to_definition_link(
|
||||
editor,
|
||||
GoToDefinitionTrigger::InlayHint(
|
||||
InlayRange {
|
||||
inlay_position: hovered_hint.position,
|
||||
highlight_start: part_range.start,
|
||||
highlight_end: part_range.end,
|
||||
},
|
||||
LocationLink {
|
||||
origin: Some(Location {
|
||||
buffer,
|
||||
range: cached_hint.position
|
||||
..cached_hint.position,
|
||||
}),
|
||||
target: location,
|
||||
},
|
||||
),
|
||||
cmd_held,
|
||||
shift_held,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
ResolveState::Resolving => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !go_to_definition_updated {
|
||||
update_go_to_definition_link(
|
||||
editor,
|
||||
GoToDefinitionTrigger::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,
|
||||
|
@ -391,14 +582,21 @@ fn go_to_fetched_definition_of_kind(
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{editor_tests::init_test, test::editor_lsp_test_context::EditorLspTestContext};
|
||||
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) {
|
||||
|
@ -853,4 +1051,209 @@ mod tests {
|
|||
"});
|
||||
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 inlay_label = ": TestStruct";
|
||||
cx.lsp
|
||||
.handle_request::<lsp::request::InlayHintRequest, _, _>(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: inlay_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![inlay_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);
|
||||
PointForPosition {
|
||||
previous_valid: inlay_range.start.to_display_point(&snapshot),
|
||||
next_valid: inlay_range.end.to_display_point(&snapshot),
|
||||
exact_unclipped: inlay_range.end.to_display_point(&snapshot),
|
||||
column_overshoot_after_line_end: (inlay_label.len() / 2) as u32,
|
||||
}
|
||||
});
|
||||
// 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_ranges = snapshot
|
||||
.highlight_ranges::<LinkGoToDefinitionState>()
|
||||
.map(|ranges| ranges.as_ref().clone().1)
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.map(|range| match range {
|
||||
DocumentRange::Text(range) => {
|
||||
panic!("Unexpected regular text selection range {range:?}")
|
||||
}
|
||||
DocumentRange::Inlay(inlay_range) => inlay_range,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let buffer_snapshot = editor.buffer().update(cx, |buffer, cx| buffer.snapshot(cx));
|
||||
let expected_highlight_start = snapshot.display_point_to_inlay_offset(
|
||||
inlay_range.start.to_display_point(&snapshot),
|
||||
Bias::Left,
|
||||
);
|
||||
let expected_ranges = vec![InlayRange {
|
||||
inlay_position: buffer_snapshot.anchor_at(inlay_range.start, Bias::Right),
|
||||
highlight_start: expected_highlight_start,
|
||||
highlight_end: InlayOffset(expected_highlight_start.0 + inlay_label.len()),
|
||||
}];
|
||||
assert_set_eq!(actual_ranges, expected_ranges);
|
||||
});
|
||||
|
||||
// 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
|
||||
.highlight_ranges::<LinkGoToDefinitionState>()
|
||||
.map(|ranges| ranges.as_ref().clone().1)
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.map(|range| match range {
|
||||
DocumentRange::Text(range) => {
|
||||
panic!("Unexpected regular text selection range {range:?}")
|
||||
}
|
||||
DocumentRange::Inlay(inlay_range) => inlay_range,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
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;
|
||||
}
|
||||
"});
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue