mirror of
https://github.com/rust-lang/rust-analyzer
synced 2025-01-14 14:13: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.
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use std::{hash::Hash, ops, sync::Arc};
|
||||
|
||||
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 {
|
||||
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 {
|
||||
pub(crate) fn attrs_query(db: &dyn DefDatabase, def: AttrDefId) -> Self {
|
||||
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()))
|
||||
}
|
||||
|
||||
/// Calls the passed closure `f` on all names in scope.
|
||||
pub fn process_all_names(&self, f: &mut dyn FnMut(Name, ScopeDef)) {
|
||||
let scope = self.resolver.names_in_scope(self.db.upcast());
|
||||
for (name, entries) in scope {
|
||||
|
|
|
@ -165,9 +165,9 @@ impl Completions {
|
|||
ctx: &CompletionContext<'_>,
|
||||
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) => {
|
||||
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,
|
||||
local_name: hir::Name,
|
||||
resolution: hir::ScopeDef,
|
||||
doc_aliases: Vec<syntax::SmolStr>,
|
||||
) {
|
||||
let is_private_editable = match ctx.def_is_visible(&resolution) {
|
||||
Visible::Yes => false,
|
||||
|
@ -187,7 +188,9 @@ impl Completions {
|
|||
};
|
||||
self.add(
|
||||
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,
|
||||
local_name,
|
||||
resolution,
|
||||
|
@ -236,12 +239,14 @@ impl Completions {
|
|||
path_ctx: &PathCompletionCtx,
|
||||
module: hir::Module,
|
||||
local_name: hir::Name,
|
||||
doc_aliases: Vec<syntax::SmolStr>,
|
||||
) {
|
||||
self.add_path_resolution(
|
||||
ctx,
|
||||
path_ctx,
|
||||
local_name,
|
||||
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)
|
||||
}
|
||||
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),
|
||||
// only show modules in a fresh UseTree
|
||||
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) => {
|
||||
acc.add_macro(ctx, path_ctx, m, name)
|
||||
}
|
||||
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)
|
||||
}
|
||||
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),
|
||||
// only show modules in a fresh UseTree
|
||||
Qualified::No => {
|
||||
ctx.process_all_names(&mut |name, def| {
|
||||
ctx.process_all_names(&mut |name, def, doc_aliases| {
|
||||
let mac = match def {
|
||||
ScopeDef::ModuleDef(hir::ModuleDef::Macro(mac))
|
||||
if !existing_derives.contains(&mac) && mac.is_derive(ctx.db) =>
|
||||
|
@ -51,7 +51,7 @@ pub(crate) fn complete_derive_path(
|
|||
mac
|
||||
}
|
||||
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,
|
||||
};
|
||||
|
|
|
@ -88,7 +88,7 @@ pub(crate) fn complete_expr_path(
|
|||
let module_scope = module.scope(ctx.db, Some(ctx.module));
|
||||
for (name, def) in module_scope {
|
||||
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)) => {
|
||||
let assocs = t.items_with_supertraits(ctx.db);
|
||||
match &*assocs {
|
||||
|
@ -220,12 +220,14 @@ pub(crate) fn complete_expr_path(
|
|||
// there is no associated item path that can be constructed with them
|
||||
[] => (),
|
||||
// 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
|
||||
[..] => 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)
|
||||
}
|
||||
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::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) => {
|
||||
acc.add_macro(ctx, path_ctx, m, name)
|
||||
}
|
||||
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
|
||||
// suggest variants + auto-imports
|
||||
ctx.process_all_names(&mut |name, res| {
|
||||
ctx.process_all_names(&mut |name, res, _| {
|
||||
let add_simple_path = match res {
|
||||
hir::ScopeDef::ModuleDef(def) => match def {
|
||||
hir::ModuleDef::Adt(hir::Adt::Struct(strukt)) => {
|
||||
|
@ -127,7 +127,7 @@ pub(crate) fn complete_pattern_path(
|
|||
};
|
||||
|
||||
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::No => {
|
||||
// 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
|
||||
let add_completion = match res {
|
||||
ScopeDef::ModuleDef(hir::ModuleDef::Macro(mac)) => mac.is_fn_like(ctx.db),
|
||||
|
@ -175,7 +175,7 @@ pub(crate) fn complete_pattern_path(
|
|||
_ => false,
|
||||
};
|
||||
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));
|
||||
for (name, def) in module_scope {
|
||||
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 {
|
||||
TypeLocation::TypeBound => {
|
||||
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 {
|
||||
ScopeDef::ModuleDef(hir::ModuleDef::Macro(mac)) => {
|
||||
mac.is_fn_like(ctx.db)
|
||||
|
@ -152,7 +152,7 @@ pub(crate) fn complete_type_path(
|
|||
_ => false,
|
||||
};
|
||||
if add_resolution {
|
||||
acc.add_path_resolution(ctx, path_ctx, name, res);
|
||||
acc.add_path_resolution(ctx, path_ctx, name, res, doc_aliases);
|
||||
}
|
||||
});
|
||||
return;
|
||||
|
@ -215,9 +215,9 @@ pub(crate) fn complete_type_path(
|
|||
};
|
||||
|
||||
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) {
|
||||
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
|
||||
Qualified::No => {
|
||||
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 {
|
||||
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))) => {
|
||||
// exclude prelude enum
|
||||
|
|
|
@ -23,7 +23,7 @@ pub(crate) fn complete_vis_path(
|
|||
if let Some(next) = next_towards_current {
|
||||
if let Some(name) = next.name(ctx.db) {
|
||||
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::{
|
||||
ast::{self, AttrKind, NameOrNameRef},
|
||||
AstNode,
|
||||
AstNode, SmolStr,
|
||||
SyntaxKind::{self, *},
|
||||
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.
|
||||
pub(crate) fn process_all_names(&self, f: &mut dyn FnMut(Name, ScopeDef)) {
|
||||
/// A version of [`SemanticsScope::process_all_names`] that filters out `#[doc(hidden)]` items and
|
||||
/// 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");
|
||||
self.scope.process_all_names(&mut |name, def| {
|
||||
if self.is_scope_def_hidden(def) {
|
||||
return;
|
||||
}
|
||||
|
||||
f(name, def);
|
||||
let doc_aliases = self.doc_aliases(def);
|
||||
f(name, def, doc_aliases);
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) fn process_all_names_raw(&self, f: &mut dyn FnMut(Name, ScopeDef)) {
|
||||
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 {
|
||||
|
@ -545,6 +546,14 @@ impl<'a> CompletionContext<'a> {
|
|||
// `doc(hidden)` items are only completed within the defining crate.
|
||||
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
|
||||
|
|
|
@ -45,7 +45,7 @@ pub struct CompletionItem {
|
|||
///
|
||||
/// That is, in `foo.bar$0` lookup of `abracadabra` will be accepted (it
|
||||
/// contains `bar` sub sequence), and `quux` will rejected.
|
||||
pub lookup: Option<SmolStr>,
|
||||
pub lookup: SmolStr,
|
||||
|
||||
/// Additional info to show in the UI pop up.
|
||||
pub detail: Option<String>,
|
||||
|
@ -353,12 +353,13 @@ impl CompletionItem {
|
|||
relevance: CompletionRelevance::default(),
|
||||
ref_match: None,
|
||||
imports_to_add: Default::default(),
|
||||
doc_aliases: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// What string is used for filtering.
|
||||
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)> {
|
||||
|
@ -385,6 +386,7 @@ pub(crate) struct Builder {
|
|||
source_range: TextRange,
|
||||
imports_to_add: SmallVec<[LocatedImport; 1]>,
|
||||
trait_name: Option<SmolStr>,
|
||||
doc_aliases: Option<SmolStr>,
|
||||
label: SmolStr,
|
||||
insert_text: Option<String>,
|
||||
is_snippet: bool,
|
||||
|
@ -413,13 +415,16 @@ impl Builder {
|
|||
let _p = profile::span("item::Builder::build");
|
||||
|
||||
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());
|
||||
|
||||
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 {
|
||||
// snippets can have multiple imports, but normal completions only have up to one
|
||||
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})"));
|
||||
}
|
||||
} else if let Some(trait_name) = self.trait_name {
|
||||
|
@ -459,6 +464,10 @@ impl Builder {
|
|||
self.trait_name = Some(trait_name);
|
||||
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 {
|
||||
self.insert_text = Some(insert_text.into());
|
||||
self
|
||||
|
|
|
@ -97,7 +97,7 @@ pub use crate::{
|
|||
|
||||
/// 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
|
||||
/// incomplete and can look really weird.
|
||||
///
|
||||
|
|
|
@ -14,6 +14,7 @@ use hir::{AsAssocItem, HasAttrs, HirDisplay, ScopeDef};
|
|||
use ide_db::{
|
||||
helpers::item_name, imports::import_assets::LocatedImport, RootDatabase, SnippetCap, SymbolKind,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use syntax::{AstNode, SmolStr, SyntaxKind, TextRange};
|
||||
|
||||
use crate::{
|
||||
|
@ -32,11 +33,17 @@ pub(crate) struct RenderContext<'a> {
|
|||
completion: &'a CompletionContext<'a>,
|
||||
is_private_editable: bool,
|
||||
import_to_add: Option<LocatedImport>,
|
||||
doc_aliases: Vec<SmolStr>,
|
||||
}
|
||||
|
||||
impl<'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 {
|
||||
|
@ -49,6 +56,11 @@ impl<'a> RenderContext<'a> {
|
|||
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> {
|
||||
self.completion.config.snippet_cap
|
||||
}
|
||||
|
@ -348,6 +360,12 @@ fn render_resolution_simple_(
|
|||
if let Some(import_to_add) = ctx.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
|
||||
}
|
||||
|
||||
|
|
|
@ -989,3 +989,100 @@ fn foo { crate::::$0 }
|
|||
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…
Reference in a new issue