mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-10-02 22:54:58 +00:00
Auto merge of #14433 - hecatia-elegua:alias-based-completion, r=Veykril
Add doc-alias based completion Closes #14406. I adapted the parsing code from the CfgExpr parsing code, maybe there's a better abstraction for both, or attribute parsing in general. It also includes `doc(hidden)`-parsing, which means it could replace the other function. There are a few tests for parsing. `process_all_names` changed the most, I added some docs there to explain what happens. Many call sites just pass an empy vec to `add_path_resolution`'s `doc_aliases`, since either it doesn't make sense to pass anything (e.g. visibility completion) or I don't know where to get them from. Shouldn't really matter, as it will just not show aliases if the vec is empty and we can extend alias completion in these cases later. I added two tests in `special.rs` for struct name completion (which was the main thing I wanted). I also tried function and field names, but these don't work yet. I want to add those in a follow-up PR.
This commit is contained in:
commit
265f83031f
17 changed files with 334 additions and 41 deletions
|
@ -1,5 +1,8 @@
|
||||||
//! A higher level attributes based on TokenTree, with also some shortcuts.
|
//! A higher level attributes based on TokenTree, with also some shortcuts.
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
|
||||||
use std::{hash::Hash, ops, sync::Arc};
|
use std::{hash::Hash, ops, sync::Arc};
|
||||||
|
|
||||||
use base_db::CrateId;
|
use base_db::CrateId;
|
||||||
|
@ -245,6 +248,14 @@ impl Attrs {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn doc_exprs(&self) -> impl Iterator<Item = DocExpr> + '_ {
|
||||||
|
self.by_key("doc").tt_values().map(DocExpr::parse)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn doc_aliases(&self) -> impl Iterator<Item = SmolStr> + '_ {
|
||||||
|
self.doc_exprs().flat_map(|doc_expr| doc_expr.aliases().to_vec())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn is_proc_macro(&self) -> bool {
|
pub fn is_proc_macro(&self) -> bool {
|
||||||
self.by_key("proc_macro").exists()
|
self.by_key("proc_macro").exists()
|
||||||
}
|
}
|
||||||
|
@ -258,6 +269,107 @@ impl Attrs {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
use std::slice::Iter as SliceIter;
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
|
||||||
|
pub enum DocAtom {
|
||||||
|
/// eg. `#[doc(hidden)]`
|
||||||
|
Flag(SmolStr),
|
||||||
|
/// eg. `#[doc(alias = "x")]`
|
||||||
|
///
|
||||||
|
/// Note that a key can have multiple values that are all considered "active" at the same time.
|
||||||
|
/// For example, `#[doc(alias = "x")]` and `#[doc(alias = "y")]`.
|
||||||
|
KeyValue { key: SmolStr, value: SmolStr },
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adapted from `CfgExpr` parsing code
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
// #[cfg_attr(test, derive(derive_arbitrary::Arbitrary))]
|
||||||
|
pub enum DocExpr {
|
||||||
|
Invalid,
|
||||||
|
/// eg. `#[doc(hidden)]`, `#[doc(alias = "x")]`
|
||||||
|
Atom(DocAtom),
|
||||||
|
/// eg. `#[doc(alias("x", "y"))]`
|
||||||
|
Alias(Vec<SmolStr>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<DocAtom> for DocExpr {
|
||||||
|
fn from(atom: DocAtom) -> Self {
|
||||||
|
DocExpr::Atom(atom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DocExpr {
|
||||||
|
fn parse<S>(tt: &tt::Subtree<S>) -> DocExpr {
|
||||||
|
next_doc_expr(&mut tt.token_trees.iter()).unwrap_or(DocExpr::Invalid)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn aliases(&self) -> &[SmolStr] {
|
||||||
|
match self {
|
||||||
|
DocExpr::Atom(DocAtom::KeyValue { key, value }) if key == "alias" => {
|
||||||
|
std::slice::from_ref(value)
|
||||||
|
}
|
||||||
|
DocExpr::Alias(aliases) => aliases,
|
||||||
|
_ => &[],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next_doc_expr<S>(it: &mut SliceIter<'_, tt::TokenTree<S>>) -> Option<DocExpr> {
|
||||||
|
let name = match it.next() {
|
||||||
|
None => return None,
|
||||||
|
Some(tt::TokenTree::Leaf(tt::Leaf::Ident(ident))) => ident.text.clone(),
|
||||||
|
Some(_) => return Some(DocExpr::Invalid),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Peek
|
||||||
|
let ret = match it.as_slice().first() {
|
||||||
|
Some(tt::TokenTree::Leaf(tt::Leaf::Punct(punct))) if punct.char == '=' => {
|
||||||
|
match it.as_slice().get(1) {
|
||||||
|
Some(tt::TokenTree::Leaf(tt::Leaf::Literal(literal))) => {
|
||||||
|
it.next();
|
||||||
|
it.next();
|
||||||
|
// FIXME: escape? raw string?
|
||||||
|
let value =
|
||||||
|
SmolStr::new(literal.text.trim_start_matches('"').trim_end_matches('"'));
|
||||||
|
DocAtom::KeyValue { key: name, value }.into()
|
||||||
|
}
|
||||||
|
_ => return Some(DocExpr::Invalid),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(tt::TokenTree::Subtree(subtree)) => {
|
||||||
|
it.next();
|
||||||
|
let subs = parse_comma_sep(subtree);
|
||||||
|
match name.as_str() {
|
||||||
|
"alias" => DocExpr::Alias(subs),
|
||||||
|
_ => DocExpr::Invalid,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => DocAtom::Flag(name).into(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Eat comma separator
|
||||||
|
if let Some(tt::TokenTree::Leaf(tt::Leaf::Punct(punct))) = it.as_slice().first() {
|
||||||
|
if punct.char == ',' {
|
||||||
|
it.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_comma_sep<S>(subtree: &tt::Subtree<S>) -> Vec<SmolStr> {
|
||||||
|
subtree
|
||||||
|
.token_trees
|
||||||
|
.iter()
|
||||||
|
.filter_map(|tt| match tt {
|
||||||
|
tt::TokenTree::Leaf(tt::Leaf::Literal(lit)) => {
|
||||||
|
// FIXME: escape? raw string?
|
||||||
|
Some(SmolStr::new(lit.text.trim_start_matches('"').trim_end_matches('"')))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
impl AttrsWithOwner {
|
impl AttrsWithOwner {
|
||||||
pub(crate) fn attrs_query(db: &dyn DefDatabase, def: AttrDefId) -> Self {
|
pub(crate) fn attrs_query(db: &dyn DefDatabase, def: AttrDefId) -> Self {
|
||||||
let _p = profile::span("attrs_query");
|
let _p = profile::span("attrs_query");
|
||||||
|
|
40
crates/hir-def/src/attr/tests.rs
Normal file
40
crates/hir-def/src/attr/tests.rs
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
//! This module contains tests for doc-expression parsing.
|
||||||
|
//! Currently, it tests `#[doc(hidden)]` and `#[doc(alias)]`.
|
||||||
|
|
||||||
|
use mbe::syntax_node_to_token_tree;
|
||||||
|
use syntax::{ast, AstNode};
|
||||||
|
|
||||||
|
use crate::attr::{DocAtom, DocExpr};
|
||||||
|
|
||||||
|
fn assert_parse_result(input: &str, expected: DocExpr) {
|
||||||
|
let (tt, _) = {
|
||||||
|
let source_file = ast::SourceFile::parse(input).ok().unwrap();
|
||||||
|
let tt = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap();
|
||||||
|
syntax_node_to_token_tree(tt.syntax())
|
||||||
|
};
|
||||||
|
let cfg = DocExpr::parse(&tt);
|
||||||
|
assert_eq!(cfg, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_doc_expr_parser() {
|
||||||
|
assert_parse_result("#![doc(hidden)]", DocAtom::Flag("hidden".into()).into());
|
||||||
|
|
||||||
|
assert_parse_result(
|
||||||
|
r#"#![doc(alias = "foo")]"#,
|
||||||
|
DocAtom::KeyValue { key: "alias".into(), value: "foo".into() }.into(),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_parse_result(r#"#![doc(alias("foo"))]"#, DocExpr::Alias(["foo".into()].into()));
|
||||||
|
assert_parse_result(
|
||||||
|
r#"#![doc(alias("foo", "bar", "baz"))]"#,
|
||||||
|
DocExpr::Alias(["foo".into(), "bar".into(), "baz".into()].into()),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_parse_result(
|
||||||
|
r#"
|
||||||
|
#[doc(alias("Bar", "Qux"))]
|
||||||
|
struct Foo;"#,
|
||||||
|
DocExpr::Alias(["Bar".into(), "Qux".into()].into()),
|
||||||
|
);
|
||||||
|
}
|
|
@ -1644,6 +1644,7 @@ impl<'a> SemanticsScope<'a> {
|
||||||
VisibleTraits(resolver.traits_in_scope(self.db.upcast()))
|
VisibleTraits(resolver.traits_in_scope(self.db.upcast()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Calls the passed closure `f` on all names in scope.
|
||||||
pub fn process_all_names(&self, f: &mut dyn FnMut(Name, ScopeDef)) {
|
pub fn process_all_names(&self, f: &mut dyn FnMut(Name, ScopeDef)) {
|
||||||
let scope = self.resolver.names_in_scope(self.db.upcast());
|
let scope = self.resolver.names_in_scope(self.db.upcast());
|
||||||
for (name, entries) in scope {
|
for (name, entries) in scope {
|
||||||
|
|
|
@ -165,9 +165,9 @@ impl Completions {
|
||||||
ctx: &CompletionContext<'_>,
|
ctx: &CompletionContext<'_>,
|
||||||
path_ctx: &PathCompletionCtx,
|
path_ctx: &PathCompletionCtx,
|
||||||
) {
|
) {
|
||||||
ctx.process_all_names(&mut |name, res| match res {
|
ctx.process_all_names(&mut |name, res, doc_aliases| match res {
|
||||||
ScopeDef::ModuleDef(hir::ModuleDef::Module(m)) if m.is_crate_root(ctx.db) => {
|
ScopeDef::ModuleDef(hir::ModuleDef::Module(m)) if m.is_crate_root(ctx.db) => {
|
||||||
self.add_module(ctx, path_ctx, m, name);
|
self.add_module(ctx, path_ctx, m, name, doc_aliases);
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
});
|
});
|
||||||
|
@ -179,6 +179,7 @@ impl Completions {
|
||||||
path_ctx: &PathCompletionCtx,
|
path_ctx: &PathCompletionCtx,
|
||||||
local_name: hir::Name,
|
local_name: hir::Name,
|
||||||
resolution: hir::ScopeDef,
|
resolution: hir::ScopeDef,
|
||||||
|
doc_aliases: Vec<syntax::SmolStr>,
|
||||||
) {
|
) {
|
||||||
let is_private_editable = match ctx.def_is_visible(&resolution) {
|
let is_private_editable = match ctx.def_is_visible(&resolution) {
|
||||||
Visible::Yes => false,
|
Visible::Yes => false,
|
||||||
|
@ -187,7 +188,9 @@ impl Completions {
|
||||||
};
|
};
|
||||||
self.add(
|
self.add(
|
||||||
render_path_resolution(
|
render_path_resolution(
|
||||||
RenderContext::new(ctx).private_editable(is_private_editable),
|
RenderContext::new(ctx)
|
||||||
|
.private_editable(is_private_editable)
|
||||||
|
.doc_aliases(doc_aliases),
|
||||||
path_ctx,
|
path_ctx,
|
||||||
local_name,
|
local_name,
|
||||||
resolution,
|
resolution,
|
||||||
|
@ -236,12 +239,14 @@ impl Completions {
|
||||||
path_ctx: &PathCompletionCtx,
|
path_ctx: &PathCompletionCtx,
|
||||||
module: hir::Module,
|
module: hir::Module,
|
||||||
local_name: hir::Name,
|
local_name: hir::Name,
|
||||||
|
doc_aliases: Vec<syntax::SmolStr>,
|
||||||
) {
|
) {
|
||||||
self.add_path_resolution(
|
self.add_path_resolution(
|
||||||
ctx,
|
ctx,
|
||||||
path_ctx,
|
path_ctx,
|
||||||
local_name,
|
local_name,
|
||||||
hir::ScopeDef::ModuleDef(module.into()),
|
hir::ScopeDef::ModuleDef(module.into()),
|
||||||
|
doc_aliases,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -93,7 +93,7 @@ pub(crate) fn complete_attribute_path(
|
||||||
acc.add_macro(ctx, path_ctx, m, name)
|
acc.add_macro(ctx, path_ctx, m, name)
|
||||||
}
|
}
|
||||||
hir::ScopeDef::ModuleDef(hir::ModuleDef::Module(m)) => {
|
hir::ScopeDef::ModuleDef(hir::ModuleDef::Module(m)) => {
|
||||||
acc.add_module(ctx, path_ctx, m, name)
|
acc.add_module(ctx, path_ctx, m, name, vec![])
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
@ -104,12 +104,12 @@ pub(crate) fn complete_attribute_path(
|
||||||
Qualified::Absolute => acc.add_crate_roots(ctx, path_ctx),
|
Qualified::Absolute => acc.add_crate_roots(ctx, path_ctx),
|
||||||
// only show modules in a fresh UseTree
|
// only show modules in a fresh UseTree
|
||||||
Qualified::No => {
|
Qualified::No => {
|
||||||
ctx.process_all_names(&mut |name, def| match def {
|
ctx.process_all_names(&mut |name, def, doc_aliases| match def {
|
||||||
hir::ScopeDef::ModuleDef(hir::ModuleDef::Macro(m)) if m.is_attr(ctx.db) => {
|
hir::ScopeDef::ModuleDef(hir::ModuleDef::Macro(m)) if m.is_attr(ctx.db) => {
|
||||||
acc.add_macro(ctx, path_ctx, m, name)
|
acc.add_macro(ctx, path_ctx, m, name)
|
||||||
}
|
}
|
||||||
hir::ScopeDef::ModuleDef(hir::ModuleDef::Module(m)) => {
|
hir::ScopeDef::ModuleDef(hir::ModuleDef::Module(m)) => {
|
||||||
acc.add_module(ctx, path_ctx, m, name)
|
acc.add_module(ctx, path_ctx, m, name, doc_aliases)
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
});
|
});
|
||||||
|
|
|
@ -34,7 +34,7 @@ pub(crate) fn complete_derive_path(
|
||||||
acc.add_macro(ctx, path_ctx, mac, name)
|
acc.add_macro(ctx, path_ctx, mac, name)
|
||||||
}
|
}
|
||||||
ScopeDef::ModuleDef(hir::ModuleDef::Module(m)) => {
|
ScopeDef::ModuleDef(hir::ModuleDef::Module(m)) => {
|
||||||
acc.add_module(ctx, path_ctx, m, name)
|
acc.add_module(ctx, path_ctx, m, name, vec![])
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
@ -43,7 +43,7 @@ pub(crate) fn complete_derive_path(
|
||||||
Qualified::Absolute => acc.add_crate_roots(ctx, path_ctx),
|
Qualified::Absolute => acc.add_crate_roots(ctx, path_ctx),
|
||||||
// only show modules in a fresh UseTree
|
// only show modules in a fresh UseTree
|
||||||
Qualified::No => {
|
Qualified::No => {
|
||||||
ctx.process_all_names(&mut |name, def| {
|
ctx.process_all_names(&mut |name, def, doc_aliases| {
|
||||||
let mac = match def {
|
let mac = match def {
|
||||||
ScopeDef::ModuleDef(hir::ModuleDef::Macro(mac))
|
ScopeDef::ModuleDef(hir::ModuleDef::Macro(mac))
|
||||||
if !existing_derives.contains(&mac) && mac.is_derive(ctx.db) =>
|
if !existing_derives.contains(&mac) && mac.is_derive(ctx.db) =>
|
||||||
|
@ -51,7 +51,7 @@ pub(crate) fn complete_derive_path(
|
||||||
mac
|
mac
|
||||||
}
|
}
|
||||||
ScopeDef::ModuleDef(hir::ModuleDef::Module(m)) => {
|
ScopeDef::ModuleDef(hir::ModuleDef::Module(m)) => {
|
||||||
return acc.add_module(ctx, path_ctx, m, name);
|
return acc.add_module(ctx, path_ctx, m, name, doc_aliases);
|
||||||
}
|
}
|
||||||
_ => return,
|
_ => return,
|
||||||
};
|
};
|
||||||
|
|
|
@ -88,7 +88,7 @@ pub(crate) fn complete_expr_path(
|
||||||
let module_scope = module.scope(ctx.db, Some(ctx.module));
|
let module_scope = module.scope(ctx.db, Some(ctx.module));
|
||||||
for (name, def) in module_scope {
|
for (name, def) in module_scope {
|
||||||
if scope_def_applicable(def) {
|
if scope_def_applicable(def) {
|
||||||
acc.add_path_resolution(ctx, path_ctx, name, def);
|
acc.add_path_resolution(ctx, path_ctx, name, def, vec![]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -212,7 +212,7 @@ pub(crate) fn complete_expr_path(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ctx.process_all_names(&mut |name, def| match def {
|
ctx.process_all_names(&mut |name, def, doc_aliases| match def {
|
||||||
ScopeDef::ModuleDef(hir::ModuleDef::Trait(t)) => {
|
ScopeDef::ModuleDef(hir::ModuleDef::Trait(t)) => {
|
||||||
let assocs = t.items_with_supertraits(ctx.db);
|
let assocs = t.items_with_supertraits(ctx.db);
|
||||||
match &*assocs {
|
match &*assocs {
|
||||||
|
@ -220,12 +220,14 @@ pub(crate) fn complete_expr_path(
|
||||||
// there is no associated item path that can be constructed with them
|
// there is no associated item path that can be constructed with them
|
||||||
[] => (),
|
[] => (),
|
||||||
// FIXME: Render the assoc item with the trait qualified
|
// FIXME: Render the assoc item with the trait qualified
|
||||||
&[_item] => acc.add_path_resolution(ctx, path_ctx, name, def),
|
&[_item] => acc.add_path_resolution(ctx, path_ctx, name, def, doc_aliases),
|
||||||
// FIXME: Append `::` to the thing here, since a trait on its own won't work
|
// FIXME: Append `::` to the thing here, since a trait on its own won't work
|
||||||
[..] => acc.add_path_resolution(ctx, path_ctx, name, def),
|
[..] => acc.add_path_resolution(ctx, path_ctx, name, def, doc_aliases),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ if scope_def_applicable(def) => acc.add_path_resolution(ctx, path_ctx, name, def),
|
_ if scope_def_applicable(def) => {
|
||||||
|
acc.add_path_resolution(ctx, path_ctx, name, def, doc_aliases)
|
||||||
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -45,7 +45,7 @@ pub(crate) fn complete_item_list(
|
||||||
acc.add_macro(ctx, path_ctx, m, name)
|
acc.add_macro(ctx, path_ctx, m, name)
|
||||||
}
|
}
|
||||||
hir::ScopeDef::ModuleDef(hir::ModuleDef::Module(m)) => {
|
hir::ScopeDef::ModuleDef(hir::ModuleDef::Module(m)) => {
|
||||||
acc.add_module(ctx, path_ctx, m, name)
|
acc.add_module(ctx, path_ctx, m, name, vec![])
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
@ -55,12 +55,12 @@ pub(crate) fn complete_item_list(
|
||||||
}
|
}
|
||||||
Qualified::Absolute => acc.add_crate_roots(ctx, path_ctx),
|
Qualified::Absolute => acc.add_crate_roots(ctx, path_ctx),
|
||||||
Qualified::No if ctx.qualifier_ctx.none() => {
|
Qualified::No if ctx.qualifier_ctx.none() => {
|
||||||
ctx.process_all_names(&mut |name, def| match def {
|
ctx.process_all_names(&mut |name, def, doc_aliases| match def {
|
||||||
hir::ScopeDef::ModuleDef(hir::ModuleDef::Macro(m)) if m.is_fn_like(ctx.db) => {
|
hir::ScopeDef::ModuleDef(hir::ModuleDef::Macro(m)) if m.is_fn_like(ctx.db) => {
|
||||||
acc.add_macro(ctx, path_ctx, m, name)
|
acc.add_macro(ctx, path_ctx, m, name)
|
||||||
}
|
}
|
||||||
hir::ScopeDef::ModuleDef(hir::ModuleDef::Module(m)) => {
|
hir::ScopeDef::ModuleDef(hir::ModuleDef::Module(m)) => {
|
||||||
acc.add_module(ctx, path_ctx, m, name)
|
acc.add_module(ctx, path_ctx, m, name, doc_aliases)
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
});
|
});
|
||||||
|
|
|
@ -64,7 +64,7 @@ pub(crate) fn complete_pattern(
|
||||||
|
|
||||||
// FIXME: ideally, we should look at the type we are matching against and
|
// FIXME: ideally, we should look at the type we are matching against and
|
||||||
// suggest variants + auto-imports
|
// suggest variants + auto-imports
|
||||||
ctx.process_all_names(&mut |name, res| {
|
ctx.process_all_names(&mut |name, res, _| {
|
||||||
let add_simple_path = match res {
|
let add_simple_path = match res {
|
||||||
hir::ScopeDef::ModuleDef(def) => match def {
|
hir::ScopeDef::ModuleDef(def) => match def {
|
||||||
hir::ModuleDef::Adt(hir::Adt::Struct(strukt)) => {
|
hir::ModuleDef::Adt(hir::Adt::Struct(strukt)) => {
|
||||||
|
@ -127,7 +127,7 @@ pub(crate) fn complete_pattern_path(
|
||||||
};
|
};
|
||||||
|
|
||||||
if add_resolution {
|
if add_resolution {
|
||||||
acc.add_path_resolution(ctx, path_ctx, name, def);
|
acc.add_path_resolution(ctx, path_ctx, name, def, vec![]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -164,7 +164,7 @@ pub(crate) fn complete_pattern_path(
|
||||||
Qualified::Absolute => acc.add_crate_roots(ctx, path_ctx),
|
Qualified::Absolute => acc.add_crate_roots(ctx, path_ctx),
|
||||||
Qualified::No => {
|
Qualified::No => {
|
||||||
// this will only be hit if there are brackets or braces, otherwise this will be parsed as an ident pattern
|
// this will only be hit if there are brackets or braces, otherwise this will be parsed as an ident pattern
|
||||||
ctx.process_all_names(&mut |name, res| {
|
ctx.process_all_names(&mut |name, res, doc_aliases| {
|
||||||
// FIXME: we should check what kind of pattern we are in and filter accordingly
|
// FIXME: we should check what kind of pattern we are in and filter accordingly
|
||||||
let add_completion = match res {
|
let add_completion = match res {
|
||||||
ScopeDef::ModuleDef(hir::ModuleDef::Macro(mac)) => mac.is_fn_like(ctx.db),
|
ScopeDef::ModuleDef(hir::ModuleDef::Macro(mac)) => mac.is_fn_like(ctx.db),
|
||||||
|
@ -175,7 +175,7 @@ pub(crate) fn complete_pattern_path(
|
||||||
_ => false,
|
_ => false,
|
||||||
};
|
};
|
||||||
if add_completion {
|
if add_completion {
|
||||||
acc.add_path_resolution(ctx, path_ctx, name, res);
|
acc.add_path_resolution(ctx, path_ctx, name, res, doc_aliases);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -85,7 +85,7 @@ pub(crate) fn complete_type_path(
|
||||||
let module_scope = module.scope(ctx.db, Some(ctx.module));
|
let module_scope = module.scope(ctx.db, Some(ctx.module));
|
||||||
for (name, def) in module_scope {
|
for (name, def) in module_scope {
|
||||||
if scope_def_applicable(def) {
|
if scope_def_applicable(def) {
|
||||||
acc.add_path_resolution(ctx, path_ctx, name, def);
|
acc.add_path_resolution(ctx, path_ctx, name, def, vec![]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -141,7 +141,7 @@ pub(crate) fn complete_type_path(
|
||||||
match location {
|
match location {
|
||||||
TypeLocation::TypeBound => {
|
TypeLocation::TypeBound => {
|
||||||
acc.add_nameref_keywords_with_colon(ctx);
|
acc.add_nameref_keywords_with_colon(ctx);
|
||||||
ctx.process_all_names(&mut |name, res| {
|
ctx.process_all_names(&mut |name, res, doc_aliases| {
|
||||||
let add_resolution = match res {
|
let add_resolution = match res {
|
||||||
ScopeDef::ModuleDef(hir::ModuleDef::Macro(mac)) => {
|
ScopeDef::ModuleDef(hir::ModuleDef::Macro(mac)) => {
|
||||||
mac.is_fn_like(ctx.db)
|
mac.is_fn_like(ctx.db)
|
||||||
|
@ -152,7 +152,7 @@ pub(crate) fn complete_type_path(
|
||||||
_ => false,
|
_ => false,
|
||||||
};
|
};
|
||||||
if add_resolution {
|
if add_resolution {
|
||||||
acc.add_path_resolution(ctx, path_ctx, name, res);
|
acc.add_path_resolution(ctx, path_ctx, name, res, doc_aliases);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
|
@ -215,9 +215,9 @@ pub(crate) fn complete_type_path(
|
||||||
};
|
};
|
||||||
|
|
||||||
acc.add_nameref_keywords_with_colon(ctx);
|
acc.add_nameref_keywords_with_colon(ctx);
|
||||||
ctx.process_all_names(&mut |name, def| {
|
ctx.process_all_names(&mut |name, def, doc_aliases| {
|
||||||
if scope_def_applicable(def) {
|
if scope_def_applicable(def) {
|
||||||
acc.add_path_resolution(ctx, path_ctx, name, def);
|
acc.add_path_resolution(ctx, path_ctx, name, def, doc_aliases);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -91,10 +91,10 @@ pub(crate) fn complete_use_path(
|
||||||
// only show modules and non-std enum in a fresh UseTree
|
// only show modules and non-std enum in a fresh UseTree
|
||||||
Qualified::No => {
|
Qualified::No => {
|
||||||
cov_mark::hit!(unqualified_path_selected_only);
|
cov_mark::hit!(unqualified_path_selected_only);
|
||||||
ctx.process_all_names(&mut |name, res| {
|
ctx.process_all_names(&mut |name, res, doc_aliases| {
|
||||||
match res {
|
match res {
|
||||||
ScopeDef::ModuleDef(hir::ModuleDef::Module(module)) => {
|
ScopeDef::ModuleDef(hir::ModuleDef::Module(module)) => {
|
||||||
acc.add_module(ctx, path_ctx, module, name);
|
acc.add_module(ctx, path_ctx, module, name, doc_aliases);
|
||||||
}
|
}
|
||||||
ScopeDef::ModuleDef(hir::ModuleDef::Adt(hir::Adt::Enum(e))) => {
|
ScopeDef::ModuleDef(hir::ModuleDef::Adt(hir::Adt::Enum(e))) => {
|
||||||
// exclude prelude enum
|
// exclude prelude enum
|
||||||
|
|
|
@ -23,7 +23,7 @@ pub(crate) fn complete_vis_path(
|
||||||
if let Some(next) = next_towards_current {
|
if let Some(next) = next_towards_current {
|
||||||
if let Some(name) = next.name(ctx.db) {
|
if let Some(name) = next.name(ctx.db) {
|
||||||
cov_mark::hit!(visibility_qualified);
|
cov_mark::hit!(visibility_qualified);
|
||||||
acc.add_module(ctx, path_ctx, next, name);
|
acc.add_module(ctx, path_ctx, next, name, vec![]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ use ide_db::{
|
||||||
};
|
};
|
||||||
use syntax::{
|
use syntax::{
|
||||||
ast::{self, AttrKind, NameOrNameRef},
|
ast::{self, AttrKind, NameOrNameRef},
|
||||||
AstNode,
|
AstNode, SmolStr,
|
||||||
SyntaxKind::{self, *},
|
SyntaxKind::{self, *},
|
||||||
SyntaxToken, TextRange, TextSize, T,
|
SyntaxToken, TextRange, TextSize, T,
|
||||||
};
|
};
|
||||||
|
@ -491,21 +491,22 @@ impl<'a> CompletionContext<'a> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A version of [`SemanticsScope::process_all_names`] that filters out `#[doc(hidden)]` items.
|
/// A version of [`SemanticsScope::process_all_names`] that filters out `#[doc(hidden)]` items and
|
||||||
pub(crate) fn process_all_names(&self, f: &mut dyn FnMut(Name, ScopeDef)) {
|
/// passes all doc-aliases along, to funnel it into [`Completions::add_path_resolution`].
|
||||||
|
pub(crate) fn process_all_names(&self, f: &mut dyn FnMut(Name, ScopeDef, Vec<SmolStr>)) {
|
||||||
let _p = profile::span("CompletionContext::process_all_names");
|
let _p = profile::span("CompletionContext::process_all_names");
|
||||||
self.scope.process_all_names(&mut |name, def| {
|
self.scope.process_all_names(&mut |name, def| {
|
||||||
if self.is_scope_def_hidden(def) {
|
if self.is_scope_def_hidden(def) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
let doc_aliases = self.doc_aliases(def);
|
||||||
f(name, def);
|
f(name, def, doc_aliases);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn process_all_names_raw(&self, f: &mut dyn FnMut(Name, ScopeDef)) {
|
pub(crate) fn process_all_names_raw(&self, f: &mut dyn FnMut(Name, ScopeDef)) {
|
||||||
let _p = profile::span("CompletionContext::process_all_names_raw");
|
let _p = profile::span("CompletionContext::process_all_names_raw");
|
||||||
self.scope.process_all_names(&mut |name, def| f(name, def));
|
self.scope.process_all_names(f);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_scope_def_hidden(&self, scope_def: ScopeDef) -> bool {
|
fn is_scope_def_hidden(&self, scope_def: ScopeDef) -> bool {
|
||||||
|
@ -545,6 +546,14 @@ impl<'a> CompletionContext<'a> {
|
||||||
// `doc(hidden)` items are only completed within the defining crate.
|
// `doc(hidden)` items are only completed within the defining crate.
|
||||||
self.krate != defining_crate && attrs.has_doc_hidden()
|
self.krate != defining_crate && attrs.has_doc_hidden()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn doc_aliases(&self, scope_def: ScopeDef) -> Vec<SmolStr> {
|
||||||
|
if let Some(attrs) = scope_def.attrs(self.db) {
|
||||||
|
attrs.doc_aliases().collect()
|
||||||
|
} else {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CompletionContext construction
|
// CompletionContext construction
|
||||||
|
|
|
@ -45,7 +45,7 @@ pub struct CompletionItem {
|
||||||
///
|
///
|
||||||
/// That is, in `foo.bar$0` lookup of `abracadabra` will be accepted (it
|
/// That is, in `foo.bar$0` lookup of `abracadabra` will be accepted (it
|
||||||
/// contains `bar` sub sequence), and `quux` will rejected.
|
/// contains `bar` sub sequence), and `quux` will rejected.
|
||||||
pub lookup: Option<SmolStr>,
|
pub lookup: SmolStr,
|
||||||
|
|
||||||
/// Additional info to show in the UI pop up.
|
/// Additional info to show in the UI pop up.
|
||||||
pub detail: Option<String>,
|
pub detail: Option<String>,
|
||||||
|
@ -353,12 +353,13 @@ impl CompletionItem {
|
||||||
relevance: CompletionRelevance::default(),
|
relevance: CompletionRelevance::default(),
|
||||||
ref_match: None,
|
ref_match: None,
|
||||||
imports_to_add: Default::default(),
|
imports_to_add: Default::default(),
|
||||||
|
doc_aliases: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// What string is used for filtering.
|
/// What string is used for filtering.
|
||||||
pub fn lookup(&self) -> &str {
|
pub fn lookup(&self) -> &str {
|
||||||
self.lookup.as_deref().unwrap_or(&self.label)
|
self.lookup.as_str()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ref_match(&self) -> Option<(String, text_edit::Indel, CompletionRelevance)> {
|
pub fn ref_match(&self) -> Option<(String, text_edit::Indel, CompletionRelevance)> {
|
||||||
|
@ -385,6 +386,7 @@ pub(crate) struct Builder {
|
||||||
source_range: TextRange,
|
source_range: TextRange,
|
||||||
imports_to_add: SmallVec<[LocatedImport; 1]>,
|
imports_to_add: SmallVec<[LocatedImport; 1]>,
|
||||||
trait_name: Option<SmolStr>,
|
trait_name: Option<SmolStr>,
|
||||||
|
doc_aliases: Option<SmolStr>,
|
||||||
label: SmolStr,
|
label: SmolStr,
|
||||||
insert_text: Option<String>,
|
insert_text: Option<String>,
|
||||||
is_snippet: bool,
|
is_snippet: bool,
|
||||||
|
@ -413,13 +415,16 @@ impl Builder {
|
||||||
let _p = profile::span("item::Builder::build");
|
let _p = profile::span("item::Builder::build");
|
||||||
|
|
||||||
let mut label = self.label;
|
let mut label = self.label;
|
||||||
let mut lookup = self.lookup;
|
let mut lookup = self.lookup.unwrap_or_else(|| label.clone());
|
||||||
let insert_text = self.insert_text.unwrap_or_else(|| label.to_string());
|
let insert_text = self.insert_text.unwrap_or_else(|| label.to_string());
|
||||||
|
|
||||||
|
if let Some(doc_aliases) = self.doc_aliases {
|
||||||
|
label = SmolStr::from(format!("{label} (alias {doc_aliases})"));
|
||||||
|
lookup = SmolStr::from(format!("{lookup} {doc_aliases}"));
|
||||||
|
}
|
||||||
if let [import_edit] = &*self.imports_to_add {
|
if let [import_edit] = &*self.imports_to_add {
|
||||||
// snippets can have multiple imports, but normal completions only have up to one
|
// snippets can have multiple imports, but normal completions only have up to one
|
||||||
if let Some(original_path) = import_edit.original_path.as_ref() {
|
if let Some(original_path) = import_edit.original_path.as_ref() {
|
||||||
lookup = lookup.or_else(|| Some(label.clone()));
|
|
||||||
label = SmolStr::from(format!("{label} (use {original_path})"));
|
label = SmolStr::from(format!("{label} (use {original_path})"));
|
||||||
}
|
}
|
||||||
} else if let Some(trait_name) = self.trait_name {
|
} else if let Some(trait_name) = self.trait_name {
|
||||||
|
@ -459,6 +464,10 @@ impl Builder {
|
||||||
self.trait_name = Some(trait_name);
|
self.trait_name = Some(trait_name);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
pub(crate) fn doc_aliases(&mut self, doc_aliases: SmolStr) -> &mut Builder {
|
||||||
|
self.doc_aliases = Some(doc_aliases);
|
||||||
|
self
|
||||||
|
}
|
||||||
pub(crate) fn insert_text(&mut self, insert_text: impl Into<String>) -> &mut Builder {
|
pub(crate) fn insert_text(&mut self, insert_text: impl Into<String>) -> &mut Builder {
|
||||||
self.insert_text = Some(insert_text.into());
|
self.insert_text = Some(insert_text.into());
|
||||||
self
|
self
|
||||||
|
|
|
@ -97,7 +97,7 @@ pub use crate::{
|
||||||
|
|
||||||
/// Main entry point for completion. We run completion as a two-phase process.
|
/// Main entry point for completion. We run completion as a two-phase process.
|
||||||
///
|
///
|
||||||
/// First, we look at the position and collect a so-called `CompletionContext.
|
/// First, we look at the position and collect a so-called `CompletionContext`.
|
||||||
/// This is a somewhat messy process, because, during completion, syntax tree is
|
/// This is a somewhat messy process, because, during completion, syntax tree is
|
||||||
/// incomplete and can look really weird.
|
/// incomplete and can look really weird.
|
||||||
///
|
///
|
||||||
|
|
|
@ -14,6 +14,7 @@ use hir::{AsAssocItem, HasAttrs, HirDisplay, ScopeDef};
|
||||||
use ide_db::{
|
use ide_db::{
|
||||||
helpers::item_name, imports::import_assets::LocatedImport, RootDatabase, SnippetCap, SymbolKind,
|
helpers::item_name, imports::import_assets::LocatedImport, RootDatabase, SnippetCap, SymbolKind,
|
||||||
};
|
};
|
||||||
|
use itertools::Itertools;
|
||||||
use syntax::{AstNode, SmolStr, SyntaxKind, TextRange};
|
use syntax::{AstNode, SmolStr, SyntaxKind, TextRange};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -32,11 +33,17 @@ pub(crate) struct RenderContext<'a> {
|
||||||
completion: &'a CompletionContext<'a>,
|
completion: &'a CompletionContext<'a>,
|
||||||
is_private_editable: bool,
|
is_private_editable: bool,
|
||||||
import_to_add: Option<LocatedImport>,
|
import_to_add: Option<LocatedImport>,
|
||||||
|
doc_aliases: Vec<SmolStr>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> RenderContext<'a> {
|
impl<'a> RenderContext<'a> {
|
||||||
pub(crate) fn new(completion: &'a CompletionContext<'a>) -> RenderContext<'a> {
|
pub(crate) fn new(completion: &'a CompletionContext<'a>) -> RenderContext<'a> {
|
||||||
RenderContext { completion, is_private_editable: false, import_to_add: None }
|
RenderContext {
|
||||||
|
completion,
|
||||||
|
is_private_editable: false,
|
||||||
|
import_to_add: None,
|
||||||
|
doc_aliases: vec![],
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn private_editable(mut self, private_editable: bool) -> Self {
|
pub(crate) fn private_editable(mut self, private_editable: bool) -> Self {
|
||||||
|
@ -49,6 +56,11 @@ impl<'a> RenderContext<'a> {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn doc_aliases(mut self, doc_aliases: Vec<SmolStr>) -> Self {
|
||||||
|
self.doc_aliases = doc_aliases;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
fn snippet_cap(&self) -> Option<SnippetCap> {
|
fn snippet_cap(&self) -> Option<SnippetCap> {
|
||||||
self.completion.config.snippet_cap
|
self.completion.config.snippet_cap
|
||||||
}
|
}
|
||||||
|
@ -348,6 +360,12 @@ fn render_resolution_simple_(
|
||||||
if let Some(import_to_add) = ctx.import_to_add {
|
if let Some(import_to_add) = ctx.import_to_add {
|
||||||
item.add_import(import_to_add);
|
item.add_import(import_to_add);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let doc_aliases = ctx.doc_aliases;
|
||||||
|
if !doc_aliases.is_empty() {
|
||||||
|
let doc_aliases = doc_aliases.into_iter().join(", ").into();
|
||||||
|
item.doc_aliases(doc_aliases);
|
||||||
|
}
|
||||||
item
|
item
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -989,3 +989,100 @@ fn foo { crate::::$0 }
|
||||||
expect![""],
|
expect![""],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn completes_struct_via_doc_alias_in_fn_body() {
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
#[doc(alias = "Bar")]
|
||||||
|
struct Foo;
|
||||||
|
|
||||||
|
fn here_we_go() {
|
||||||
|
$0
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
fn here_we_go() fn()
|
||||||
|
st Foo (alias Bar)
|
||||||
|
bt u32
|
||||||
|
kw const
|
||||||
|
kw crate::
|
||||||
|
kw enum
|
||||||
|
kw extern
|
||||||
|
kw false
|
||||||
|
kw fn
|
||||||
|
kw for
|
||||||
|
kw if
|
||||||
|
kw if let
|
||||||
|
kw impl
|
||||||
|
kw let
|
||||||
|
kw loop
|
||||||
|
kw match
|
||||||
|
kw mod
|
||||||
|
kw return
|
||||||
|
kw self::
|
||||||
|
kw static
|
||||||
|
kw struct
|
||||||
|
kw trait
|
||||||
|
kw true
|
||||||
|
kw type
|
||||||
|
kw union
|
||||||
|
kw unsafe
|
||||||
|
kw use
|
||||||
|
kw while
|
||||||
|
kw while let
|
||||||
|
sn macro_rules
|
||||||
|
sn pd
|
||||||
|
sn ppd
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn completes_struct_via_multiple_doc_aliases_in_fn_body() {
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
#[doc(alias("Bar", "Qux"))]
|
||||||
|
#[doc(alias = "Baz")]
|
||||||
|
struct Foo;
|
||||||
|
|
||||||
|
fn here_we_go() {
|
||||||
|
B$0
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
fn here_we_go() fn()
|
||||||
|
st Foo (alias Bar, Qux, Baz)
|
||||||
|
bt u32
|
||||||
|
kw const
|
||||||
|
kw crate::
|
||||||
|
kw enum
|
||||||
|
kw extern
|
||||||
|
kw false
|
||||||
|
kw fn
|
||||||
|
kw for
|
||||||
|
kw if
|
||||||
|
kw if let
|
||||||
|
kw impl
|
||||||
|
kw let
|
||||||
|
kw loop
|
||||||
|
kw match
|
||||||
|
kw mod
|
||||||
|
kw return
|
||||||
|
kw self::
|
||||||
|
kw static
|
||||||
|
kw struct
|
||||||
|
kw trait
|
||||||
|
kw true
|
||||||
|
kw type
|
||||||
|
kw union
|
||||||
|
kw unsafe
|
||||||
|
kw use
|
||||||
|
kw while
|
||||||
|
kw while let
|
||||||
|
sn macro_rules
|
||||||
|
sn pd
|
||||||
|
sn ppd
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue