Skip to content

Commit 068ede0

Browse files
Merge #9693
9693: feat: Add the Hover Range capability which enables showing the type of an expression r=matklad a=alexfertel Closes #389 This PR extends the `textDocument/hover` method to allow getting the type of an expression. It looks like this: ![type_of_expression](https://user-images.githubusercontent.com/22298999/126914293-0ce49a92-545d-4005-a59e-9294fa2330d6.gif) Edit: One thing I noticed is that when hovering a selection that includes a macro it doesn't work, so maybe this would need a follow-up issue discussing what problem that may have. (PS: What a great project! I am learning a lot! 🚀) Co-authored-by: Alexander Gonzalez <[email protected]> Co-authored-by: Alexander González <[email protected]>
2 parents f749e9c + 4d3a052 commit 068ede0

File tree

11 files changed

+302
-34
lines changed

11 files changed

+302
-34
lines changed

crates/hir_def/src/test_db.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ impl TestDB {
233233
events
234234
.into_iter()
235235
.filter_map(|e| match e.kind {
236-
// This pretty horrible, but `Debug` is the only way to inspect
236+
// This is pretty horrible, but `Debug` is the only way to inspect
237237
// QueryDescriptor at the moment.
238238
salsa::EventKind::WillExecute { database_key } => {
239239
Some(format!("{:?}", database_key.debug(self)))

crates/hir_ty/src/test_db.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ impl TestDB {
138138
events
139139
.into_iter()
140140
.filter_map(|e| match e.kind {
141-
// This pretty horrible, but `Debug` is the only way to inspect
141+
// This is pretty horrible, but `Debug` is the only way to inspect
142142
// QueryDescriptor at the moment.
143143
salsa::EventKind::WillExecute { database_key } => {
144144
Some(format!("{:?}", database_key.debug(self)))

crates/ide/src/hover.rs

+209-14
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use either::Either;
22
use hir::{AsAssocItem, HasAttrs, HasSource, HirDisplay, Semantics};
33
use ide_db::{
4-
base_db::SourceDatabase,
4+
base_db::{FileRange, SourceDatabase},
55
defs::{Definition, NameClass, NameRefClass},
66
helpers::{
77
generated_lints::{CLIPPY_LINTS, DEFAULT_LINTS, FEATURES},
@@ -12,8 +12,12 @@ use ide_db::{
1212
use itertools::Itertools;
1313
use stdx::format_to;
1414
use syntax::{
15-
algo, ast, display::fn_as_proc_macro_label, match_ast, AstNode, AstToken, Direction,
16-
SyntaxKind::*, SyntaxToken, T,
15+
algo::{self, find_node_at_range},
16+
ast,
17+
display::fn_as_proc_macro_label,
18+
match_ast, AstNode, AstToken, Direction,
19+
SyntaxKind::*,
20+
SyntaxToken, T,
1721
};
1822

1923
use crate::{
@@ -69,17 +73,39 @@ pub struct HoverResult {
6973

7074
// Feature: Hover
7175
//
72-
// Shows additional information, like type of an expression or documentation for definition when "focusing" code.
76+
// Shows additional information, like the type of an expression or the documentation for a definition when "focusing" code.
7377
// Focusing is usually hovering with a mouse, but can also be triggered with a shortcut.
7478
//
7579
// image::https://user-images.githubusercontent.com/48062697/113020658-b5f98b80-917a-11eb-9f88-3dbc27320c95.gif[]
7680
pub(crate) fn hover(
7781
db: &RootDatabase,
78-
position: FilePosition,
82+
range: FileRange,
7983
config: &HoverConfig,
8084
) -> Option<RangeInfo<HoverResult>> {
8185
let sema = hir::Semantics::new(db);
82-
let file = sema.parse(position.file_id).syntax().clone();
86+
let file = sema.parse(range.file_id).syntax().clone();
87+
88+
// This means we're hovering over a range.
89+
if !range.range.is_empty() {
90+
let expr = find_node_at_range::<ast::Expr>(&file, range.range)?;
91+
let ty = sema.type_of_expr(&expr)?;
92+
93+
if ty.is_unknown() {
94+
return None;
95+
}
96+
97+
let mut res = HoverResult::default();
98+
99+
res.markup = if config.markdown() {
100+
Markup::fenced_block(&ty.display(db))
101+
} else {
102+
ty.display(db).to_string().into()
103+
};
104+
105+
return Some(RangeInfo::new(range.range, res));
106+
}
107+
108+
let position = FilePosition { file_id: range.file_id, offset: range.range.start() };
83109
let token = pick_best_token(file.token_at_offset(position.offset), |kind| match kind {
84110
IDENT | INT_NUMBER | LIFETIME_IDENT | T![self] | T![super] | T![crate] => 3,
85111
T!['('] | T![')'] => 2,
@@ -94,8 +120,8 @@ pub(crate) fn hover(
94120
let mut range = None;
95121
let definition = match_ast! {
96122
match node {
97-
// we don't use NameClass::referenced_or_defined here as we do not want to resolve
98-
// field pattern shorthands to their definition
123+
// We don't use NameClass::referenced_or_defined here as we do not want to resolve
124+
// field pattern shorthands to their definition.
99125
ast::Name(name) => NameClass::classify(&sema, &name).map(|class| match class {
100126
NameClass::Definition(it) | NameClass::ConstReference(it) => it,
101127
NameClass::PatFieldShorthand { local_def, field_ref: _ } => Definition::Local(local_def),
@@ -193,6 +219,7 @@ pub(crate) fn hover(
193219
} else {
194220
ty.display(db).to_string().into()
195221
};
222+
196223
let range = sema.original_range(&node).range;
197224
Some(RangeInfo::new(range, res))
198225
}
@@ -530,7 +557,8 @@ fn find_std_module(famous_defs: &FamousDefs, name: &str) -> Option<hir::Module>
530557
#[cfg(test)]
531558
mod tests {
532559
use expect_test::{expect, Expect};
533-
use ide_db::base_db::FileLoader;
560+
use ide_db::base_db::{FileLoader, FileRange};
561+
use syntax::TextRange;
534562

535563
use crate::{fixture, hover::HoverDocFormat, HoverConfig};
536564

@@ -542,7 +570,7 @@ mod tests {
542570
links_in_hover: true,
543571
documentation: Some(HoverDocFormat::Markdown),
544572
},
545-
position,
573+
FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) },
546574
)
547575
.unwrap();
548576
assert!(hover.is_none());
@@ -556,7 +584,7 @@ mod tests {
556584
links_in_hover: true,
557585
documentation: Some(HoverDocFormat::Markdown),
558586
},
559-
position,
587+
FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) },
560588
)
561589
.unwrap()
562590
.unwrap();
@@ -576,7 +604,7 @@ mod tests {
576604
links_in_hover: false,
577605
documentation: Some(HoverDocFormat::Markdown),
578606
},
579-
position,
607+
FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) },
580608
)
581609
.unwrap()
582610
.unwrap();
@@ -596,7 +624,7 @@ mod tests {
596624
links_in_hover: true,
597625
documentation: Some(HoverDocFormat::PlainText),
598626
},
599-
position,
627+
FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) },
600628
)
601629
.unwrap()
602630
.unwrap();
@@ -616,13 +644,42 @@ mod tests {
616644
links_in_hover: true,
617645
documentation: Some(HoverDocFormat::Markdown),
618646
},
619-
position,
647+
FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) },
620648
)
621649
.unwrap()
622650
.unwrap();
623651
expect.assert_debug_eq(&hover.info.actions)
624652
}
625653

654+
fn check_hover_range(ra_fixture: &str, expect: Expect) {
655+
let (analysis, range) = fixture::range(ra_fixture);
656+
let hover = analysis
657+
.hover(
658+
&HoverConfig {
659+
links_in_hover: false,
660+
documentation: Some(HoverDocFormat::Markdown),
661+
},
662+
range,
663+
)
664+
.unwrap()
665+
.unwrap();
666+
expect.assert_eq(hover.info.markup.as_str())
667+
}
668+
669+
fn check_hover_range_no_results(ra_fixture: &str) {
670+
let (analysis, range) = fixture::range(ra_fixture);
671+
let hover = analysis
672+
.hover(
673+
&HoverConfig {
674+
links_in_hover: false,
675+
documentation: Some(HoverDocFormat::Markdown),
676+
},
677+
range,
678+
)
679+
.unwrap();
680+
assert!(hover.is_none());
681+
}
682+
626683
#[test]
627684
fn hover_shows_type_of_an_expression() {
628685
check(
@@ -3882,4 +3939,142 @@ struct Foo;
38823939
"#]],
38833940
);
38843941
}
3942+
3943+
#[test]
3944+
fn hover_range_math() {
3945+
check_hover_range(
3946+
r#"
3947+
fn f() { let expr = $01 + 2 * 3$0 }
3948+
"#,
3949+
expect![[r#"
3950+
```rust
3951+
i32
3952+
```"#]],
3953+
);
3954+
3955+
check_hover_range(
3956+
r#"
3957+
fn f() { let expr = 1 $0+ 2 * $03 }
3958+
"#,
3959+
expect![[r#"
3960+
```rust
3961+
i32
3962+
```"#]],
3963+
);
3964+
3965+
check_hover_range(
3966+
r#"
3967+
fn f() { let expr = 1 + $02 * 3$0 }
3968+
"#,
3969+
expect![[r#"
3970+
```rust
3971+
i32
3972+
```"#]],
3973+
);
3974+
}
3975+
3976+
#[test]
3977+
fn hover_range_arrays() {
3978+
check_hover_range(
3979+
r#"
3980+
fn f() { let expr = $0[1, 2, 3, 4]$0 }
3981+
"#,
3982+
expect![[r#"
3983+
```rust
3984+
[i32; 4]
3985+
```"#]],
3986+
);
3987+
3988+
check_hover_range(
3989+
r#"
3990+
fn f() { let expr = [1, 2, $03, 4]$0 }
3991+
"#,
3992+
expect![[r#"
3993+
```rust
3994+
[i32; 4]
3995+
```"#]],
3996+
);
3997+
3998+
check_hover_range(
3999+
r#"
4000+
fn f() { let expr = [1, 2, $03$0, 4] }
4001+
"#,
4002+
expect![[r#"
4003+
```rust
4004+
i32
4005+
```"#]],
4006+
);
4007+
}
4008+
4009+
#[test]
4010+
fn hover_range_functions() {
4011+
check_hover_range(
4012+
r#"
4013+
fn f<T>(a: &[T]) { }
4014+
fn b() { $0f$0(&[1, 2, 3, 4, 5]); }
4015+
"#,
4016+
expect![[r#"
4017+
```rust
4018+
fn f<i32>(&[i32])
4019+
```"#]],
4020+
);
4021+
4022+
check_hover_range(
4023+
r#"
4024+
fn f<T>(a: &[T]) { }
4025+
fn b() { f($0&[1, 2, 3, 4, 5]$0); }
4026+
"#,
4027+
expect![[r#"
4028+
```rust
4029+
&[i32; 5]
4030+
```"#]],
4031+
);
4032+
}
4033+
4034+
#[test]
4035+
fn hover_range_shows_nothing_when_invalid() {
4036+
check_hover_range_no_results(
4037+
r#"
4038+
fn f<T>(a: &[T]) { }
4039+
fn b()$0 { f(&[1, 2, 3, 4, 5]); }$0
4040+
"#,
4041+
);
4042+
4043+
check_hover_range_no_results(
4044+
r#"
4045+
fn f<T>$0(a: &[T]) { }
4046+
fn b() { f(&[1, 2, 3,$0 4, 5]); }
4047+
"#,
4048+
);
4049+
4050+
check_hover_range_no_results(
4051+
r#"
4052+
fn $0f() { let expr = [1, 2, 3, 4]$0 }
4053+
"#,
4054+
);
4055+
}
4056+
4057+
#[test]
4058+
fn hover_range_shows_unit_for_statements() {
4059+
check_hover_range(
4060+
r#"
4061+
fn f<T>(a: &[T]) { }
4062+
fn b() { $0f(&[1, 2, 3, 4, 5]); }$0
4063+
"#,
4064+
expect![[r#"
4065+
```rust
4066+
()
4067+
```"#]],
4068+
);
4069+
4070+
check_hover_range(
4071+
r#"
4072+
fn f() { let expr$0 = $0[1, 2, 3, 4] }
4073+
"#,
4074+
expect![[r#"
4075+
```rust
4076+
()
4077+
```"#]],
4078+
);
4079+
}
38854080
}

crates/ide/src/lib.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -418,9 +418,9 @@ impl Analysis {
418418
pub fn hover(
419419
&self,
420420
config: &HoverConfig,
421-
position: FilePosition,
421+
range: FileRange,
422422
) -> Cancellable<Option<RangeInfo<HoverResult>>> {
423-
self.with_db(|db| hover::hover(db, position, config))
423+
self.with_db(|db| hover::hover(db, range, config))
424424
}
425425

426426
/// Return URL(s) for the documentation of the symbol under the cursor.

crates/rust-analyzer/src/caps.rs

+1
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ pub fn server_capabilities(config: &Config) -> ServerCapabilities {
118118
"ssr": true,
119119
"onEnter": true,
120120
"parentModule": true,
121+
"hoverRange": true,
121122
"runnables": {
122123
"kinds": [ "cargo" ],
123124
},

crates/rust-analyzer/src/handlers.rs

+14-5
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,10 @@ use crate::{
3636
from_proto,
3737
global_state::{GlobalState, GlobalStateSnapshot},
3838
line_index::LineEndings,
39-
lsp_ext::{self, InlayHint, InlayHintsParams, ViewCrateGraphParams, WorkspaceSymbolParams},
39+
lsp_ext::{
40+
self, InlayHint, InlayHintsParams, PositionOrRange, ViewCrateGraphParams,
41+
WorkspaceSymbolParams,
42+
},
4043
lsp_utils::all_edits_are_disjoint,
4144
to_proto, LspError, Result,
4245
};
@@ -867,15 +870,21 @@ pub(crate) fn handle_signature_help(
867870

868871
pub(crate) fn handle_hover(
869872
snap: GlobalStateSnapshot,
870-
params: lsp_types::HoverParams,
873+
params: lsp_ext::HoverParams,
871874
) -> Result<Option<lsp_ext::Hover>> {
872875
let _p = profile::span("handle_hover");
873-
let position = from_proto::file_position(&snap, params.text_document_position_params)?;
874-
let info = match snap.analysis.hover(&snap.config.hover(), position)? {
876+
let range = match params.position {
877+
PositionOrRange::Position(position) => Range::new(position, position),
878+
PositionOrRange::Range(range) => range,
879+
};
880+
881+
let file_range = from_proto::file_range(&snap, params.text_document, range)?;
882+
let info = match snap.analysis.hover(&snap.config.hover(), file_range)? {
875883
None => return Ok(None),
876884
Some(info) => info,
877885
};
878-
let line_index = snap.file_line_index(position.file_id)?;
886+
887+
let line_index = snap.file_line_index(file_range.file_id)?;
879888
let range = to_proto::range(&line_index, info.range);
880889
let hover = lsp_ext::Hover {
881890
hover: lsp_types::Hover {

0 commit comments

Comments
 (0)