mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-09-27 12:29:21 +00:00
Split out some hover functions
This commit is contained in:
parent
c5b1e3f2ae
commit
a542bd46bf
3 changed files with 237 additions and 87 deletions
|
@ -86,30 +86,38 @@ pub struct HoverResult {
|
||||||
// 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[]
|
||||||
pub(crate) fn hover(
|
pub(crate) fn hover(
|
||||||
db: &RootDatabase,
|
db: &RootDatabase,
|
||||||
file_range: FileRange,
|
frange @ FileRange { file_id, range }: FileRange,
|
||||||
config: &HoverConfig,
|
config: &HoverConfig,
|
||||||
) -> Option<RangeInfo<HoverResult>> {
|
) -> Option<RangeInfo<HoverResult>> {
|
||||||
let sema = &hir::Semantics::new(db);
|
let sema = &hir::Semantics::new(db);
|
||||||
let mut res = hover_impl(sema, file_range, config)?;
|
let file = sema.parse(file_id).syntax().clone();
|
||||||
|
let mut res = if range.is_empty() {
|
||||||
|
hover_simple(sema, FilePosition { file_id, offset: range.start() }, file, config)
|
||||||
|
} else {
|
||||||
|
hover_ranged(sema, frange, file, config)
|
||||||
|
}?;
|
||||||
|
|
||||||
if let HoverDocFormat::PlainText = config.format {
|
if let HoverDocFormat::PlainText = config.format {
|
||||||
res.info.markup = remove_markdown(res.info.markup.as_str()).into();
|
res.info.markup = remove_markdown(res.info.markup.as_str()).into();
|
||||||
}
|
}
|
||||||
Some(res)
|
Some(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hover_impl(
|
fn hover_simple(
|
||||||
sema: &Semantics<'_, RootDatabase>,
|
sema: &Semantics<'_, RootDatabase>,
|
||||||
FileRange { file_id, range }: FileRange,
|
FilePosition { file_id, offset }: FilePosition,
|
||||||
|
file: SyntaxNode,
|
||||||
config: &HoverConfig,
|
config: &HoverConfig,
|
||||||
) -> Option<RangeInfo<HoverResult>> {
|
) -> Option<RangeInfo<HoverResult>> {
|
||||||
let file = sema.parse(file_id).syntax().clone();
|
|
||||||
if !range.is_empty() {
|
|
||||||
return hover_ranged(&file, range, sema, config);
|
|
||||||
}
|
|
||||||
let offset = range.start();
|
|
||||||
|
|
||||||
let original_token = pick_best_token(file.token_at_offset(offset), |kind| match kind {
|
let original_token = pick_best_token(file.token_at_offset(offset), |kind| match kind {
|
||||||
IDENT | INT_NUMBER | LIFETIME_IDENT | T![self] | T![super] | T![crate] | T![Self] => 4,
|
IDENT
|
||||||
|
| INT_NUMBER
|
||||||
|
| LIFETIME_IDENT
|
||||||
|
| T![self]
|
||||||
|
| T![super]
|
||||||
|
| T![crate]
|
||||||
|
| T![Self]
|
||||||
|
| T![_] => 4,
|
||||||
// index and prefix ops
|
// index and prefix ops
|
||||||
T!['['] | T![']'] | T![?] | T![*] | T![-] | T![!] => 3,
|
T!['['] | T![']'] | T![?] | T![*] | T![-] | T![!] => 3,
|
||||||
kind if kind.is_keyword() => 2,
|
kind if kind.is_keyword() => 2,
|
||||||
|
@ -142,19 +150,18 @@ fn hover_impl(
|
||||||
} else {
|
} else {
|
||||||
sema.descend_into_macros_with_same_text(original_token.clone())
|
sema.descend_into_macros_with_same_text(original_token.clone())
|
||||||
};
|
};
|
||||||
|
let descended = || descended.iter();
|
||||||
|
|
||||||
|
let result = descended()
|
||||||
// try lint hover
|
// try lint hover
|
||||||
let result = descended
|
|
||||||
.iter()
|
|
||||||
.find_map(|token| {
|
.find_map(|token| {
|
||||||
// FIXME: Definition should include known lints and the like instead of having this special case here
|
// FIXME: Definition should include known lints and the like instead of having this special case here
|
||||||
let attr = token.parent_ancestors().find_map(ast::Attr::cast)?;
|
let attr = token.parent_ancestors().find_map(ast::Attr::cast)?;
|
||||||
render::try_for_lint(&attr, token)
|
render::try_for_lint(&attr, token)
|
||||||
})
|
})
|
||||||
// try item definitions
|
// try definitions
|
||||||
.or_else(|| {
|
.or_else(|| {
|
||||||
descended
|
descended()
|
||||||
.iter()
|
|
||||||
.filter_map(|token| {
|
.filter_map(|token| {
|
||||||
let node = token.parent()?;
|
let node = token.parent()?;
|
||||||
let class = IdentClass::classify_token(sema, token)?;
|
let class = IdentClass::classify_token(sema, token)?;
|
||||||
|
@ -175,10 +182,12 @@ fn hover_impl(
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
// try keywords
|
// try keywords
|
||||||
.or_else(|| descended.iter().find_map(|token| render::keyword(sema, config, token)))
|
.or_else(|| descended().find_map(|token| render::keyword(sema, config, token)))
|
||||||
// try rest item hover
|
// try _ hovers
|
||||||
|
.or_else(|| descended().find_map(|token| render::underscore(sema, config, token)))
|
||||||
|
// try rest pattern hover
|
||||||
.or_else(|| {
|
.or_else(|| {
|
||||||
descended.iter().find_map(|token| {
|
descended().find_map(|token| {
|
||||||
if token.kind() != DOT2 {
|
if token.kind() != DOT2 {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
@ -201,8 +210,34 @@ fn hover_impl(
|
||||||
})
|
})
|
||||||
// fallback to type hover if there aren't any other suggestions
|
// fallback to type hover if there aren't any other suggestions
|
||||||
// this finds its own range instead of using the closest token's range
|
// this finds its own range instead of using the closest token's range
|
||||||
.or_else(|| {
|
.or_else(|| descended().find_map(|token| hover_type_fallback(sema, config, token, token)))
|
||||||
descended.iter().find_map(|token| hover_type_fallback(sema, config, token, token))
|
}
|
||||||
|
|
||||||
|
fn hover_ranged(
|
||||||
|
sema: &Semantics<'_, RootDatabase>,
|
||||||
|
FileRange { range, .. }: FileRange,
|
||||||
|
file: SyntaxNode,
|
||||||
|
config: &HoverConfig,
|
||||||
|
) -> Option<RangeInfo<HoverResult>> {
|
||||||
|
// FIXME: make this work in attributes
|
||||||
|
let expr_or_pat =
|
||||||
|
file.covering_element(range).ancestors().find_map(Either::<ast::Expr, ast::Pat>::cast)?;
|
||||||
|
let res = match &expr_or_pat {
|
||||||
|
Either::Left(ast::Expr::TryExpr(try_expr)) => render::try_expr(sema, config, try_expr),
|
||||||
|
Either::Left(ast::Expr::PrefixExpr(prefix_expr))
|
||||||
|
if prefix_expr.op_kind() == Some(ast::UnaryOp::Deref) =>
|
||||||
|
{
|
||||||
|
render::deref_expr(sema, config, prefix_expr)
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
let res = res.or_else(|| render::type_info_of(sema, config, &expr_or_pat));
|
||||||
|
res.map(|it| {
|
||||||
|
let range = match expr_or_pat {
|
||||||
|
Either::Left(it) => it.syntax().text_range(),
|
||||||
|
Either::Right(it) => it.syntax().text_range(),
|
||||||
|
};
|
||||||
|
RangeInfo::new(range, it)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,44 +255,19 @@ pub(crate) fn hover_for_definition(
|
||||||
render::definition(sema.db, definition, famous_defs.as_ref(), config).map(|markup| {
|
render::definition(sema.db, definition, famous_defs.as_ref(), config).map(|markup| {
|
||||||
HoverResult {
|
HoverResult {
|
||||||
markup: render::process_markup(sema.db, definition, &markup, config),
|
markup: render::process_markup(sema.db, definition, &markup, config),
|
||||||
actions: show_implementations_action(sema.db, definition)
|
actions: [
|
||||||
|
show_implementations_action(sema.db, definition),
|
||||||
|
show_fn_references_action(sema.db, definition),
|
||||||
|
runnable_action(sema, definition, file_id),
|
||||||
|
goto_type_action_for_def(sema.db, definition),
|
||||||
|
]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.chain(show_fn_references_action(sema.db, definition))
|
.flatten()
|
||||||
.chain(runnable_action(sema, definition, file_id))
|
|
||||||
.chain(goto_type_action_for_def(sema.db, definition))
|
|
||||||
.collect(),
|
.collect(),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hover_ranged(
|
|
||||||
file: &SyntaxNode,
|
|
||||||
range: syntax::TextRange,
|
|
||||||
sema: &Semantics<'_, RootDatabase>,
|
|
||||||
config: &HoverConfig,
|
|
||||||
) -> Option<RangeInfo<HoverResult>> {
|
|
||||||
// FIXME: make this work in attributes
|
|
||||||
let expr_or_pat =
|
|
||||||
file.covering_element(range).ancestors().find_map(Either::<ast::Expr, ast::Pat>::cast)?;
|
|
||||||
let res = match &expr_or_pat {
|
|
||||||
Either::Left(ast::Expr::TryExpr(try_expr)) => render::try_expr(sema, config, try_expr),
|
|
||||||
Either::Left(ast::Expr::PrefixExpr(prefix_expr))
|
|
||||||
if prefix_expr.op_kind() == Some(ast::UnaryOp::Deref) =>
|
|
||||||
{
|
|
||||||
render::deref_expr(sema, config, prefix_expr)
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
let res = res.or_else(|| render::type_info(sema, config, &expr_or_pat));
|
|
||||||
res.map(|it| {
|
|
||||||
let range = match expr_or_pat {
|
|
||||||
Either::Left(it) => it.syntax().text_range(),
|
|
||||||
Either::Right(it) => it.syntax().text_range(),
|
|
||||||
};
|
|
||||||
RangeInfo::new(range, it)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn hover_type_fallback(
|
fn hover_type_fallback(
|
||||||
sema: &Semantics<'_, RootDatabase>,
|
sema: &Semantics<'_, RootDatabase>,
|
||||||
config: &HoverConfig,
|
config: &HoverConfig,
|
||||||
|
@ -282,7 +292,7 @@ fn hover_type_fallback(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let res = render::type_info(sema, config, &expr_or_pat)?;
|
let res = render::type_info_of(sema, config, &expr_or_pat)?;
|
||||||
|
|
||||||
let range = sema
|
let range = sema
|
||||||
.original_range_opt(&node)
|
.original_range_opt(&node)
|
||||||
|
|
|
@ -29,7 +29,7 @@ use crate::{
|
||||||
HoverAction, HoverConfig, HoverResult, Markup,
|
HoverAction, HoverConfig, HoverResult, Markup,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub(super) fn type_info(
|
pub(super) fn type_info_of(
|
||||||
sema: &Semantics<'_, RootDatabase>,
|
sema: &Semantics<'_, RootDatabase>,
|
||||||
_config: &HoverConfig,
|
_config: &HoverConfig,
|
||||||
expr_or_pat: &Either<ast::Expr, ast::Pat>,
|
expr_or_pat: &Either<ast::Expr, ast::Pat>,
|
||||||
|
@ -38,34 +38,7 @@ pub(super) fn type_info(
|
||||||
Either::Left(expr) => sema.type_of_expr(expr)?,
|
Either::Left(expr) => sema.type_of_expr(expr)?,
|
||||||
Either::Right(pat) => sema.type_of_pat(pat)?,
|
Either::Right(pat) => sema.type_of_pat(pat)?,
|
||||||
};
|
};
|
||||||
|
type_info(sema, _config, original, adjusted)
|
||||||
let mut res = HoverResult::default();
|
|
||||||
let mut targets: Vec<hir::ModuleDef> = Vec::new();
|
|
||||||
let mut push_new_def = |item: hir::ModuleDef| {
|
|
||||||
if !targets.contains(&item) {
|
|
||||||
targets.push(item);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
walk_and_push_ty(sema.db, &original, &mut push_new_def);
|
|
||||||
|
|
||||||
res.markup = if let Some(adjusted_ty) = adjusted {
|
|
||||||
walk_and_push_ty(sema.db, &adjusted_ty, &mut push_new_def);
|
|
||||||
let original = original.display(sema.db).to_string();
|
|
||||||
let adjusted = adjusted_ty.display(sema.db).to_string();
|
|
||||||
let static_text_diff_len = "Coerced to: ".len() - "Type: ".len();
|
|
||||||
format!(
|
|
||||||
"```text\nType: {:>apad$}\nCoerced to: {:>opad$}\n```\n",
|
|
||||||
original,
|
|
||||||
adjusted,
|
|
||||||
apad = static_text_diff_len + adjusted.len().max(original.len()),
|
|
||||||
opad = original.len(),
|
|
||||||
)
|
|
||||||
.into()
|
|
||||||
} else {
|
|
||||||
Markup::fenced_block(&original.display(sema.db))
|
|
||||||
};
|
|
||||||
res.actions.push(HoverAction::goto_type_from_targets(sema.db, targets));
|
|
||||||
Some(res)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn try_expr(
|
pub(super) fn try_expr(
|
||||||
|
@ -217,6 +190,48 @@ pub(super) fn deref_expr(
|
||||||
Some(res)
|
Some(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) fn underscore(
|
||||||
|
sema: &Semantics<'_, RootDatabase>,
|
||||||
|
config: &HoverConfig,
|
||||||
|
token: &SyntaxToken,
|
||||||
|
) -> Option<HoverResult> {
|
||||||
|
if token.kind() != T![_] {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let parent = token.parent()?;
|
||||||
|
let _it = match_ast! {
|
||||||
|
match parent {
|
||||||
|
ast::InferType(it) => it,
|
||||||
|
ast::UnderscoreExpr(it) => return type_info_of(sema, config, &Either::Left(ast::Expr::UnderscoreExpr(it))),
|
||||||
|
ast::WildcardPat(it) => return type_info_of(sema, config, &Either::Right(ast::Pat::WildcardPat(it))),
|
||||||
|
_ => return None,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// let it = infer_type.syntax().parent()?;
|
||||||
|
// match_ast! {
|
||||||
|
// match it {
|
||||||
|
// ast::LetStmt(_it) => (),
|
||||||
|
// ast::Param(_it) => (),
|
||||||
|
// ast::RetType(_it) => (),
|
||||||
|
// ast::TypeArg(_it) => (),
|
||||||
|
|
||||||
|
// ast::CastExpr(_it) => (),
|
||||||
|
// ast::ParenType(_it) => (),
|
||||||
|
// ast::TupleType(_it) => (),
|
||||||
|
// ast::PtrType(_it) => (),
|
||||||
|
// ast::RefType(_it) => (),
|
||||||
|
// ast::ArrayType(_it) => (),
|
||||||
|
// ast::SliceType(_it) => (),
|
||||||
|
// ast::ForType(_it) => (),
|
||||||
|
// _ => return None,
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// FIXME: https://github.com/rust-lang/rust-analyzer/issues/11762, this currently always returns Unknown
|
||||||
|
// type_info(sema, config, sema.resolve_type(&ast::Type::InferType(it))?, None)
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) fn keyword(
|
pub(super) fn keyword(
|
||||||
sema: &Semantics<'_, RootDatabase>,
|
sema: &Semantics<'_, RootDatabase>,
|
||||||
config: &HoverConfig,
|
config: &HoverConfig,
|
||||||
|
@ -458,6 +473,41 @@ pub(super) fn definition(
|
||||||
markup(docs, label, mod_path)
|
markup(docs, label, mod_path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn type_info(
|
||||||
|
sema: &Semantics<'_, RootDatabase>,
|
||||||
|
_config: &HoverConfig,
|
||||||
|
original: hir::Type,
|
||||||
|
adjusted: Option<hir::Type>,
|
||||||
|
) -> Option<HoverResult> {
|
||||||
|
let mut res = HoverResult::default();
|
||||||
|
let mut targets: Vec<hir::ModuleDef> = Vec::new();
|
||||||
|
let mut push_new_def = |item: hir::ModuleDef| {
|
||||||
|
if !targets.contains(&item) {
|
||||||
|
targets.push(item);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
walk_and_push_ty(sema.db, &original, &mut push_new_def);
|
||||||
|
|
||||||
|
res.markup = if let Some(adjusted_ty) = adjusted {
|
||||||
|
walk_and_push_ty(sema.db, &adjusted_ty, &mut push_new_def);
|
||||||
|
let original = original.display(sema.db).to_string();
|
||||||
|
let adjusted = adjusted_ty.display(sema.db).to_string();
|
||||||
|
let static_text_diff_len = "Coerced to: ".len() - "Type: ".len();
|
||||||
|
format!(
|
||||||
|
"```text\nType: {:>apad$}\nCoerced to: {:>opad$}\n```\n",
|
||||||
|
original,
|
||||||
|
adjusted,
|
||||||
|
apad = static_text_diff_len + adjusted.len().max(original.len()),
|
||||||
|
opad = original.len(),
|
||||||
|
)
|
||||||
|
.into()
|
||||||
|
} else {
|
||||||
|
Markup::fenced_block(&original.display(sema.db))
|
||||||
|
};
|
||||||
|
res.actions.push(HoverAction::goto_type_from_targets(sema.db, targets));
|
||||||
|
Some(res)
|
||||||
|
}
|
||||||
|
|
||||||
fn render_builtin_attr(db: &RootDatabase, attr: hir::BuiltinAttr) -> Option<Markup> {
|
fn render_builtin_attr(db: &RootDatabase, attr: hir::BuiltinAttr) -> Option<Markup> {
|
||||||
let name = attr.name(db);
|
let name = attr.name(db);
|
||||||
let desc = format!("#[{name}]");
|
let desc = format!("#[{name}]");
|
||||||
|
|
|
@ -5592,3 +5592,93 @@ fn main() {
|
||||||
"#]],
|
"#]],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hover_underscore_pat() {
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
fn main() {
|
||||||
|
let _$0 = 0;
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
*_*
|
||||||
|
```rust
|
||||||
|
i32
|
||||||
|
```
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
fn main() {
|
||||||
|
let (_$0,) = (0,);
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
*_*
|
||||||
|
```rust
|
||||||
|
i32
|
||||||
|
```
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hover_underscore_expr() {
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
fn main() {
|
||||||
|
_$0 = 0;
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
*_*
|
||||||
|
```rust
|
||||||
|
i32
|
||||||
|
```
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
fn main() {
|
||||||
|
(_$0,) = (0,);
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
*_*
|
||||||
|
```rust
|
||||||
|
i32
|
||||||
|
```
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hover_underscore_type() {
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
fn main() {
|
||||||
|
let x: _$0 = 0;
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
*_*
|
||||||
|
```rust
|
||||||
|
{unknown}
|
||||||
|
```
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
fn main() {
|
||||||
|
let x: (_$0,) = (0,);
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
*_*
|
||||||
|
```rust
|
||||||
|
{unknown}
|
||||||
|
```
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue