mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-10-01 11:31:15 +00:00
Add doc-alias based completion
This commit is contained in:
parent
7a98e24777
commit
0863389dd1
@ -7,6 +7,7 @@ use cfg::{CfgExpr, CfgOptions};
|
|||||||
use either::Either;
|
use either::Either;
|
||||||
use hir_expand::{
|
use hir_expand::{
|
||||||
attrs::{collect_attrs, Attr, AttrId, RawAttrs},
|
attrs::{collect_attrs, Attr, AttrId, RawAttrs},
|
||||||
|
name::{AsName, Name},
|
||||||
HirFileId, InFile,
|
HirFileId, InFile,
|
||||||
};
|
};
|
||||||
use itertools::Itertools;
|
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 {
|
pub fn is_proc_macro(&self) -> bool {
|
||||||
self.by_key("proc_macro").exists()
|
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 {
|
impl AttrsWithOwner {
|
||||||
pub(crate) fn attrs_query(db: &dyn DefDatabase, def: AttrDefId) -> Self {
|
pub(crate) fn attrs_query(db: &dyn DefDatabase, def: AttrDefId) -> Self {
|
||||||
// FIXME: this should use `Trace` to avoid duplication in `source_map` below
|
// 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;
|
mod test_db;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod macro_expansion_tests;
|
mod macro_expansion_tests;
|
||||||
|
#[cfg(test)]
|
||||||
|
mod attr_tests;
|
||||||
mod pretty;
|
mod pretty;
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
|
@ -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![]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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<syntax::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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
// CompletionContext construction
|
||||||
|
@ -353,6 +353,7 @@ 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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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,
|
||||||
@ -424,6 +426,8 @@ impl Builder {
|
|||||||
}
|
}
|
||||||
} else if let Some(trait_name) = self.trait_name {
|
} else if let Some(trait_name) = self.trait_name {
|
||||||
label = SmolStr::from(format!("{label} (as {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 {
|
let text_edit = match self.text_edit {
|
||||||
@ -459,6 +463,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…
x
Reference in New Issue
Block a user