]> git.lizzy.rs Git - rust.git/blobdiff - crates/ide/src/hover.rs
Merge #7851
[rust.git] / crates / ide / src / hover.rs
index ac2d7727ee117aa49e16ea49c89e057a8f702273..a9454cfa317342c56225449c240640edd2df8259 100644 (file)
@@ -2,9 +2,10 @@
     Adt, AsAssocItem, AssocItemContainer, FieldSource, GenericParam, HasAttrs, HasSource,
     HirDisplay, Module, ModuleDef, ModuleSource, Semantics,
 };
-use ide_db::base_db::SourceDatabase;
 use ide_db::{
+    base_db::SourceDatabase,
     defs::{Definition, NameClass, NameRefClass},
+    helpers::FamousDefs,
     RootDatabase,
 };
 use itertools::Itertools;
@@ -94,25 +95,27 @@ pub(crate) fn hover(
     let node = token.parent();
     let definition = match_ast! {
         match node {
-            ast::Name(name) => NameClass::classify(&sema, &name).and_then(|d| d.defined(sema.db)),
+            // 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),
+            }),
             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)),
-            ast::SelfParam(self_param) => NameClass::classify_self_param(&sema, &self_param).and_then(|d| d.defined(sema.db)),
             _ => None,
         }
     };
     if let Some(definition) = definition {
-        if let Some(markup) = hover_for_definition(db, definition) {
-            let markup = markup.as_str();
-            let markup = if !markdown {
-                remove_markdown(markup)
-            } else if links_in_hover {
-                rewrite_links(db, markup, &definition)
-            } else {
-                remove_links(markup)
-            };
-            res.markup = Markup::from(markup);
+        let famous_defs = match &definition {
+            Definition::ModuleDef(ModuleDef::BuiltinType(_)) => {
+                Some(FamousDefs(&sema, sema.scope(&node).krate()))
+            }
+            _ => None,
+        };
+        if let Some(markup) = hover_for_definition(db, definition, famous_defs.as_ref()) {
+            res.markup = process_markup(sema.db, definition, &markup, links_in_hover, markdown);
             if let Some(action) = show_implementations_action(db, definition) {
                 res.actions.push(action);
             }
@@ -134,6 +137,9 @@ pub(crate) fn hover(
         // 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;
+    }
 
     let node = token
         .ancestors()
@@ -268,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> {
     match def {
         Definition::Field(f) => Some(f.parent_def(db).name(db)),
@@ -300,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)))
 }
 
-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);
     return match def {
         Definition::Macro(it) => {
@@ -322,6 +350,7 @@ fn hover_for_definition(db: &RootDatabase, def: Definition) -> Option<Markup> {
                 match it.definition_source(db).value {
                     ModuleSource::Module(it) => it.short_label(),
                     ModuleSource::SourceFile(it) => it.short_label(),
+                    ModuleSource::BlockExpr(it) => it.short_label(),
                 },
                 mod_path,
             ),
@@ -334,7 +363,9 @@ fn hover_for_definition(db: &RootDatabase, def: Definition) -> Option<Markup> {
             ModuleDef::Static(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::BuiltinType(it) => Some(Markup::fenced_block(&it)),
+            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::SelfType(impl_def) => {
@@ -375,11 +406,52 @@ fn from_def_source_labeled<D>(
     }
 }
 
+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> {
     return tokens.max_by_key(priority);
     fn priority(n: &SyntaxToken) -> usize {
         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,
             kind if kind.is_trivia() => 0,
             _ => 1,
@@ -1826,6 +1898,35 @@ pub struct Bar
             "#]],
         );
     }
+    #[test]
+    fn test_hover_intra_link_reference_to_trait_method() {
+        check(
+            r#"
+pub trait Foo {
+    fn buzz() -> usize;
+}
+/// [Foo][buzz]
+///
+/// [buzz]: Foo::buzz
+pub struct B$0ar
+"#,
+            expect![[r#"
+                *Bar*
+
+                ```rust
+                test
+                ```
+
+                ```rust
+                pub struct Bar
+                ```
+
+                ---
+
+                [Foo](https://docs.rs/test/*/test/trait.Foo.html#tymethod.buzz)
+            "#]],
+        );
+    }
 
     #[test]
     fn test_hover_external_url() {
@@ -3218,7 +3319,7 @@ fn bar(&sel$0f) {}
 }
 "#,
             expect![[r#"
-                *&self*
+                *self*
 
                 ```rust
                 &Foo
@@ -3238,7 +3339,7 @@ fn bar(sel$0f: Arc<Foo>) {}
 }
 "#,
             expect![[r#"
-                *self: Arc<Foo>*
+                *self*
 
                 ```rust
                 Arc<Foo>
@@ -3383,7 +3484,7 @@ impl<T> Foo<T$0> {}
                 ```
                 "#]],
         );
-        // lifetimes aren't being substituted yet
+        // lifetimes bounds arent being tracked yet
         check(
             r#"
 struct Foo<T>(T);
@@ -3393,7 +3494,7 @@ impl<T: 'static> Foo<T$0> {}
                 *T*
 
                 ```rust
-                T: {error}
+                T
                 ```
                 "#]],
         );
@@ -3415,4 +3516,122 @@ impl<const LEN: usize> Foo<LEN$0> {}
             "#]],
         );
     }
+
+    #[test]
+    fn hover_const_pat() {
+        check(
+            r#"
+/// This is a doc
+const FOO: usize = 3;
+fn foo() {
+    match 5 {
+        FOO$0 => (),
+        _ => ()
+    }
+}
+"#,
+            expect![[r#"
+                *FOO*
+
+                ```rust
+                test
+                ```
+
+                ```rust
+                const FOO: usize = 3
+                ```
+
+                ---
+
+                This is a doc
+            "#]],
+        );
+    }
+
+    #[test]
+    fn hover_mod_def() {
+        check(
+            r#"
+//- /main.rs
+mod foo$0;
+//- /foo.rs
+//! For the horde!
+"#,
+            expect![[r#"
+                *foo*
+                For the horde!
+            "#]],
+        );
+    }
+
+    #[test]
+    fn hover_self_in_use() {
+        check(
+            r#"
+//! This should not appear
+mod foo {
+    /// But this should appear
+    pub mod bar {}
+}
+use foo::bar::{self$0};
+"#,
+            expect![[r#"
+                *self*
+
+                ```rust
+                test::foo
+                ```
+
+                ```rust
+                pub mod bar
+                ```
+
+                ---
+
+                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
+            "#]],
+        );
+    }
 }