Unnamed repository; edit this file 'description' to name the repository.
Merge #10319
10319: internal: Cleanup hover a bit r=Veykril a=Veykril bors r+ Co-authored-by: Lukas Wirth <[email protected]>
bors[bot] 2021-09-23
parent 88a214f · parent 42eb4ef · commit eb727c7
-rw-r--r--Cargo.lock1
-rw-r--r--crates/hir_def/src/attr.rs2
-rw-r--r--crates/ide/src/goto_definition.rs2
-rw-r--r--crates/ide/src/hover.rs188
-rw-r--r--crates/ide_db/Cargo.toml1
-rw-r--r--crates/ide_db/src/defs.rs114
-rw-r--r--crates/syntax/src/ast/node_ext.rs10
7 files changed, 145 insertions, 173 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 47e88e877d..9170ec3a66 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -645,6 +645,7 @@ dependencies = [
name = "ide_db"
version = "0.0.0"
dependencies = [
+ "arrayvec",
"base_db",
"cov-mark",
"either",
diff --git a/crates/hir_def/src/attr.rs b/crates/hir_def/src/attr.rs
index 0470b9510f..ef86572fe5 100644
--- a/crates/hir_def/src/attr.rs
+++ b/crates/hir_def/src/attr.rs
@@ -547,6 +547,7 @@ fn inner_attributes(
Some((attrs, docs))
}
+#[derive(Debug)]
pub struct AttrSourceMap {
attrs: Vec<InFile<ast::Attr>>,
doc_comments: Vec<InFile<ast::Comment>>,
@@ -599,6 +600,7 @@ impl AttrSourceMap {
}
/// A struct to map text ranges from [`Documentation`] back to TextRanges in the syntax tree.
+#[derive(Debug)]
pub struct DocsRangeMap {
source_map: AttrSourceMap,
// (docstring-line-range, attr_index, attr-string-range)
diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs
index 4c01231f4a..9acea114d6 100644
--- a/crates/ide/src/goto_definition.rs
+++ b/crates/ide/src/goto_definition.rs
@@ -64,7 +64,7 @@ pub(crate) fn goto_definition(
}
}
Some(
- Definition::from_node(&sema, &token)
+ Definition::from_token(&sema, &token)
.into_iter()
.flat_map(|def| {
try_find_trait_item_definition(sema.db, &def)
diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs
index 80e91f303c..4e57484c78 100644
--- a/crates/ide/src/hover.rs
+++ b/crates/ide/src/hover.rs
@@ -1,4 +1,4 @@
-use std::{collections::HashSet, ops::ControlFlow};
+use std::iter;
use either::Either;
use hir::{AsAssocItem, HasAttrs, HasSource, HirDisplay, Semantics, TypeInfo};
@@ -15,7 +15,7 @@ use itertools::Itertools;
use stdx::format_to;
use syntax::{
algo, ast, display::fn_as_proc_macro_label, match_ast, AstNode, Direction, SyntaxKind::*,
- SyntaxNode, SyntaxToken, TextRange, TextSize, T,
+ SyntaxNode, SyntaxToken, T,
};
use crate::{
@@ -99,7 +99,7 @@ pub(crate) fn hover(
FileRange { file_id, range }: FileRange,
config: &HoverConfig,
) -> Option<RangeInfo<HoverResult>> {
- let sema = hir::Semantics::new(db);
+ let sema = &hir::Semantics::new(db);
let file = sema.parse(file_id).syntax().clone();
if !range.is_empty() {
@@ -114,135 +114,75 @@ pub(crate) fn hover(
_ => 1,
})?;
- let mut seen = HashSet::default();
+ let descended = sema.descend_into_macros_many(original_token.clone());
- let mut fallback = None;
- // attributes, require special machinery as they are mere ident tokens
-
- let descend_macros = sema.descend_into_macros_many(original_token.clone());
-
- for token in &descend_macros {
- if token.kind() != COMMENT {
- if let Some(attr) = token.ancestors().find_map(ast::Attr::cast) {
- // lints
- if let Some(res) = try_hover_for_lint(&attr, &token) {
- return Some(res);
- }
- }
- }
- }
-
- descend_macros
- .iter()
- .filter_map(|token| match token.parent() {
- Some(node) => {
- match find_hover_result(
- &sema,
- file_id,
- offset,
- config,
- &original_token,
- token,
- &node,
- &mut seen,
- ) {
- Some(res) => match res {
- ControlFlow::Break(inner) => Some(inner),
- ControlFlow::Continue(_) => {
- if fallback.is_none() {
- // FIXME we're only taking the first fallback into account that's not `None`
- fallback = hover_for_keyword(&sema, config, &token)
- .or(type_hover(&sema, config, &token));
- }
- None
- }
- },
- None => None,
- }
- }
- None => None,
- })
- // reduce all descends into a single `RangeInfo`
- // that spans from the earliest start to the latest end (fishy/FIXME),
- // concatenates all `Markup`s with `\n---\n`,
- // and accumulates all actions into its `actions` vector.
- .reduce(|mut acc, RangeInfo { range, mut info }| {
- let start = acc.range.start().min(range.start());
- let end = acc.range.end().max(range.end());
-
- acc.range = TextRange::new(start, end);
- acc.info.actions.append(&mut info.actions);
- acc.info.markup = Markup::from(format!("{}\n---\n{}", acc.info.markup, info.markup));
- acc
- })
- .or(fallback)
-}
-
-fn find_hover_result(
- sema: &Semantics<RootDatabase>,
- file_id: FileId,
- offset: TextSize,
- config: &HoverConfig,
- original_token: &SyntaxToken,
- token: &SyntaxToken,
- node: &SyntaxNode,
- seen: &mut HashSet<Definition>,
-) -> Option<ControlFlow<RangeInfo<HoverResult>>> {
- let mut range_override = None;
-
- // intra-doc links and attributes are special cased
- // so don't add them to the `seen` duplicate check
- let mut add_to_seen_definitions = true;
-
- let definition = Definition::from_node(sema, token).into_iter().next().or_else(|| {
+ // FIXME handle doc attributes? TokenMap currently doesn't work with comments
+ if original_token.kind() == COMMENT {
+ let relative_comment_offset = offset - original_token.text_range().start();
// intra-doc links
- // FIXME: move comment + attribute special cases somewhere else to simplify control flow,
- // hopefully simplifying the return type of this function in the process
- // (the `Break`/`Continue` distinction is needed to decide whether to use fallback hovers)
- //
- // FIXME: hovering the intra doc link to `Foo` not working:
- //
- // #[identity]
- // trait Foo {
- // /// [`Foo`]
- // fn foo() {}
- if token.kind() == COMMENT {
- add_to_seen_definitions = false;
- cov_mark::hit!(no_highlight_on_comment_hover);
- let (attributes, def) = doc_attributes(sema, node)?;
+ cov_mark::hit!(no_highlight_on_comment_hover);
+ return descended.iter().find_map(|t| {
+ match t.kind() {
+ COMMENT => (),
+ TOKEN_TREE => {}
+ _ => return None,
+ }
+ let node = t.parent()?;
+ let absolute_comment_offset = t.text_range().start() + relative_comment_offset;
+ let (attributes, def) = doc_attributes(sema, &node)?;
let (docs, doc_mapping) = attributes.docs_with_rangemap(sema.db)?;
let (idl_range, link, ns) = extract_definitions_from_docs(&docs).into_iter().find_map(
|(range, link, ns)| {
let mapped = doc_mapping.map(range)?;
- (mapped.file_id == file_id.into() && mapped.value.contains(offset))
- .then(|| (mapped.value, link, ns))
+ (mapped.file_id == file_id.into()
+ && mapped.value.contains(absolute_comment_offset))
+ .then(|| (mapped.value, link, ns))
},
)?;
- range_override = Some(idl_range);
- Some(match resolve_doc_path_for_def(sema.db, def, &link, ns)? {
+ let def = match resolve_doc_path_for_def(sema.db, def, &link, ns)? {
Either::Left(it) => Definition::ModuleDef(it),
Either::Right(it) => Definition::Macro(it),
- })
- } else {
- None
- }
- });
+ };
+ let res = hover_for_definition(sema, file_id, def, &node, config)?;
+ Some(RangeInfo::new(idl_range, res))
+ });
+ }
- if let Some(definition) = definition {
- // skip duplicates
- if seen.contains(&definition) {
- return None;
- }
- if add_to_seen_definitions {
- seen.insert(definition);
+ // attributes, require special machinery as they are mere ident tokens
+
+ // FIXME: Definition should include known lints and the like instead of having this special case here
+ if let res @ Some(_) = descended.iter().find_map(|token| {
+ let attr = token.ancestors().find_map(ast::Attr::cast)?;
+ try_hover_for_lint(&attr, &token)
+ }) {
+ return res;
+ }
+
+ let result = descended
+ .iter()
+ .filter_map(|token| {
+ let node = token.parent()?;
+ let defs = Definition::from_token(sema, token);
+ Some(defs.into_iter().zip(iter::once(node).cycle()))
+ })
+ .flatten()
+ .unique_by(|&(def, _)| def)
+ .filter_map(|(def, node)| hover_for_definition(sema, file_id, def, &node, config))
+ .reduce(|mut acc, HoverResult { markup, actions }| {
+ acc.actions.extend(actions);
+ acc.markup = Markup::from(format!("{}\n---\n{}", acc.markup, markup));
+ acc
+ });
+ if result.is_none() {
+ // fallbacks, show keywords or types
+ if let res @ Some(_) = hover_for_keyword(sema, config, &original_token) {
+ return res;
}
- if let Some(res) = hover_for_definition(sema, file_id, definition, &node, config) {
- let range = range_override.unwrap_or_else(|| original_token.text_range());
- return Some(ControlFlow::Break(RangeInfo::new(range, res)));
+ if let res @ Some(_) = descended.iter().find_map(|token| type_hover(sema, config, token)) {
+ return res;
}
}
-
- Some(ControlFlow::Continue(()))
+ result.map(|res| RangeInfo::new(original_token.text_range(), res))
}
fn type_hover(
@@ -250,9 +190,6 @@ fn type_hover(
config: &HoverConfig,
token: &SyntaxToken,
) -> Option<RangeInfo<HoverResult>> {
- if token.kind() == COMMENT {
- return None;
- }
let node = token
.ancestors()
.take_while(|it| !ast::Item::can_cast(it.kind()))
@@ -3626,6 +3563,15 @@ fn main() {
```rust
f: &i32
```
+ ---
+
+ ```rust
+ test::S
+ ```
+
+ ```rust
+ f: i32
+ ```
"#]],
);
}
diff --git a/crates/ide_db/Cargo.toml b/crates/ide_db/Cargo.toml
index 981a58347a..cdaff0ce39 100644
--- a/crates/ide_db/Cargo.toml
+++ b/crates/ide_db/Cargo.toml
@@ -17,6 +17,7 @@ rustc-hash = "1.1.0"
once_cell = "1.3.1"
either = "1.6.1"
itertools = "0.10.0"
+arrayvec = "0.7"
stdx = { path = "../stdx", version = "0.0.0" }
syntax = { path = "../syntax", version = "0.0.0" }
diff --git a/crates/ide_db/src/defs.rs b/crates/ide_db/src/defs.rs
index d42af9c38f..effa694aae 100644
--- a/crates/ide_db/src/defs.rs
+++ b/crates/ide_db/src/defs.rs
@@ -5,13 +5,14 @@
// FIXME: this badly needs rename/rewrite (matklad, 2020-02-06).
+use arrayvec::ArrayVec;
use hir::{
Field, GenericParam, HasVisibility, Impl, Label, Local, MacroDef, Module, ModuleDef, Name,
PathResolution, Semantics, Visibility,
};
use syntax::{
ast::{self, AstNode},
- match_ast, SyntaxKind, SyntaxToken,
+ match_ast, SyntaxKind, SyntaxNode, SyntaxToken,
};
use crate::{helpers::try_resolve_derive_input_at, RootDatabase};
@@ -29,60 +30,71 @@ pub enum Definition {
}
impl Definition {
- pub fn from_node(sema: &Semantics<RootDatabase>, token: &SyntaxToken) -> Vec<Definition> {
- let node = if let Some(x) = token.parent() {
- x
- } else {
- return vec![];
+ pub fn from_token(
+ sema: &Semantics<RootDatabase>,
+ token: &SyntaxToken,
+ ) -> ArrayVec<Definition, 2> {
+ let parent = match token.parent() {
+ Some(parent) => parent,
+ None => return Default::default(),
};
- if token.kind() != SyntaxKind::COMMENT {
- if let Some(attr) = token.ancestors().find_map(ast::Attr::cast) {
- // derives
- let def = try_resolve_derive_input_at(&sema, &attr, &token).map(Definition::Macro);
- if let Some(def) = def {
- return vec![def];
- }
- }
+ let attr = parent
+ .ancestors()
+ .find_map(ast::TokenTree::cast)
+ .and_then(|tt| tt.parent_meta())
+ .and_then(|meta| meta.parent_attr());
+ if let Some(attr) = attr {
+ try_resolve_derive_input_at(&sema, &attr, &token)
+ .map(Definition::Macro)
+ .into_iter()
+ .collect()
+ } else {
+ Self::from_node(sema, &parent)
}
- match_ast! {
- match node {
- ast::Name(name) => {
- let class = if let Some(x) = NameClass::classify(&sema, &name) {
- x
- } else {
- return vec![];
- };
- match class {
- NameClass::Definition(it) | NameClass::ConstReference(it) => vec![it],
- NameClass::PatFieldShorthand { local_def, field_ref } => vec![Definition::Local(local_def), Definition::Field(field_ref)],
- }
- },
- ast::NameRef(name_ref) => {
- let class = if let Some(x) = NameRefClass::classify(sema, &name_ref) {
- x
- } else {
- return vec![];
- };
- match class {
- NameRefClass::Definition(def) => vec![def],
- NameRefClass::FieldShorthand { local_ref, field_ref } => {
- vec![Definition::Field(field_ref), Definition::Local(local_ref)]
+ }
+
+ pub fn from_node(sema: &Semantics<RootDatabase>, node: &SyntaxNode) -> ArrayVec<Definition, 2> {
+ let mut res = ArrayVec::new();
+ (|| {
+ match_ast! {
+ match node {
+ ast::Name(name) => {
+ match NameClass::classify(&sema, &name)? {
+ NameClass::Definition(it) | NameClass::ConstReference(it) => res.push(it),
+ NameClass::PatFieldShorthand { local_def, field_ref } => {
+ res.push(Definition::Local(local_def));
+ res.push(Definition::Field(field_ref));
+ }
}
- }
- },
- ast::Lifetime(lifetime) => {
- (if let Some(x) = NameClass::classify_lifetime(&sema, &lifetime) {
- NameClass::defined(x)
- } else {
- NameRefClass::classify_lifetime(&sema, &lifetime).and_then(|class| match class {
- NameRefClass::Definition(it) => Some(it),
- _ => None,
- })
- }).into_iter().collect()
- },
- _ => vec![],
+ },
+ ast::NameRef(name_ref) => {
+ match NameRefClass::classify(sema, &name_ref)? {
+ NameRefClass::Definition(it) => res.push(it),
+ NameRefClass::FieldShorthand { local_ref, field_ref } => {
+ res.push(Definition::Local(local_ref));
+ res.push(Definition::Field(field_ref));
+ }
+ }
+ },
+ ast::Lifetime(lifetime) => {
+ let def = if let Some(x) = NameClass::classify_lifetime(&sema, &lifetime) {
+ NameClass::defined(x)
+ } else {
+ NameRefClass::classify_lifetime(&sema, &lifetime).and_then(|class| match class {
+ NameRefClass::Definition(it) => Some(it),
+ _ => None,
+ })
+ };
+ if let Some(def) = def {
+ res.push(def);
+ }
+ },
+ _ => (),
+ }
}
- }
+ Some(())
+ })();
+ res
}
pub fn module(&self, db: &RootDatabase) -> Option<Module> {
diff --git a/crates/syntax/src/ast/node_ext.rs b/crates/syntax/src/ast/node_ext.rs
index 930e5ce74f..4070406702 100644
--- a/crates/syntax/src/ast/node_ext.rs
+++ b/crates/syntax/src/ast/node_ext.rs
@@ -796,6 +796,16 @@ impl ast::TokenTree {
.into_token()
.filter(|it| matches!(it.kind(), T!['}'] | T![')'] | T![']']))
}
+
+ pub fn parent_meta(&self) -> Option<ast::Meta> {
+ self.syntax().parent().and_then(ast::Meta::cast)
+ }
+}
+
+impl ast::Meta {
+ pub fn parent_attr(&self) -> Option<ast::Attr> {
+ self.syntax().parent().and_then(ast::Attr::cast)
+ }
}
impl ast::GenericParamList {