use either::Either;
use hir::{
- AsAssocItem, AssocItemContainer, GenericParam, HasAttrs, HasSource, HirDisplay, Module,
+ AsAssocItem, AssocItemContainer, GenericParam, HasAttrs, HasSource, HirDisplay, InFile, Module,
ModuleDef, Semantics,
};
use ide_db::{
base_db::SourceDatabase,
defs::{Definition, NameClass, NameRefClass},
- helpers::FamousDefs,
+ helpers::{
+ generated_lints::{CLIPPY_LINTS, DEFAULT_LINTS, FEATURES},
+ FamousDefs,
+ },
RootDatabase,
};
use itertools::Itertools;
use stdx::format_to;
-use syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset, T};
+use syntax::{
+ algo, ast, match_ast, AstNode, AstToken, Direction, SyntaxKind::*, SyntaxToken, TokenAtOffset,
+ T,
+};
use crate::{
display::{macro_label, TryToNav},
- doc_links::{remove_links, rewrite_links},
+ doc_links::{
+ doc_attributes, extract_definitions_from_markdown, remove_links, resolve_doc_path_for_def,
+ rewrite_links,
+ },
markdown_remove::remove_markdown,
markup::Markup,
runnables::{runnable_fn, runnable_mod},
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct HoverConfig {
pub implementations: bool,
+ pub references: bool,
pub run: bool,
pub debug: bool,
pub goto_type_def: bool,
impl HoverConfig {
pub const NO_ACTIONS: Self = Self {
implementations: false,
+ references: false,
run: false,
debug: false,
goto_type_def: false,
};
pub fn any(&self) -> bool {
- self.implementations || self.runnable() || self.goto_type_def
+ self.implementations || self.references || self.runnable() || self.goto_type_def
}
pub fn none(&self) -> bool {
pub enum HoverAction {
Runnable(Runnable),
Implementation(FilePosition),
+ Reference(FilePosition),
GoToType(Vec<HoverGotoTypeData>),
}
//
// Shows additional information, like type of an expression or documentation for 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[]
pub(crate) fn hover(
db: &RootDatabase,
position: FilePosition,
let mut res = HoverResult::default();
let node = token.parent()?;
+ 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
ast::Name(name) => NameClass::classify(&sema, &name).and_then(|class| match class {
NameClass::ConstReference(def) => Some(def),
- def => def.defined(sema.db),
+ def => def.defined(db),
}),
- ast::NameRef(name_ref) => NameRefClass::classify(&sema, &name_ref).map(|d| d.referenced(sema.db)),
- ast::Lifetime(lifetime) => NameClass::classify_lifetime(&sema, &lifetime)
- .map_or_else(|| NameRefClass::classify_lifetime(&sema, &lifetime).map(|d| d.referenced(sema.db)), |d| d.defined(sema.db)),
- _ => None,
+ ast::NameRef(name_ref) => {
+ NameRefClass::classify(&sema, &name_ref).map(|d| d.referenced(db))
+ },
+ ast::Lifetime(lifetime) => NameClass::classify_lifetime(&sema, &lifetime).map_or_else(
+ || NameRefClass::classify_lifetime(&sema, &lifetime).map(|d| d.referenced(db)),
+ |d| d.defined(db),
+ ),
+
+ _ => {
+ if ast::Comment::cast(token.clone()).is_some() {
+ cov_mark::hit!(no_highlight_on_comment_hover);
+ let (attributes, def) = doc_attributes(&sema, &node)?;
+ let (docs, doc_mapping) = attributes.docs_with_rangemap(db)?;
+ let (idl_range, link, ns) =
+ extract_definitions_from_markdown(docs.as_str()).into_iter().find_map(|(range, link, ns)| {
+ let InFile { file_id, value: range } = doc_mapping.map(range)?;
+ if file_id == position.file_id.into() && range.contains(position.offset) {
+ Some((range, link, ns))
+ } else {
+ None
+ }
+ })?;
+ range = Some(idl_range);
+ resolve_doc_path_for_def(db, def, &link, ns).map(Definition::ModuleDef)
+ } else if let res@Some(_) = try_hover_for_attribute(&token) {
+ return res;
+ } else {
+ None
+ }
+ },
}
};
+
if let Some(definition) = definition {
let famous_defs = match &definition {
Definition::ModuleDef(ModuleDef::BuiltinType(_)) => {
res.actions.push(action);
}
+ if let Some(action) = show_fn_references_action(db, definition) {
+ res.actions.push(action);
+ }
+
if let Some(action) = runnable_action(&sema, definition, position.file_id) {
res.actions.push(action);
}
res.actions.push(action);
}
- let range = sema.original_range(&node).range;
+ let range = range.unwrap_or_else(|| sema.original_range(&node).range);
return Some(RangeInfo::new(range, res));
}
}
- if token.kind() == syntax::SyntaxKind::COMMENT {
- // don't highlight the entire parent node on comment hover
- return None;
- }
if let res @ Some(_) = hover_for_keyword(&sema, links_in_hover, markdown, &token) {
return res;
}
Some(RangeInfo::new(range, res))
}
+fn try_hover_for_attribute(token: &SyntaxToken) -> Option<RangeInfo<HoverResult>> {
+ let attr = token.ancestors().find_map(ast::Attr::cast)?;
+ let (path, tt) = attr.as_simple_call()?;
+ if !tt.syntax().text_range().contains(token.text_range().start()) {
+ return None;
+ }
+ let (is_clippy, lints) = match &*path {
+ "feature" => (false, FEATURES),
+ "allow" | "deny" | "forbid" | "warn" => {
+ let is_clippy = algo::non_trivia_sibling(token.clone().into(), Direction::Prev)
+ .filter(|t| t.kind() == T![:])
+ .and_then(|t| algo::non_trivia_sibling(t, Direction::Prev))
+ .filter(|t| t.kind() == T![:])
+ .and_then(|t| algo::non_trivia_sibling(t, Direction::Prev))
+ .map_or(false, |t| {
+ t.kind() == T![ident] && t.into_token().map_or(false, |t| t.text() == "clippy")
+ });
+ if is_clippy {
+ (true, CLIPPY_LINTS)
+ } else {
+ (false, DEFAULT_LINTS)
+ }
+ }
+ _ => return None,
+ };
+
+ let tmp;
+ let needle = if is_clippy {
+ tmp = format!("clippy::{}", token.text());
+ &tmp
+ } else {
+ &*token.text()
+ };
+
+ let lint =
+ lints.binary_search_by_key(&needle, |lint| lint.label).ok().map(|idx| &lints[idx])?;
+ Some(RangeInfo::new(
+ token.text_range(),
+ HoverResult {
+ markup: Markup::from(format!("```\n{}\n```\n___\n\n{}", lint.label, lint.description)),
+ ..Default::default()
+ },
+ ))
+}
+
fn show_implementations_action(db: &RootDatabase, def: Definition) -> Option<HoverAction> {
fn to_action(nav_target: NavigationTarget) -> HoverAction {
HoverAction::Implementation(FilePosition {
let adt = match def {
Definition::ModuleDef(ModuleDef::Trait(it)) => return it.try_to_nav(db).map(to_action),
Definition::ModuleDef(ModuleDef::Adt(it)) => Some(it),
- Definition::SelfType(it) => it.target_ty(db).as_adt(),
+ Definition::SelfType(it) => it.self_ty(db).as_adt(),
_ => None,
}?;
adt.try_to_nav(db).map(to_action)
}
+fn show_fn_references_action(db: &RootDatabase, def: Definition) -> Option<HoverAction> {
+ match def {
+ Definition::ModuleDef(ModuleDef::Function(it)) => it.try_to_nav(db).map(|nav_target| {
+ HoverAction::Reference(FilePosition {
+ file_id: nav_target.file_id,
+ offset: nav_target.focus_or_full_range().start(),
+ })
+ }),
+ _ => None,
+ }
+}
+
fn runnable_action(
sema: &Semantics<RootDatabase>,
def: Definition,
) -> Option<HoverAction> {
match def {
Definition::ModuleDef(it) => match it {
- ModuleDef::Module(it) => runnable_mod(&sema, it).map(|it| HoverAction::Runnable(it)),
+ ModuleDef::Module(it) => runnable_mod(sema, it).map(HoverAction::Runnable),
ModuleDef::Function(func) => {
let src = func.source(sema.db)?;
if src.file_id != file_id.into() {
return None;
}
- runnable_fn(&sema, func).map(HoverAction::Runnable)
+ runnable_fn(sema, func).map(HoverAction::Runnable)
}
_ => None,
},
Definition::ModuleDef(md) => match md {
ModuleDef::Function(f) => match f.as_assoc_item(db)?.container(db) {
AssocItemContainer::Trait(t) => Some(t.name(db)),
- AssocItemContainer::Impl(i) => i.target_ty(db).as_adt().map(|adt| adt.name(db)),
+ AssocItemContainer::Impl(i) => i.self_ty(db).as_adt().map(|adt| adt.name(db)),
},
ModuleDef::Variant(e) => Some(e.parent_enum(db).name(db)),
_ => None,
return match def {
Definition::Macro(it) => match &it.source(db)?.value {
Either::Left(mac) => {
- let label = macro_label(&mac);
+ let label = macro_label(mac);
from_def_source_labeled(db, it, Some(label), mod_path)
}
Either::Right(_) => {
},
Definition::Local(it) => hover_for_local(it, db),
Definition::SelfType(impl_def) => {
- impl_def.target_ty(db).as_adt().and_then(|adt| from_hir_fmt(db, adt, mod_path))
+ impl_def.self_ty(db).as_adt().and_then(|adt| from_hir_fmt(db, adt, mod_path))
}
Definition::GenericParam(it) => from_hir_fmt(db, it, None),
Definition::Label(it) => Some(Markup::fenced_block(&it.name(db))),
if !token.kind().is_keyword() {
return None;
}
- let famous_defs = FamousDefs(&sema, sema.scope(&token.parent()?).krate());
+ 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)?;
fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> {
return tokens.max_by_key(priority);
+
fn priority(n: &SyntaxToken) -> usize {
match n.kind() {
IDENT | INT_NUMBER | LIFETIME_IDENT | T![self] | T![super] | T![crate] => 3,
use crate::fixture;
- use super::*;
-
fn check_hover_no_result(ra_fixture: &str) {
let (analysis, position) = fixture::position(ra_fixture);
assert!(analysis.hover(position, true, true).unwrap().is_none());
)
}
+ #[test]
+ fn test_hover_macro2_invocation() {
+ check(
+ r#"
+/// foo bar
+///
+/// foo bar baz
+macro foo() {}
+
+fn f() { fo$0o!(); }
+"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ macro foo
+ ```
+
+ ---
+
+ foo bar
+
+ foo bar baz
+ "#]],
+ )
+ }
+
#[test]
fn test_hover_tuple_field() {
check(
);
}
- #[ignore = "path based links currently only support documentation on ModuleDef items"]
#[test]
fn test_hover_path_link_field() {
+ // FIXME: Should be
+ // [Foo](https://docs.rs/test/*/test/struct.Foo.html)
check(
r#"
pub struct Foo;
---
- [Foo](https://docs.rs/test/*/test/struct.Foo.html)
+ [Foo](struct.Foo.html)
"#]],
);
}
"#,
expect![[r#"
[
+ Reference(
+ FilePosition {
+ file_id: FileId(
+ 0,
+ ),
+ offset: 11,
+ },
+ ),
Runnable(
Runnable {
nav: NavigationTarget {
fn test_hover_async_block_impl_trait_has_goto_type_action() {
check_actions(
r#"
+//- minicore: future
struct S;
fn foo() {
let fo$0o = async { S };
}
-
-#[prelude_import] use future::*;
-mod future {
- #[lang = "future_trait"]
- pub trait Future { type Output; }
-}
"#,
expect![[r#"
[
GoToType(
[
HoverGotoTypeData {
- mod_path: "test::future::Future",
+ mod_path: "core::future::Future",
nav: NavigationTarget {
file_id: FileId(
- 0,
+ 1,
),
- full_range: 101..163,
- focus_range: 140..146,
+ full_range: 248..430,
+ focus_range: 287..293,
name: "Future",
kind: Trait,
description: "pub trait Future",
#[test]
fn hover_comments_dont_highlight_parent() {
+ cov_mark::check!(no_highlight_on_comment_hover);
check_hover_no_result(
r#"
fn no_hover() {
#[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,
+ r#"
+//- /main.rs crate:main deps:std
+fn f() { retur$0n; }
+//- /libstd.rs crate:std
+/// Docs for return_keyword
+mod return_keyword {}
+"#,
expect![[r#"
*return*
#[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,
+ r#"
+//- /main.rs crate:main deps:std
+cosnt _: &str$0 = ""; }
+
+//- /libstd.rs crate:std
+/// Docs for prim_str
+mod prim_str {}
+"#,
expect![[r#"
*str*
"#]],
)
}
+
+ #[test]
+ fn hover_intra_doc_links() {
+ check(
+ r#"
+
+pub mod theitem {
+ /// This is the item. Cool!
+ pub struct TheItem;
+}
+
+/// Gives you a [`TheItem$0`].
+///
+/// [`TheItem`]: theitem::TheItem
+pub fn gimme() -> theitem::TheItem {
+ theitem::TheItem
+}
+"#,
+ expect![[r#"
+ *[`TheItem`]*
+
+ ```rust
+ test::theitem
+ ```
+
+ ```rust
+ pub struct TheItem
+ ```
+
+ ---
+
+ This is the item. Cool!
+ "#]],
+ );
+ }
+
+ #[test]
+ fn hover_generic_assoc() {
+ check(
+ r#"
+fn foo<T: A>() where T::Assoc$0: {}
+
+trait A {
+ type Assoc;
+}"#,
+ expect![[r#"
+ *Assoc*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ type Assoc
+ ```
+ "#]],
+ );
+ check(
+ r#"
+fn foo<T: A>() {
+ let _: <T>::Assoc$0;
+}
+
+trait A {
+ type Assoc;
+}"#,
+ expect![[r#"
+ *Assoc*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ type Assoc
+ ```
+ "#]],
+ );
+ check(
+ r#"
+trait A where
+ Self::Assoc$0: ,
+{
+ type Assoc;
+}"#,
+ expect![[r#"
+ *Assoc*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ type Assoc
+ ```
+ "#]],
+ );
+ }
+
+ #[test]
+ fn string_shadowed_with_inner_items() {
+ check(
+ r#"
+//- /main.rs crate:main deps:alloc
+
+/// Custom `String` type.
+struct String;
+
+fn f() {
+ let _: String$0;
+
+ fn inner() {}
+}
+
+//- /alloc.rs crate:alloc
+#[prelude_import]
+pub use string::*;
+
+mod string {
+ /// This is `alloc::String`.
+ pub struct String;
+}
+ "#,
+ expect![[r#"
+ *String*
+
+ ```rust
+ main
+ ```
+
+ ```rust
+ struct String
+ ```
+
+ ---
+
+ Custom `String` type.
+ "#]],
+ )
+ }
+
+ #[test]
+ fn function_doesnt_shadow_crate_in_use_tree() {
+ check(
+ r#"
+//- /main.rs crate:main deps:foo
+use foo$0::{foo};
+
+//- /foo.rs crate:foo
+pub fn foo() {}
+"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ extern crate foo
+ ```
+ "#]],
+ )
+ }
+
+ #[test]
+ fn hover_feature() {
+ check(
+ r#"#![feature(box_syntax$0)]"#,
+ expect![[r##"
+ *box_syntax*
+ ```
+ box_syntax
+ ```
+ ___
+
+ # `box_syntax`
+
+ The tracking issue for this feature is: [#49733]
+
+ [#49733]: https://github.com/rust-lang/rust/issues/49733
+
+ See also [`box_patterns`](box-patterns.md)
+
+ ------------------------
+
+ Currently the only stable way to create a `Box` is via the `Box::new` method.
+ Also it is not possible in stable Rust to destructure a `Box` in a match
+ pattern. The unstable `box` keyword can be used to create a `Box`. An example
+ usage would be:
+
+ ```rust
+ #![feature(box_syntax)]
+
+ fn main() {
+ let b = box 5;
+ }
+ ```
+
+ "##]],
+ )
+ }
+
+ #[test]
+ fn hover_lint() {
+ check(
+ r#"#![allow(arithmetic_overflow$0)]"#,
+ expect![[r#"
+ *arithmetic_overflow*
+ ```
+ arithmetic_overflow
+ ```
+ ___
+
+ arithmetic operation overflows
+ "#]],
+ )
+ }
+
+ #[test]
+ fn hover_clippy_lint() {
+ check(
+ r#"#![allow(clippy::almost_swapped$0)]"#,
+ expect![[r#"
+ *almost_swapped*
+ ```
+ clippy::almost_swapped
+ ```
+ ___
+
+ Checks for `foo = bar; bar = foo` sequences.
+ "#]],
+ )
+ }
}