mirror of
https://github.com/rust-lang/rust-analyzer
synced 2025-01-13 13:48:50 +00:00
Merge #7795
7795: Show docs on hover for keywords and primitives r=matklad a=Veykril ![lAWFadkziX](https://user-images.githubusercontent.com/3757771/109369534-eeb4f500-789c-11eb-8f2b-2f9c4e129de3.gif) It's a bit annoying that this requires the `SyntaxNode` and `Semantics` to be pulled through `hover_for_definition` just so we can get the `std` crate but I couldn't think of a better way. Co-authored-by: Lukas Wirth <lukastw97@gmail.com>
This commit is contained in:
commit
6a585c6ee2
4 changed files with 139 additions and 19 deletions
|
@ -33,11 +33,11 @@ mod has_source;
|
||||||
pub use crate::{
|
pub use crate::{
|
||||||
attrs::{HasAttrs, Namespace},
|
attrs::{HasAttrs, Namespace},
|
||||||
code_model::{
|
code_model::{
|
||||||
Access, Adt, AsAssocItem, AssocItem, AssocItemContainer, Callable, CallableKind, Const,
|
Access, Adt, AsAssocItem, AssocItem, AssocItemContainer, BuiltinType, Callable,
|
||||||
ConstParam, Crate, CrateDependency, DefWithBody, Enum, Field, FieldSource, Function,
|
CallableKind, Const, ConstParam, Crate, CrateDependency, DefWithBody, Enum, Field,
|
||||||
GenericDef, GenericParam, HasVisibility, Impl, Label, LifetimeParam, Local, MacroDef,
|
FieldSource, Function, GenericDef, GenericParam, HasVisibility, Impl, Label, LifetimeParam,
|
||||||
Module, ModuleDef, ScopeDef, Static, Struct, Trait, Type, TypeAlias, TypeParam, Union,
|
Local, MacroDef, Module, ModuleDef, ScopeDef, Static, Struct, Trait, Type, TypeAlias,
|
||||||
Variant, VariantDef,
|
TypeParam, Union, Variant, VariantDef,
|
||||||
},
|
},
|
||||||
has_source::HasSource,
|
has_source::HasSource,
|
||||||
semantics::{PathResolution, Semantics, SemanticsScope},
|
semantics::{PathResolution, Semantics, SemanticsScope},
|
||||||
|
@ -47,7 +47,6 @@ pub use hir_def::{
|
||||||
adt::StructKind,
|
adt::StructKind,
|
||||||
attr::{Attrs, Documentation},
|
attr::{Attrs, Documentation},
|
||||||
body::scope::ExprScopes,
|
body::scope::ExprScopes,
|
||||||
builtin_type::BuiltinType,
|
|
||||||
find_path::PrefixKind,
|
find_path::PrefixKind,
|
||||||
import_map,
|
import_map,
|
||||||
item_scope::ItemInNs,
|
item_scope::ItemInNs,
|
||||||
|
|
|
@ -5,6 +5,7 @@ use hir::{
|
||||||
use ide_db::{
|
use ide_db::{
|
||||||
base_db::SourceDatabase,
|
base_db::SourceDatabase,
|
||||||
defs::{Definition, NameClass, NameRefClass},
|
defs::{Definition, NameClass, NameRefClass},
|
||||||
|
helpers::FamousDefs,
|
||||||
RootDatabase,
|
RootDatabase,
|
||||||
};
|
};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
@ -107,16 +108,14 @@ pub(crate) fn hover(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if let Some(definition) = definition {
|
if let Some(definition) = definition {
|
||||||
if let Some(markup) = hover_for_definition(db, definition) {
|
let famous_defs = match &definition {
|
||||||
let markup = markup.as_str();
|
Definition::ModuleDef(ModuleDef::BuiltinType(_)) => {
|
||||||
let markup = if !markdown {
|
Some(FamousDefs(&sema, sema.scope(&node).krate()))
|
||||||
remove_markdown(markup)
|
}
|
||||||
} else if links_in_hover {
|
_ => None,
|
||||||
rewrite_links(db, markup, &definition)
|
};
|
||||||
} else {
|
if let Some(markup) = hover_for_definition(db, definition, famous_defs.as_ref()) {
|
||||||
remove_links(markup)
|
res.markup = process_markup(sema.db, definition, &markup, links_in_hover, markdown);
|
||||||
};
|
|
||||||
res.markup = Markup::from(markup);
|
|
||||||
if let Some(action) = show_implementations_action(db, definition) {
|
if let Some(action) = show_implementations_action(db, definition) {
|
||||||
res.actions.push(action);
|
res.actions.push(action);
|
||||||
}
|
}
|
||||||
|
@ -138,6 +137,9 @@ pub(crate) fn hover(
|
||||||
// don't highlight the entire parent node on comment hover
|
// don't highlight the entire parent node on comment hover
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
if let res @ Some(_) = hover_for_keyword(&sema, links_in_hover, markdown, &token) {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
let node = token
|
let node = token
|
||||||
.ancestors()
|
.ancestors()
|
||||||
|
@ -272,6 +274,24 @@ fn hover_markup(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn process_markup(
|
||||||
|
db: &RootDatabase,
|
||||||
|
def: Definition,
|
||||||
|
markup: &Markup,
|
||||||
|
links_in_hover: bool,
|
||||||
|
markdown: bool,
|
||||||
|
) -> Markup {
|
||||||
|
let markup = markup.as_str();
|
||||||
|
let markup = if !markdown {
|
||||||
|
remove_markdown(markup)
|
||||||
|
} else if links_in_hover {
|
||||||
|
rewrite_links(db, markup, &def)
|
||||||
|
} else {
|
||||||
|
remove_links(markup)
|
||||||
|
};
|
||||||
|
Markup::from(markup)
|
||||||
|
}
|
||||||
|
|
||||||
fn definition_owner_name(db: &RootDatabase, def: &Definition) -> Option<String> {
|
fn definition_owner_name(db: &RootDatabase, def: &Definition) -> Option<String> {
|
||||||
match def {
|
match def {
|
||||||
Definition::Field(f) => Some(f.parent_def(db).name(db)),
|
Definition::Field(f) => Some(f.parent_def(db).name(db)),
|
||||||
|
@ -304,7 +324,11 @@ fn definition_mod_path(db: &RootDatabase, def: &Definition) -> Option<String> {
|
||||||
def.module(db).map(|module| render_path(db, module, definition_owner_name(db, def)))
|
def.module(db).map(|module| render_path(db, module, definition_owner_name(db, def)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hover_for_definition(db: &RootDatabase, def: Definition) -> Option<Markup> {
|
fn hover_for_definition(
|
||||||
|
db: &RootDatabase,
|
||||||
|
def: Definition,
|
||||||
|
famous_defs: Option<&FamousDefs>,
|
||||||
|
) -> Option<Markup> {
|
||||||
let mod_path = definition_mod_path(db, &def);
|
let mod_path = definition_mod_path(db, &def);
|
||||||
return match def {
|
return match def {
|
||||||
Definition::Macro(it) => {
|
Definition::Macro(it) => {
|
||||||
|
@ -339,7 +363,9 @@ fn hover_for_definition(db: &RootDatabase, def: Definition) -> Option<Markup> {
|
||||||
ModuleDef::Static(it) => from_def_source(db, it, mod_path),
|
ModuleDef::Static(it) => from_def_source(db, it, mod_path),
|
||||||
ModuleDef::Trait(it) => from_def_source(db, it, mod_path),
|
ModuleDef::Trait(it) => from_def_source(db, it, mod_path),
|
||||||
ModuleDef::TypeAlias(it) => from_def_source(db, it, mod_path),
|
ModuleDef::TypeAlias(it) => from_def_source(db, it, mod_path),
|
||||||
ModuleDef::BuiltinType(it) => Some(Markup::fenced_block(&it.name())),
|
ModuleDef::BuiltinType(it) => famous_defs
|
||||||
|
.and_then(|fd| hover_for_builtin(fd, it))
|
||||||
|
.or_else(|| Some(Markup::fenced_block(&it.name()))),
|
||||||
},
|
},
|
||||||
Definition::Local(it) => Some(Markup::fenced_block(&it.ty(db).display(db))),
|
Definition::Local(it) => Some(Markup::fenced_block(&it.ty(db).display(db))),
|
||||||
Definition::SelfType(impl_def) => {
|
Definition::SelfType(impl_def) => {
|
||||||
|
@ -380,11 +406,52 @@ fn hover_for_definition(db: &RootDatabase, def: Definition) -> Option<Markup> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn hover_for_keyword(
|
||||||
|
sema: &Semantics<RootDatabase>,
|
||||||
|
links_in_hover: bool,
|
||||||
|
markdown: bool,
|
||||||
|
token: &SyntaxToken,
|
||||||
|
) -> Option<RangeInfo<HoverResult>> {
|
||||||
|
if !token.kind().is_keyword() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let famous_defs = FamousDefs(&sema, sema.scope(&token.parent()).krate());
|
||||||
|
// std exposes {}_keyword modules with docstrings on the root to document keywords
|
||||||
|
let keyword_mod = format!("{}_keyword", token.text());
|
||||||
|
let doc_owner = find_std_module(&famous_defs, &keyword_mod)?;
|
||||||
|
let docs = doc_owner.attrs(sema.db).docs()?;
|
||||||
|
let markup = process_markup(
|
||||||
|
sema.db,
|
||||||
|
Definition::ModuleDef(doc_owner.into()),
|
||||||
|
&hover_markup(Some(docs.into()), Some(token.text().into()), None)?,
|
||||||
|
links_in_hover,
|
||||||
|
markdown,
|
||||||
|
);
|
||||||
|
Some(RangeInfo::new(token.text_range(), HoverResult { markup, actions: Default::default() }))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hover_for_builtin(famous_defs: &FamousDefs, builtin: hir::BuiltinType) -> Option<Markup> {
|
||||||
|
// std exposes prim_{} modules with docstrings on the root to document the builtins
|
||||||
|
let primitive_mod = format!("prim_{}", builtin.name());
|
||||||
|
let doc_owner = find_std_module(famous_defs, &primitive_mod)?;
|
||||||
|
let docs = doc_owner.attrs(famous_defs.0.db).docs()?;
|
||||||
|
hover_markup(Some(docs.into()), Some(builtin.name().to_string()), None)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_std_module(famous_defs: &FamousDefs, name: &str) -> Option<hir::Module> {
|
||||||
|
let db = famous_defs.0.db;
|
||||||
|
let std_crate = famous_defs.std()?;
|
||||||
|
let std_root_module = std_crate.root_module(db);
|
||||||
|
std_root_module
|
||||||
|
.children(db)
|
||||||
|
.find(|module| module.name(db).map_or(false, |module| module.to_string() == name))
|
||||||
|
}
|
||||||
|
|
||||||
fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> {
|
fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> {
|
||||||
return tokens.max_by_key(priority);
|
return tokens.max_by_key(priority);
|
||||||
fn priority(n: &SyntaxToken) -> usize {
|
fn priority(n: &SyntaxToken) -> usize {
|
||||||
match n.kind() {
|
match n.kind() {
|
||||||
IDENT | INT_NUMBER | LIFETIME_IDENT | T![self] => 3,
|
IDENT | INT_NUMBER | LIFETIME_IDENT | T![self] | T![super] | T![crate] => 3,
|
||||||
T!['('] | T![')'] => 2,
|
T!['('] | T![')'] => 2,
|
||||||
kind if kind.is_trivia() => 0,
|
kind if kind.is_trivia() => 0,
|
||||||
_ => 1,
|
_ => 1,
|
||||||
|
@ -3523,6 +3590,48 @@ use foo::bar::{self$0};
|
||||||
|
|
||||||
But this should appear
|
But this should appear
|
||||||
"#]],
|
"#]],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hover_keyword() {
|
||||||
|
let ra_fixture = r#"//- /main.rs crate:main deps:std
|
||||||
|
fn f() { retur$0n; }"#;
|
||||||
|
let fixture = format!("{}\n{}", ra_fixture, FamousDefs::FIXTURE);
|
||||||
|
check(
|
||||||
|
&fixture,
|
||||||
|
expect![[r#"
|
||||||
|
*return*
|
||||||
|
|
||||||
|
```rust
|
||||||
|
return
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Docs for return_keyword
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hover_builtin() {
|
||||||
|
let ra_fixture = r#"//- /main.rs crate:main deps:std
|
||||||
|
cosnt _: &str$0 = ""; }"#;
|
||||||
|
let fixture = format!("{}\n{}", ra_fixture, FamousDefs::FIXTURE);
|
||||||
|
check(
|
||||||
|
&fixture,
|
||||||
|
expect![[r#"
|
||||||
|
*str*
|
||||||
|
|
||||||
|
```rust
|
||||||
|
str
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Docs for prim_str
|
||||||
|
"#]],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,6 +41,10 @@ pub struct FamousDefs<'a, 'b>(pub &'a Semantics<'b, RootDatabase>, pub Option<Cr
|
||||||
impl FamousDefs<'_, '_> {
|
impl FamousDefs<'_, '_> {
|
||||||
pub const FIXTURE: &'static str = include_str!("helpers/famous_defs_fixture.rs");
|
pub const FIXTURE: &'static str = include_str!("helpers/famous_defs_fixture.rs");
|
||||||
|
|
||||||
|
pub fn std(&self) -> Option<Crate> {
|
||||||
|
self.find_crate("std")
|
||||||
|
}
|
||||||
|
|
||||||
pub fn core(&self) -> Option<Crate> {
|
pub fn core(&self) -> Option<Crate> {
|
||||||
self.find_crate("core")
|
self.find_crate("core")
|
||||||
}
|
}
|
||||||
|
|
|
@ -129,3 +129,11 @@ pub mod prelude {
|
||||||
}
|
}
|
||||||
#[prelude_import]
|
#[prelude_import]
|
||||||
pub use prelude::*;
|
pub use prelude::*;
|
||||||
|
//- /libstd.rs crate:std deps:core
|
||||||
|
//! Signatures of traits, types and functions from the std lib for use in tests.
|
||||||
|
|
||||||
|
/// Docs for return_keyword
|
||||||
|
mod return_keyword {}
|
||||||
|
|
||||||
|
/// Docs for prim_str
|
||||||
|
mod prim_str {}
|
||||||
|
|
Loading…
Reference in a new issue