diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2021-03-02 13:46:50 +0000 |
---|---|---|
committer | GitHub <[email protected]> | 2021-03-02 13:46:50 +0000 |
commit | 6a585c6ee24bbebfed2d0aafcf2e885a9ae77877 (patch) | |
tree | c8de313a9271d96035836a16a9f80b796e047a66 | |
parent | 657ec3616f076c85e21d82feba0397690e836bd9 (diff) | |
parent | 8d305680e6560debaf7868c160112e07a7bea8a0 (diff) |
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 <[email protected]>
-rw-r--r-- | crates/hir/src/lib.rs | 11 | ||||
-rw-r--r-- | crates/ide/src/hover.rs | 135 | ||||
-rw-r--r-- | crates/ide_db/src/helpers.rs | 4 | ||||
-rw-r--r-- | crates/ide_db/src/helpers/famous_defs_fixture.rs | 8 |
4 files changed, 139 insertions, 19 deletions
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 769945c47..69fcdab07 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs | |||
@@ -33,11 +33,11 @@ mod has_source; | |||
33 | pub use crate::{ | 33 | pub use crate::{ |
34 | attrs::{HasAttrs, Namespace}, | 34 | attrs::{HasAttrs, Namespace}, |
35 | code_model::{ | 35 | code_model::{ |
36 | Access, Adt, AsAssocItem, AssocItem, AssocItemContainer, Callable, CallableKind, Const, | 36 | Access, Adt, AsAssocItem, AssocItem, AssocItemContainer, BuiltinType, Callable, |
37 | ConstParam, Crate, CrateDependency, DefWithBody, Enum, Field, FieldSource, Function, | 37 | CallableKind, Const, ConstParam, Crate, CrateDependency, DefWithBody, Enum, Field, |
38 | GenericDef, GenericParam, HasVisibility, Impl, Label, LifetimeParam, Local, MacroDef, | 38 | FieldSource, Function, GenericDef, GenericParam, HasVisibility, Impl, Label, LifetimeParam, |
39 | Module, ModuleDef, ScopeDef, Static, Struct, Trait, Type, TypeAlias, TypeParam, Union, | 39 | Local, MacroDef, Module, ModuleDef, ScopeDef, Static, Struct, Trait, Type, TypeAlias, |
40 | Variant, VariantDef, | 40 | TypeParam, Union, Variant, VariantDef, |
41 | }, | 41 | }, |
42 | has_source::HasSource, | 42 | has_source::HasSource, |
43 | semantics::{PathResolution, Semantics, SemanticsScope}, | 43 | semantics::{PathResolution, Semantics, SemanticsScope}, |
@@ -47,7 +47,6 @@ pub use hir_def::{ | |||
47 | adt::StructKind, | 47 | adt::StructKind, |
48 | attr::{Attrs, Documentation}, | 48 | attr::{Attrs, Documentation}, |
49 | body::scope::ExprScopes, | 49 | body::scope::ExprScopes, |
50 | builtin_type::BuiltinType, | ||
51 | find_path::PrefixKind, | 50 | find_path::PrefixKind, |
52 | import_map, | 51 | import_map, |
53 | item_scope::ItemInNs, | 52 | item_scope::ItemInNs, |
diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs index 20b799490..a9454cfa3 100644 --- a/crates/ide/src/hover.rs +++ b/crates/ide/src/hover.rs | |||
@@ -5,6 +5,7 @@ use hir::{ | |||
5 | use ide_db::{ | 5 | use ide_db::{ |
6 | base_db::SourceDatabase, | 6 | base_db::SourceDatabase, |
7 | defs::{Definition, NameClass, NameRefClass}, | 7 | defs::{Definition, NameClass, NameRefClass}, |
8 | helpers::FamousDefs, | ||
8 | RootDatabase, | 9 | RootDatabase, |
9 | }; | 10 | }; |
10 | use itertools::Itertools; | 11 | use itertools::Itertools; |
@@ -107,16 +108,14 @@ pub(crate) fn hover( | |||
107 | } | 108 | } |
108 | }; | 109 | }; |
109 | if let Some(definition) = definition { | 110 | if let Some(definition) = definition { |
110 | if let Some(markup) = hover_for_definition(db, definition) { | 111 | let famous_defs = match &definition { |
111 | let markup = markup.as_str(); | 112 | Definition::ModuleDef(ModuleDef::BuiltinType(_)) => { |
112 | let markup = if !markdown { | 113 | Some(FamousDefs(&sema, sema.scope(&node).krate())) |
113 | remove_markdown(markup) | 114 | } |
114 | } else if links_in_hover { | 115 | _ => None, |
115 | rewrite_links(db, markup, &definition) | 116 | }; |
116 | } else { | 117 | if let Some(markup) = hover_for_definition(db, definition, famous_defs.as_ref()) { |
117 | remove_links(markup) | 118 | res.markup = process_markup(sema.db, definition, &markup, links_in_hover, markdown); |
118 | }; | ||
119 | res.markup = Markup::from(markup); | ||
120 | if let Some(action) = show_implementations_action(db, definition) { | 119 | if let Some(action) = show_implementations_action(db, definition) { |
121 | res.actions.push(action); | 120 | res.actions.push(action); |
122 | } | 121 | } |
@@ -138,6 +137,9 @@ pub(crate) fn hover( | |||
138 | // don't highlight the entire parent node on comment hover | 137 | // don't highlight the entire parent node on comment hover |
139 | return None; | 138 | return None; |
140 | } | 139 | } |
140 | if let res @ Some(_) = hover_for_keyword(&sema, links_in_hover, markdown, &token) { | ||
141 | return res; | ||
142 | } | ||
141 | 143 | ||
142 | let node = token | 144 | let node = token |
143 | .ancestors() | 145 | .ancestors() |
@@ -272,6 +274,24 @@ fn hover_markup( | |||
272 | } | 274 | } |
273 | } | 275 | } |
274 | 276 | ||
277 | fn process_markup( | ||
278 | db: &RootDatabase, | ||
279 | def: Definition, | ||
280 | markup: &Markup, | ||
281 | links_in_hover: bool, | ||
282 | markdown: bool, | ||
283 | ) -> Markup { | ||
284 | let markup = markup.as_str(); | ||
285 | let markup = if !markdown { | ||
286 | remove_markdown(markup) | ||
287 | } else if links_in_hover { | ||
288 | rewrite_links(db, markup, &def) | ||
289 | } else { | ||
290 | remove_links(markup) | ||
291 | }; | ||
292 | Markup::from(markup) | ||
293 | } | ||
294 | |||
275 | fn definition_owner_name(db: &RootDatabase, def: &Definition) -> Option<String> { | 295 | fn definition_owner_name(db: &RootDatabase, def: &Definition) -> Option<String> { |
276 | match def { | 296 | match def { |
277 | Definition::Field(f) => Some(f.parent_def(db).name(db)), | 297 | Definition::Field(f) => Some(f.parent_def(db).name(db)), |
@@ -304,7 +324,11 @@ fn definition_mod_path(db: &RootDatabase, def: &Definition) -> Option<String> { | |||
304 | def.module(db).map(|module| render_path(db, module, definition_owner_name(db, def))) | 324 | def.module(db).map(|module| render_path(db, module, definition_owner_name(db, def))) |
305 | } | 325 | } |
306 | 326 | ||
307 | fn hover_for_definition(db: &RootDatabase, def: Definition) -> Option<Markup> { | 327 | fn hover_for_definition( |
328 | db: &RootDatabase, | ||
329 | def: Definition, | ||
330 | famous_defs: Option<&FamousDefs>, | ||
331 | ) -> Option<Markup> { | ||
308 | let mod_path = definition_mod_path(db, &def); | 332 | let mod_path = definition_mod_path(db, &def); |
309 | return match def { | 333 | return match def { |
310 | Definition::Macro(it) => { | 334 | Definition::Macro(it) => { |
@@ -339,7 +363,9 @@ fn hover_for_definition(db: &RootDatabase, def: Definition) -> Option<Markup> { | |||
339 | ModuleDef::Static(it) => from_def_source(db, it, mod_path), | 363 | ModuleDef::Static(it) => from_def_source(db, it, mod_path), |
340 | ModuleDef::Trait(it) => from_def_source(db, it, mod_path), | 364 | ModuleDef::Trait(it) => from_def_source(db, it, mod_path), |
341 | ModuleDef::TypeAlias(it) => from_def_source(db, it, mod_path), | 365 | ModuleDef::TypeAlias(it) => from_def_source(db, it, mod_path), |
342 | ModuleDef::BuiltinType(it) => Some(Markup::fenced_block(&it.name())), | 366 | ModuleDef::BuiltinType(it) => famous_defs |
367 | .and_then(|fd| hover_for_builtin(fd, it)) | ||
368 | .or_else(|| Some(Markup::fenced_block(&it.name()))), | ||
343 | }, | 369 | }, |
344 | Definition::Local(it) => Some(Markup::fenced_block(&it.ty(db).display(db))), | 370 | Definition::Local(it) => Some(Markup::fenced_block(&it.ty(db).display(db))), |
345 | Definition::SelfType(impl_def) => { | 371 | Definition::SelfType(impl_def) => { |
@@ -380,11 +406,52 @@ fn hover_for_definition(db: &RootDatabase, def: Definition) -> Option<Markup> { | |||
380 | } | 406 | } |
381 | } | 407 | } |
382 | 408 | ||
409 | fn hover_for_keyword( | ||
410 | sema: &Semantics<RootDatabase>, | ||
411 | links_in_hover: bool, | ||
412 | markdown: bool, | ||
413 | token: &SyntaxToken, | ||
414 | ) -> Option<RangeInfo<HoverResult>> { | ||
415 | if !token.kind().is_keyword() { | ||
416 | return None; | ||
417 | } | ||
418 | let famous_defs = FamousDefs(&sema, sema.scope(&token.parent()).krate()); | ||
419 | // std exposes {}_keyword modules with docstrings on the root to document keywords | ||
420 | let keyword_mod = format!("{}_keyword", token.text()); | ||
421 | let doc_owner = find_std_module(&famous_defs, &keyword_mod)?; | ||
422 | let docs = doc_owner.attrs(sema.db).docs()?; | ||
423 | let markup = process_markup( | ||
424 | sema.db, | ||
425 | Definition::ModuleDef(doc_owner.into()), | ||
426 | &hover_markup(Some(docs.into()), Some(token.text().into()), None)?, | ||
427 | links_in_hover, | ||
428 | markdown, | ||
429 | ); | ||
430 | Some(RangeInfo::new(token.text_range(), HoverResult { markup, actions: Default::default() })) | ||
431 | } | ||
432 | |||
433 | fn hover_for_builtin(famous_defs: &FamousDefs, builtin: hir::BuiltinType) -> Option<Markup> { | ||
434 | // std exposes prim_{} modules with docstrings on the root to document the builtins | ||
435 | let primitive_mod = format!("prim_{}", builtin.name()); | ||
436 | let doc_owner = find_std_module(famous_defs, &primitive_mod)?; | ||
437 | let docs = doc_owner.attrs(famous_defs.0.db).docs()?; | ||
438 | hover_markup(Some(docs.into()), Some(builtin.name().to_string()), None) | ||
439 | } | ||
440 | |||
441 | fn find_std_module(famous_defs: &FamousDefs, name: &str) -> Option<hir::Module> { | ||
442 | let db = famous_defs.0.db; | ||
443 | let std_crate = famous_defs.std()?; | ||
444 | let std_root_module = std_crate.root_module(db); | ||
445 | std_root_module | ||
446 | .children(db) | ||
447 | .find(|module| module.name(db).map_or(false, |module| module.to_string() == name)) | ||
448 | } | ||
449 | |||
383 | fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> { | 450 | fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> { |
384 | return tokens.max_by_key(priority); | 451 | return tokens.max_by_key(priority); |
385 | fn priority(n: &SyntaxToken) -> usize { | 452 | fn priority(n: &SyntaxToken) -> usize { |
386 | match n.kind() { | 453 | match n.kind() { |
387 | IDENT | INT_NUMBER | LIFETIME_IDENT | T![self] => 3, | 454 | IDENT | INT_NUMBER | LIFETIME_IDENT | T![self] | T![super] | T![crate] => 3, |
388 | T!['('] | T![')'] => 2, | 455 | T!['('] | T![')'] => 2, |
389 | kind if kind.is_trivia() => 0, | 456 | kind if kind.is_trivia() => 0, |
390 | _ => 1, | 457 | _ => 1, |
@@ -3523,6 +3590,48 @@ use foo::bar::{self$0}; | |||
3523 | 3590 | ||
3524 | But this should appear | 3591 | But this should appear |
3525 | "#]], | 3592 | "#]], |
3593 | ) | ||
3594 | } | ||
3595 | |||
3596 | #[test] | ||
3597 | fn hover_keyword() { | ||
3598 | let ra_fixture = r#"//- /main.rs crate:main deps:std | ||
3599 | fn f() { retur$0n; }"#; | ||
3600 | let fixture = format!("{}\n{}", ra_fixture, FamousDefs::FIXTURE); | ||
3601 | check( | ||
3602 | &fixture, | ||
3603 | expect![[r#" | ||
3604 | *return* | ||
3605 | |||
3606 | ```rust | ||
3607 | return | ||
3608 | ``` | ||
3609 | |||
3610 | --- | ||
3611 | |||
3612 | Docs for return_keyword | ||
3613 | "#]], | ||
3614 | ); | ||
3615 | } | ||
3616 | |||
3617 | #[test] | ||
3618 | fn hover_builtin() { | ||
3619 | let ra_fixture = r#"//- /main.rs crate:main deps:std | ||
3620 | cosnt _: &str$0 = ""; }"#; | ||
3621 | let fixture = format!("{}\n{}", ra_fixture, FamousDefs::FIXTURE); | ||
3622 | check( | ||
3623 | &fixture, | ||
3624 | expect![[r#" | ||
3625 | *str* | ||
3626 | |||
3627 | ```rust | ||
3628 | str | ||
3629 | ``` | ||
3630 | |||
3631 | --- | ||
3632 | |||
3633 | Docs for prim_str | ||
3634 | "#]], | ||
3526 | ); | 3635 | ); |
3527 | } | 3636 | } |
3528 | } | 3637 | } |
diff --git a/crates/ide_db/src/helpers.rs b/crates/ide_db/src/helpers.rs index f9de8ce0e..3ff77400b 100644 --- a/crates/ide_db/src/helpers.rs +++ b/crates/ide_db/src/helpers.rs | |||
@@ -41,6 +41,10 @@ pub struct FamousDefs<'a, 'b>(pub &'a Semantics<'b, RootDatabase>, pub Option<Cr | |||
41 | impl FamousDefs<'_, '_> { | 41 | impl FamousDefs<'_, '_> { |
42 | pub const FIXTURE: &'static str = include_str!("helpers/famous_defs_fixture.rs"); | 42 | pub const FIXTURE: &'static str = include_str!("helpers/famous_defs_fixture.rs"); |
43 | 43 | ||
44 | pub fn std(&self) -> Option<Crate> { | ||
45 | self.find_crate("std") | ||
46 | } | ||
47 | |||
44 | pub fn core(&self) -> Option<Crate> { | 48 | pub fn core(&self) -> Option<Crate> { |
45 | self.find_crate("core") | 49 | self.find_crate("core") |
46 | } | 50 | } |
diff --git a/crates/ide_db/src/helpers/famous_defs_fixture.rs b/crates/ide_db/src/helpers/famous_defs_fixture.rs index bb4e9666b..d3464ae17 100644 --- a/crates/ide_db/src/helpers/famous_defs_fixture.rs +++ b/crates/ide_db/src/helpers/famous_defs_fixture.rs | |||
@@ -129,3 +129,11 @@ pub mod prelude { | |||
129 | } | 129 | } |
130 | #[prelude_import] | 130 | #[prelude_import] |
131 | pub use prelude::*; | 131 | pub use prelude::*; |
132 | //- /libstd.rs crate:std deps:core | ||
133 | //! Signatures of traits, types and functions from the std lib for use in tests. | ||
134 | |||
135 | /// Docs for return_keyword | ||
136 | mod return_keyword {} | ||
137 | |||
138 | /// Docs for prim_str | ||
139 | mod prim_str {} | ||