mirror of
https://github.com/zed-industries/zed.git
synced 2024-09-19 10:29:35 +03:00
Add hover tests
This commit is contained in:
parent
f19c659ed6
commit
e44516cc6c
@ -804,10 +804,17 @@ impl DiagnosticPopover {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{editor_tests::init_test, test::editor_lsp_test_context::EditorLspTestContext};
|
||||
use crate::{
|
||||
editor_tests::init_test,
|
||||
element::PointForPosition,
|
||||
inlay_hint_cache::tests::{cached_hint_labels, visible_hint_labels},
|
||||
link_go_to_definition::update_inlay_link_and_hover_points,
|
||||
test::editor_lsp_test_context::EditorLspTestContext,
|
||||
};
|
||||
use collections::BTreeSet;
|
||||
use gpui::fonts::Weight;
|
||||
use indoc::indoc;
|
||||
use language::{Diagnostic, DiagnosticSet};
|
||||
use language::{language_settings::InlayHintSettings, Diagnostic, DiagnosticSet};
|
||||
use lsp::LanguageServerId;
|
||||
use project::{HoverBlock, HoverBlockKind};
|
||||
use smol::stream::StreamExt;
|
||||
@ -1243,4 +1250,311 @@ mod tests {
|
||||
editor
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_hover_inlay_label_parts(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::Right(
|
||||
lsp::InlayHintServerCapabilities::Options(lsp::InlayHintOptions {
|
||||
resolve_provider: Some(true),
|
||||
..Default::default()
|
||||
}),
|
||||
)),
|
||||
..Default::default()
|
||||
},
|
||||
cx,
|
||||
)
|
||||
.await;
|
||||
|
||||
cx.set_state(indoc! {"
|
||||
struct TestStruct;
|
||||
|
||||
// ==================
|
||||
|
||||
struct TestNewType<T>(T);
|
||||
|
||||
fn main() {
|
||||
let variableˇ = TestNewType(TestStruct);
|
||||
}
|
||||
"});
|
||||
|
||||
let hint_start_offset = cx.ranges(indoc! {"
|
||||
struct TestStruct;
|
||||
|
||||
// ==================
|
||||
|
||||
struct TestNewType<T>(T);
|
||||
|
||||
fn main() {
|
||||
let variableˇ = TestNewType(TestStruct);
|
||||
}
|
||||
"})[0]
|
||||
.start;
|
||||
let hint_position = cx.to_lsp(hint_start_offset);
|
||||
let new_type_target_range = cx.lsp_range(indoc! {"
|
||||
struct TestStruct;
|
||||
|
||||
// ==================
|
||||
|
||||
struct «TestNewType»<T>(T);
|
||||
|
||||
fn main() {
|
||||
let variable = TestNewType(TestStruct);
|
||||
}
|
||||
"});
|
||||
let struct_target_range = cx.lsp_range(indoc! {"
|
||||
struct «TestStruct»;
|
||||
|
||||
// ==================
|
||||
|
||||
struct TestNewType<T>(T);
|
||||
|
||||
fn main() {
|
||||
let variable = TestNewType(TestStruct);
|
||||
}
|
||||
"});
|
||||
|
||||
let uri = cx.buffer_lsp_url.clone();
|
||||
let new_type_label = "TestNewType";
|
||||
let struct_label = "TestStruct";
|
||||
let entire_hint_label = ": TestNewType<TestStruct>";
|
||||
let closure_uri = uri.clone();
|
||||
cx.lsp
|
||||
.handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
|
||||
let task_uri = closure_uri.clone();
|
||||
async move {
|
||||
assert_eq!(params.text_document.uri, task_uri);
|
||||
Ok(Some(vec![lsp::InlayHint {
|
||||
position: hint_position,
|
||||
label: lsp::InlayHintLabel::LabelParts(vec![lsp::InlayHintLabelPart {
|
||||
value: entire_hint_label.to_string(),
|
||||
..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![entire_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;
|
||||
|
||||
// ==================
|
||||
|
||||
struct TestNewType<T>(T);
|
||||
|
||||
fn main() {
|
||||
let variable« »= TestNewType(TestStruct);
|
||||
}
|
||||
"})
|
||||
.get(0)
|
||||
.cloned()
|
||||
.unwrap();
|
||||
let new_type_hint_part_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: (entire_hint_label.find(new_type_label).unwrap()
|
||||
+ new_type_label.len() / 2)
|
||||
as u32,
|
||||
}
|
||||
});
|
||||
cx.update_editor(|editor, cx| {
|
||||
update_inlay_link_and_hover_points(
|
||||
&editor.snapshot(cx),
|
||||
new_type_hint_part_hover_position,
|
||||
editor,
|
||||
true,
|
||||
false,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
let resolve_closure_uri = uri.clone();
|
||||
cx.lsp
|
||||
.handle_request::<lsp::request::InlayHintResolveRequest, _, _>(
|
||||
move |mut hint_to_resolve, _| {
|
||||
let mut resolved_hint_positions = BTreeSet::new();
|
||||
let task_uri = resolve_closure_uri.clone();
|
||||
async move {
|
||||
let inserted = resolved_hint_positions.insert(hint_to_resolve.position);
|
||||
assert!(inserted, "Hint {hint_to_resolve:?} was resolved twice");
|
||||
|
||||
// `: TestNewType<TestStruct>`
|
||||
hint_to_resolve.label = lsp::InlayHintLabel::LabelParts(vec![
|
||||
lsp::InlayHintLabelPart {
|
||||
value: ": ".to_string(),
|
||||
..Default::default()
|
||||
},
|
||||
lsp::InlayHintLabelPart {
|
||||
value: new_type_label.to_string(),
|
||||
location: Some(lsp::Location {
|
||||
uri: task_uri.clone(),
|
||||
range: new_type_target_range,
|
||||
}),
|
||||
tooltip: Some(lsp::InlayHintLabelPartTooltip::String(format!(
|
||||
"A tooltip for `{new_type_label}`"
|
||||
))),
|
||||
..Default::default()
|
||||
},
|
||||
lsp::InlayHintLabelPart {
|
||||
value: "<".to_string(),
|
||||
..Default::default()
|
||||
},
|
||||
lsp::InlayHintLabelPart {
|
||||
value: struct_label.to_string(),
|
||||
location: Some(lsp::Location {
|
||||
uri: task_uri,
|
||||
range: struct_target_range,
|
||||
}),
|
||||
tooltip: Some(lsp::InlayHintLabelPartTooltip::MarkupContent(
|
||||
lsp::MarkupContent {
|
||||
kind: lsp::MarkupKind::Markdown,
|
||||
value: format!("A tooltip for `{struct_label}`"),
|
||||
},
|
||||
)),
|
||||
..Default::default()
|
||||
},
|
||||
lsp::InlayHintLabelPart {
|
||||
value: ">".to_string(),
|
||||
..Default::default()
|
||||
},
|
||||
]);
|
||||
|
||||
Ok(hint_to_resolve)
|
||||
}
|
||||
},
|
||||
)
|
||||
.next()
|
||||
.await;
|
||||
cx.foreground().run_until_parked();
|
||||
|
||||
cx.update_editor(|editor, cx| {
|
||||
update_inlay_link_and_hover_points(
|
||||
&editor.snapshot(cx),
|
||||
new_type_hint_part_hover_position,
|
||||
editor,
|
||||
true,
|
||||
false,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
cx.foreground()
|
||||
.advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100));
|
||||
cx.foreground().run_until_parked();
|
||||
cx.update_editor(|editor, cx| {
|
||||
let snapshot = editor.snapshot(cx);
|
||||
let hover_state = &editor.hover_state;
|
||||
assert!(hover_state.diagnostic_popover.is_none() && hover_state.info_popover.is_some());
|
||||
let popover = hover_state.info_popover.as_ref().unwrap();
|
||||
let buffer_snapshot = editor.buffer().update(cx, |buffer, cx| buffer.snapshot(cx));
|
||||
let entire_inlay_start = snapshot.display_point_to_inlay_offset(
|
||||
inlay_range.start.to_display_point(&snapshot),
|
||||
Bias::Left,
|
||||
);
|
||||
|
||||
let expected_new_type_label_start = InlayOffset(entire_inlay_start.0 + ": ".len());
|
||||
assert_eq!(
|
||||
popover.symbol_range,
|
||||
DocumentRange::Inlay(InlayRange {
|
||||
inlay_position: buffer_snapshot.anchor_at(inlay_range.start, Bias::Right),
|
||||
highlight_start: expected_new_type_label_start,
|
||||
highlight_end: InlayOffset(
|
||||
expected_new_type_label_start.0 + new_type_label.len()
|
||||
),
|
||||
}),
|
||||
"Popover range should match the new type label part"
|
||||
);
|
||||
assert_eq!(
|
||||
popover
|
||||
.rendered_content
|
||||
.as_ref()
|
||||
.expect("should have label text for new type hint")
|
||||
.text,
|
||||
format!("A tooltip for `{new_type_label}`"),
|
||||
"Rendered text should not anyhow alter backticks"
|
||||
);
|
||||
});
|
||||
|
||||
let struct_hint_part_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: (entire_hint_label.find(struct_label).unwrap()
|
||||
+ struct_label.len() / 2)
|
||||
as u32,
|
||||
}
|
||||
});
|
||||
cx.update_editor(|editor, cx| {
|
||||
update_inlay_link_and_hover_points(
|
||||
&editor.snapshot(cx),
|
||||
struct_hint_part_hover_position,
|
||||
editor,
|
||||
true,
|
||||
false,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
cx.foreground()
|
||||
.advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100));
|
||||
cx.foreground().run_until_parked();
|
||||
cx.update_editor(|editor, cx| {
|
||||
let snapshot = editor.snapshot(cx);
|
||||
let hover_state = &editor.hover_state;
|
||||
assert!(hover_state.diagnostic_popover.is_none() && hover_state.info_popover.is_some());
|
||||
let popover = hover_state.info_popover.as_ref().unwrap();
|
||||
let buffer_snapshot = editor.buffer().update(cx, |buffer, cx| buffer.snapshot(cx));
|
||||
let entire_inlay_start = snapshot.display_point_to_inlay_offset(
|
||||
inlay_range.start.to_display_point(&snapshot),
|
||||
Bias::Left,
|
||||
);
|
||||
let expected_struct_label_start =
|
||||
InlayOffset(entire_inlay_start.0 + ": ".len() + new_type_label.len() + "<".len());
|
||||
assert_eq!(
|
||||
popover.symbol_range,
|
||||
DocumentRange::Inlay(InlayRange {
|
||||
inlay_position: buffer_snapshot.anchor_at(inlay_range.start, Bias::Right),
|
||||
highlight_start: expected_struct_label_start,
|
||||
highlight_end: InlayOffset(expected_struct_label_start.0 + struct_label.len()),
|
||||
}),
|
||||
"Popover range should match the struct label part"
|
||||
);
|
||||
assert_eq!(
|
||||
popover
|
||||
.rendered_content
|
||||
.as_ref()
|
||||
.expect("should have label text for struct hint")
|
||||
.text,
|
||||
format!("A tooltip for {struct_label}"),
|
||||
"Rendered markdown element should remove backticks from text"
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -41,7 +41,7 @@ pub enum TriggerPoint {
|
||||
InlayHint(InlayRange, LocationLink),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum DocumentRange {
|
||||
Text(Range<Anchor>),
|
||||
Inlay(InlayRange),
|
||||
@ -1096,7 +1096,7 @@ mod tests {
|
||||
"});
|
||||
|
||||
let expected_uri = cx.buffer_lsp_url.clone();
|
||||
let inlay_label = ": TestStruct";
|
||||
let hint_label = ": TestStruct";
|
||||
cx.lsp
|
||||
.handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
|
||||
let expected_uri = expected_uri.clone();
|
||||
@ -1105,7 +1105,7 @@ mod tests {
|
||||
Ok(Some(vec![lsp::InlayHint {
|
||||
position: hint_position,
|
||||
label: lsp::InlayHintLabel::LabelParts(vec![lsp::InlayHintLabelPart {
|
||||
value: inlay_label.to_string(),
|
||||
value: hint_label.to_string(),
|
||||
location: Some(lsp::Location {
|
||||
uri: params.text_document.uri,
|
||||
range: target_range,
|
||||
@ -1125,7 +1125,7 @@ mod tests {
|
||||
.await;
|
||||
cx.foreground().run_until_parked();
|
||||
cx.update_editor(|editor, cx| {
|
||||
let expected_layers = vec![inlay_label.to_string()];
|
||||
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));
|
||||
});
|
||||
@ -1147,7 +1147,7 @@ mod tests {
|
||||
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,
|
||||
column_overshoot_after_line_end: (hint_label.len() / 2) as u32,
|
||||
}
|
||||
});
|
||||
// Press cmd to trigger highlight
|
||||
@ -1185,7 +1185,7 @@ mod tests {
|
||||
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()),
|
||||
highlight_end: InlayOffset(expected_highlight_start.0 + hint_label.len()),
|
||||
}];
|
||||
assert_set_eq!(actual_ranges, expected_ranges);
|
||||
});
|
||||
|
@ -17,7 +17,7 @@ use language::{
|
||||
CodeAction, Completion, LanguageServerName, OffsetRangeExt, PointUtf16, ToOffset, ToPointUtf16,
|
||||
Transaction, Unclipped,
|
||||
};
|
||||
use lsp::{DocumentHighlightKind, LanguageServer, LanguageServerId, ServerCapabilities};
|
||||
use lsp::{DocumentHighlightKind, LanguageServer, LanguageServerId, OneOf, ServerCapabilities};
|
||||
use std::{cmp::Reverse, ops::Range, path::Path, sync::Arc};
|
||||
|
||||
pub fn lsp_formatting_options(tab_size: u32) -> lsp::FormattingOptions {
|
||||
@ -2213,6 +2213,22 @@ impl InlayHints {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn can_resolve_inlays(capabilities: &ServerCapabilities) -> bool {
|
||||
capabilities
|
||||
.inlay_hint_provider
|
||||
.as_ref()
|
||||
.and_then(|options| match options {
|
||||
OneOf::Left(_is_supported) => None,
|
||||
OneOf::Right(capabilities) => match capabilities {
|
||||
lsp::InlayHintServerCapabilities::Options(o) => o.resolve_provider,
|
||||
lsp::InlayHintServerCapabilities::RegistrationOptions(o) => {
|
||||
o.inlay_hint_options.resolve_provider
|
||||
}
|
||||
},
|
||||
})
|
||||
.unwrap_or(false)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
@ -2269,14 +2285,10 @@ impl LspCommand for InlayHints {
|
||||
lsp_adapter.name.0.as_ref() == "typescript-language-server";
|
||||
|
||||
let hints = message.unwrap_or_default().into_iter().map(|lsp_hint| {
|
||||
let resolve_state = match lsp_server.capabilities().inlay_hint_provider {
|
||||
Some(lsp::OneOf::Right(lsp::InlayHintServerCapabilities::Options(
|
||||
lsp::InlayHintOptions {
|
||||
resolve_provider: Some(true),
|
||||
..
|
||||
},
|
||||
))) => ResolveState::CanResolve(lsp_server.server_id(), lsp_hint.data.clone()),
|
||||
_ => ResolveState::Resolved,
|
||||
let resolve_state = if InlayHints::can_resolve_inlays(lsp_server.capabilities()) {
|
||||
ResolveState::CanResolve(lsp_server.server_id(), lsp_hint.data.clone())
|
||||
} else {
|
||||
ResolveState::Resolved
|
||||
};
|
||||
|
||||
let project = project.clone();
|
||||
|
@ -5043,13 +5043,7 @@ impl Project {
|
||||
} else {
|
||||
return Task::ready(Ok(hint));
|
||||
};
|
||||
let can_resolve = lang_server
|
||||
.capabilities()
|
||||
.completion_provider
|
||||
.as_ref()
|
||||
.and_then(|options| options.resolve_provider)
|
||||
.unwrap_or(false);
|
||||
if !can_resolve {
|
||||
if !InlayHints::can_resolve_inlays(lang_server.capabilities()) {
|
||||
return Task::ready(Ok(hint));
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user