Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add the Hover Range capability which enables showing the type of an expression #9693

Merged
merged 19 commits into from
Jul 28, 2021
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/hir_def/src/test_db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ impl TestDB {
events
.into_iter()
.filter_map(|e| match e.kind {
// This pretty horrible, but `Debug` is the only way to inspect
// This is pretty horrible, but `Debug` is the only way to inspect
// QueryDescriptor at the moment.
salsa::EventKind::WillExecute { database_key } => {
Some(format!("{:?}", database_key.debug(self)))
Expand Down
2 changes: 1 addition & 1 deletion crates/hir_ty/src/test_db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ impl TestDB {
events
.into_iter()
.filter_map(|e| match e.kind {
// This pretty horrible, but `Debug` is the only way to inspect
// This is pretty horrible, but `Debug` is the only way to inspect
// QueryDescriptor at the moment.
salsa::EventKind::WillExecute { database_key } => {
Some(format!("{:?}", database_key.debug(self)))
Expand Down
225 changes: 210 additions & 15 deletions crates/ide/src/hover.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use either::Either;
use hir::{AsAssocItem, HasAttrs, HasSource, HirDisplay, Semantics};
use ide_db::{
base_db::SourceDatabase,
base_db::{FileRange, SourceDatabase},
defs::{Definition, NameClass, NameRefClass},
helpers::{
generated_lints::{CLIPPY_LINTS, DEFAULT_LINTS, FEATURES},
Expand All @@ -12,8 +12,12 @@ use ide_db::{
use itertools::Itertools;
use stdx::format_to;
use syntax::{
algo, ast, display::fn_as_proc_macro_label, match_ast, AstNode, AstToken, Direction,
SyntaxKind::*, SyntaxToken, T,
algo::{self, find_node_at_range},
ast,
display::fn_as_proc_macro_label,
match_ast, AstNode, AstToken, Direction,
SyntaxKind::*,
SyntaxToken, T,
};

use crate::{
Expand Down Expand Up @@ -69,17 +73,39 @@ pub struct HoverResult {

// Feature: Hover
//
// Shows additional information, like type of an expression or documentation for definition when "focusing" code.
// Shows additional information, like the type of an expression or the documentation for a definition when "focusing" code.
// Focusing is usually hovering with a mouse, but can also be triggered with a shortcut.
//
// image::https://user-images.githubusercontent.com/48062697/113020658-b5f98b80-917a-11eb-9f88-3dbc27320c95.gif[]
// image::https://user-images.githubusercontent.com/48062697/113020658-b5f98b80-917a-11eb-9f88-3dbc27320c95.gif
lnicola marked this conversation as resolved.
Show resolved Hide resolved
pub(crate) fn hover(
db: &RootDatabase,
position: FilePosition,
range: FileRange,
config: &HoverConfig,
) -> Option<RangeInfo<HoverResult>> {
let sema = hir::Semantics::new(db);
let file = sema.parse(position.file_id).syntax().clone();
let file = sema.parse(range.file_id).syntax().clone();

// This means we're hovering over a range.
if !range.range.is_empty() {
let expr = find_node_at_range::<ast::Expr>(&file, range.range)?;
let ty = sema.type_of_expr(&expr)?;

if ty.is_unknown() {
return None;
}

let mut res = HoverResult::default();

res.markup = if config.markdown() {
Markup::fenced_block(&ty.display(db))
} else {
ty.display(db).to_string().into()
};

return Some(RangeInfo::new(range.range, res));
}

let position = FilePosition { file_id: range.file_id, offset: range.range.start() };
let token = pick_best_token(file.token_at_offset(position.offset), |kind| match kind {
IDENT | INT_NUMBER | LIFETIME_IDENT | T![self] | T![super] | T![crate] => 3,
T!['('] | T![')'] => 2,
Expand All @@ -94,8 +120,8 @@ pub(crate) fn hover(
let mut range = None;
let definition = match_ast! {
match node {
// we don't use NameClass::referenced_or_defined here as we do not want to resolve
// field pattern shorthands to their definition
// We don't use NameClass::referenced_or_defined here as we do not want to resolve
// field pattern shorthands to their definition.
ast::Name(name) => NameClass::classify(&sema, &name).map(|class| match class {
NameClass::Definition(it) | NameClass::ConstReference(it) => it,
NameClass::PatFieldShorthand { local_def, field_ref: _ } => Definition::Local(local_def),
Expand Down Expand Up @@ -193,6 +219,7 @@ pub(crate) fn hover(
} else {
ty.display(db).to_string().into()
};

let range = sema.original_range(&node).range;
Some(RangeInfo::new(range, res))
}
Expand Down Expand Up @@ -530,7 +557,8 @@ fn find_std_module(famous_defs: &FamousDefs, name: &str) -> Option<hir::Module>
#[cfg(test)]
mod tests {
use expect_test::{expect, Expect};
use ide_db::base_db::FileLoader;
use ide_db::base_db::{FileLoader, FileRange};
use syntax::TextRange;

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

Expand All @@ -542,7 +570,7 @@ mod tests {
links_in_hover: true,
documentation: Some(HoverDocFormat::Markdown),
},
position,
FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) },
)
.unwrap();
assert!(hover.is_none());
Expand All @@ -556,7 +584,7 @@ mod tests {
links_in_hover: true,
documentation: Some(HoverDocFormat::Markdown),
},
position,
FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) },
)
.unwrap()
.unwrap();
Expand All @@ -576,7 +604,7 @@ mod tests {
links_in_hover: false,
documentation: Some(HoverDocFormat::Markdown),
},
position,
FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) },
)
.unwrap()
.unwrap();
Expand All @@ -596,7 +624,7 @@ mod tests {
links_in_hover: true,
documentation: Some(HoverDocFormat::PlainText),
},
position,
FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) },
)
.unwrap()
.unwrap();
Expand All @@ -616,13 +644,42 @@ mod tests {
links_in_hover: true,
documentation: Some(HoverDocFormat::Markdown),
},
position,
FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) },
)
.unwrap()
.unwrap();
expect.assert_debug_eq(&hover.info.actions)
}

fn check_hover_range(ra_fixture: &str, expect: Expect) {
let (analysis, range) = fixture::range(ra_fixture);
let hover = analysis
.hover(
&HoverConfig {
links_in_hover: false,
documentation: Some(HoverDocFormat::Markdown),
},
range,
)
.unwrap()
.unwrap();
expect.assert_eq(hover.info.markup.as_str())
}

fn check_hover_range_no_results(ra_fixture: &str) {
let (analysis, range) = fixture::range(ra_fixture);
let hover = analysis
.hover(
&HoverConfig {
links_in_hover: false,
documentation: Some(HoverDocFormat::Markdown),
},
range,
)
.unwrap();
assert!(hover.is_none());
}

#[test]
fn hover_shows_type_of_an_expression() {
check(
Expand Down Expand Up @@ -3882,4 +3939,142 @@ struct Foo;
"#]],
);
}

#[test]
fn hover_range_math() {
check_hover_range(
r#"
fn f() { let expr = $01 + 2 * 3$0 }
"#,
expect![[r#"
```rust
i32
```"#]],
);

check_hover_range(
r#"
fn f() { let expr = 1 $0+ 2 * $03 }
"#,
expect![[r#"
```rust
i32
```"#]],
);

check_hover_range(
r#"
fn f() { let expr = 1 + $02 * 3$0 }
"#,
expect![[r#"
```rust
i32
```"#]],
);
}

#[test]
fn hover_range_arrays() {
check_hover_range(
r#"
fn f() { let expr = $0[1, 2, 3, 4]$0 }
"#,
expect![[r#"
```rust
[i32; 4]
```"#]],
);

check_hover_range(
r#"
fn f() { let expr = [1, 2, $03, 4]$0 }
"#,
expect![[r#"
```rust
[i32; 4]
```"#]],
);

check_hover_range(
r#"
fn f() { let expr = [1, 2, $03$0, 4] }
"#,
expect![[r#"
```rust
i32
```"#]],
);
}

#[test]
fn hover_range_functions() {
check_hover_range(
r#"
fn f<T>(a: &[T]) { }
fn b() { $0f$0(&[1, 2, 3, 4, 5]); }
"#,
expect![[r#"
```rust
fn f<i32>(&[i32])
```"#]],
);

check_hover_range(
r#"
fn f<T>(a: &[T]) { }
fn b() { f($0&[1, 2, 3, 4, 5]$0); }
"#,
expect![[r#"
```rust
&[i32; 5]
```"#]],
);
}

#[test]
fn hover_range_shows_nothing_when_invalid() {
check_hover_range_no_results(
r#"
fn f<T>(a: &[T]) { }
fn b()$0 { f(&[1, 2, 3, 4, 5]); }$0
"#,
);

check_hover_range_no_results(
r#"
fn f<T>$0(a: &[T]) { }
fn b() { f(&[1, 2, 3,$0 4, 5]); }
"#,
);

check_hover_range_no_results(
r#"
fn $0f() { let expr = [1, 2, 3, 4]$0 }
"#,
);
}

#[test]
fn hover_range_shows_unit_for_statements() {
check_hover_range(
r#"
fn f<T>(a: &[T]) { }
fn b() { $0f(&[1, 2, 3, 4, 5]); }$0
"#,
expect![[r#"
```rust
()
```"#]],
);

check_hover_range(
r#"
fn f() { let expr$0 = $0[1, 2, 3, 4] }
"#,
expect![[r#"
```rust
()
```"#]],
);
}
}
4 changes: 2 additions & 2 deletions crates/ide/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -418,9 +418,9 @@ impl Analysis {
pub fn hover(
&self,
config: &HoverConfig,
position: FilePosition,
range: FileRange,
) -> Cancellable<Option<RangeInfo<HoverResult>>> {
self.with_db(|db| hover::hover(db, position, config))
self.with_db(|db| hover::hover(db, range, config))
}

/// Return URL(s) for the documentation of the symbol under the cursor.
Expand Down
1 change: 1 addition & 0 deletions crates/rust-analyzer/src/caps.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ pub fn server_capabilities(config: &Config) -> ServerCapabilities {
"ssr": true,
"onEnter": true,
"parentModule": true,
"hoverRange": true,
"runnables": {
"kinds": [ "cargo" ],
},
Expand Down
19 changes: 14 additions & 5 deletions crates/rust-analyzer/src/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@ use crate::{
from_proto,
global_state::{GlobalState, GlobalStateSnapshot},
line_index::LineEndings,
lsp_ext::{self, InlayHint, InlayHintsParams, ViewCrateGraphParams, WorkspaceSymbolParams},
lsp_ext::{
self, InlayHint, InlayHintsParams, PositionOrRange, ViewCrateGraphParams,
WorkspaceSymbolParams,
},
lsp_utils::all_edits_are_disjoint,
to_proto, LspError, Result,
};
Expand Down Expand Up @@ -867,15 +870,21 @@ pub(crate) fn handle_signature_help(

pub(crate) fn handle_hover(
snap: GlobalStateSnapshot,
params: lsp_types::HoverParams,
params: lsp_ext::HoverParams,
) -> Result<Option<lsp_ext::Hover>> {
let _p = profile::span("handle_hover");
let position = from_proto::file_position(&snap, params.text_document_position_params)?;
let info = match snap.analysis.hover(&snap.config.hover(), position)? {
let range = match params.position {
PositionOrRange::Position(position) => Range::new(position, position),
PositionOrRange::Range(range) => range,
};

let file_range = from_proto::file_range(&snap, params.text_document, range)?;
let info = match snap.analysis.hover(&snap.config.hover(), file_range)? {
None => return Ok(None),
Some(info) => info,
};
let line_index = snap.file_line_index(position.file_id)?;

let line_index = snap.file_line_index(file_range.file_id)?;
let range = to_proto::range(&line_index, info.range);
let hover = lsp_ext::Hover {
hover: lsp_types::Hover {
Expand Down
Loading