mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-11-14 17:07:26 +00:00
Add doc-alias based completion
This commit is contained in:
parent
7a98e24777
commit
0863389dd1
18 changed files with 330 additions and 36 deletions
|
@ -7,6 +7,7 @@ use cfg::{CfgExpr, CfgOptions};
|
|||
use either::Either;
|
||||
use hir_expand::{
|
||||
attrs::{collect_attrs, Attr, AttrId, RawAttrs},
|
||||
name::{AsName, Name},
|
||||
HirFileId, InFile,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
|
@ -238,6 +239,17 @@ impl Attrs {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn doc_exprs(&self) -> Vec<DocExpr> {
|
||||
self.by_key("doc").tt_values().map(DocExpr::parse).collect()
|
||||
}
|
||||
|
||||
pub fn doc_aliases(&self) -> Vec<SmolStr> {
|
||||
self.doc_exprs()
|
||||
.into_iter()
|
||||
.flat_map(|doc_expr| doc_expr.aliases())
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn is_proc_macro(&self) -> bool {
|
||||
self.by_key("proc_macro").exists()
|
||||
}
|
||||
|
@ -251,6 +263,106 @@ 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 },
|
||||
}
|
||||
|
||||
#[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 {
|
||||
pub fn parse<S>(tt: &tt::Subtree<S>) -> DocExpr {
|
||||
next_doc_expr(&mut tt.token_trees.iter()).unwrap_or(DocExpr::Invalid)
|
||||
}
|
||||
|
||||
pub fn aliases(self) -> Vec<SmolStr> {
|
||||
match self {
|
||||
DocExpr::Atom(DocAtom::KeyValue { key, value }) if key == "alias" => {
|
||||
vec![value]
|
||||
}
|
||||
DocExpr::Alias(aliases) => aliases,
|
||||
_ => vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
// FIXME: this should use `Trace` to avoid duplication in `source_map` below
|
||||
|
|
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()),
|
||||
);
|
||||
}
|
|
@ -53,6 +53,8 @@ pub mod import_map;
|
|||
mod test_db;
|
||||
#[cfg(test)]
|
||||
mod macro_expansion_tests;
|
||||
#[cfg(test)]
|
||||
mod attr_tests;
|
||||
mod pretty;
|
||||
|
||||
use std::{
|
||||
|
|
|
@ -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![]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<syntax::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()
|
||||
}
|
||||
|
||||
pub fn doc_aliases(&self, scope_def: ScopeDef) -> Vec<syntax::SmolStr> {
|
||||
if let Some(attrs) = scope_def.attrs(self.db) {
|
||||
attrs.doc_aliases()
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CompletionContext construction
|
||||
|
|
|
@ -353,6 +353,7 @@ impl CompletionItem {
|
|||
relevance: CompletionRelevance::default(),
|
||||
ref_match: None,
|
||||
imports_to_add: Default::default(),
|
||||
doc_aliases: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -424,6 +426,8 @@ impl Builder {
|
|||
}
|
||||
} else if let Some(trait_name) = self.trait_name {
|
||||
label = SmolStr::from(format!("{label} (as {trait_name})"));
|
||||
} else if let Some(doc_aliases) = self.doc_aliases {
|
||||
label = SmolStr::from(format!("{label} (alias {doc_aliases})"));
|
||||
}
|
||||
|
||||
let text_edit = match self.text_edit {
|
||||
|
@ -459,6 +463,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