1796: Support completion for macros r=matklad a=uHOOCCOOHu

This is based on #1795 , and fixes #1727 

Also prettify hover text of macros.

Some screenshorts below:

Completion in item place.
<img width="416" alt="Screenshot_20190910_134056" src="https://user-images.githubusercontent.com/14816024/64587159-fa72da00-d3d0-11e9-86bb-c98f169ec08d.png">

After pressing `tab`.
<img width="313" alt="Screenshot_20190910_134111" src="https://user-images.githubusercontent.com/14816024/64587160-fa72da00-d3d0-11e9-9464-21e3f6957bd7.png">

Complete macros from `std`.
<img width="588" alt="Screenshot_20190910_134147" src="https://user-images.githubusercontent.com/14816024/64587161-fb0b7080-d3d0-11e9-866e-5161f0d1b546.png">

Hover text.
<img width="521" alt="Screenshot_20190910_134242" src="https://user-images.githubusercontent.com/14816024/64587162-fb0b7080-d3d0-11e9-8f09-ad17e3f6702a.png">



Co-authored-by: uHOOCCOOHu <hooccooh1896@gmail.com>
This commit is contained in:
bors[bot] 2019-09-11 14:49:57 +00:00 committed by GitHub
commit 6ce6744e18
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 334 additions and 8 deletions

View file

@ -416,7 +416,7 @@ impl CrateDefMap {
);
}
// Since it is a quantified path here, it should not contains legacy macros
// Since it is a qualified path here, it should not contains legacy macros
match self[module.module_id].scope.get(&segment.name) {
Some(res) => res.def,
_ => {

View file

@ -430,7 +430,7 @@ fn macro_use_can_be_aliased() {
}
#[test]
fn path_quantified_macros() {
fn path_qualified_macros() {
let map = def_map(
"
//- /main.rs

View file

@ -2839,7 +2839,7 @@ fn main() {
}
#[test]
fn infer_path_quantified_macros_expanded() {
fn infer_path_qualified_macros_expanded() {
assert_snapshot!(
infer(r#"
#[macro_export]

View file

@ -12,6 +12,7 @@ mod complete_snippet;
mod complete_path;
mod complete_scope;
mod complete_postfix;
mod complete_macro_in_item_position;
use ra_db::SourceDatabase;
@ -69,5 +70,6 @@ pub(crate) fn completions(db: &db::RootDatabase, position: FilePosition) -> Opti
complete_record_pattern::complete_record_pattern(&mut acc, &ctx);
complete_pattern::complete_pattern(&mut acc, &ctx);
complete_postfix::complete_postfix(&mut acc, &ctx);
complete_macro_in_item_position::complete_macro_in_item_position(&mut acc, &ctx);
Some(acc)
}

View file

@ -0,0 +1,50 @@
use crate::completion::{CompletionContext, Completions};
pub(super) fn complete_macro_in_item_position(acc: &mut Completions, ctx: &CompletionContext) {
// Show only macros in top level.
if ctx.is_new_item {
for (name, res) in ctx.analyzer.all_names(ctx.db) {
if res.get_macros().is_some() {
acc.add_resolution(ctx, name.to_string(), &res.only_macros());
}
}
}
}
#[cfg(test)]
mod tests {
use crate::completion::{do_completion, CompletionItem, CompletionKind};
use insta::assert_debug_snapshot;
fn do_reference_completion(code: &str) -> Vec<CompletionItem> {
do_completion(code, CompletionKind::Reference)
}
#[test]
fn completes_macros_as_item() {
assert_debug_snapshot!(
do_reference_completion(
"
//- /main.rs
macro_rules! foo {
() => {}
}
fn foo() {}
<|>
"
),
@r##"[
CompletionItem {
label: "foo",
source_range: [46; 46),
delete: [46; 46),
insert: "foo!",
kind: Macro,
detail: "macro_rules! foo",
},
]"##
);
}
}

View file

@ -584,6 +584,42 @@ mod tests {
kind: Function,
detail: "fn foo()",
},
]"###
);
}
#[test]
fn completes_qualified_macros() {
assert_debug_snapshot!(
do_reference_completion(
"
#[macro_export]
macro_rules! foo {
() => {}
}
fn main() {
let _ = crate::<|>
}
"
),
@r###"[
CompletionItem {
label: "foo",
source_range: [179; 179),
delete: [179; 179),
insert: "foo!",
kind: Macro,
detail: "#[macro_export]\nmacro_rules! foo",
},
CompletionItem {
label: "main",
source_range: [179; 179),
delete: [179; 179),
insert: "main()$0",
kind: Function,
detail: "fn main()",
},
]"###
);
}

View file

@ -532,4 +532,196 @@ mod tests {
]"#
);
}
#[test]
fn completes_macros_as_value() {
assert_debug_snapshot!(
do_reference_completion(
"
//- /main.rs
macro_rules! foo {
() => {}
}
#[macro_use]
mod m1 {
macro_rules! bar {
() => {}
}
}
mod m2 {
macro_rules! nope {
() => {}
}
#[macro_export]
macro_rules! baz {
() => {}
}
}
fn main() {
let v = <|>
}
"
),
@r##"[
CompletionItem {
label: "bar",
source_range: [252; 252),
delete: [252; 252),
insert: "bar!",
kind: Macro,
detail: "macro_rules! bar",
},
CompletionItem {
label: "baz",
source_range: [252; 252),
delete: [252; 252),
insert: "baz!",
kind: Macro,
detail: "#[macro_export]\nmacro_rules! baz",
},
CompletionItem {
label: "foo",
source_range: [252; 252),
delete: [252; 252),
insert: "foo!",
kind: Macro,
detail: "macro_rules! foo",
},
CompletionItem {
label: "m1",
source_range: [252; 252),
delete: [252; 252),
insert: "m1",
kind: Module,
},
CompletionItem {
label: "m2",
source_range: [252; 252),
delete: [252; 252),
insert: "m2",
kind: Module,
},
CompletionItem {
label: "main",
source_range: [252; 252),
delete: [252; 252),
insert: "main()$0",
kind: Function,
detail: "fn main()",
},
]"##
);
}
#[test]
fn completes_both_macro_and_value() {
assert_debug_snapshot!(
do_reference_completion(
"
//- /main.rs
macro_rules! foo {
() => {}
}
fn foo() {
<|>
}
"
),
@r##"[
CompletionItem {
label: "foo",
source_range: [49; 49),
delete: [49; 49),
insert: "foo!",
kind: Macro,
detail: "macro_rules! foo",
},
CompletionItem {
label: "foo",
source_range: [49; 49),
delete: [49; 49),
insert: "foo()$0",
kind: Function,
detail: "fn foo()",
},
]"##
);
}
#[test]
fn completes_macros_as_type() {
assert_debug_snapshot!(
do_reference_completion(
"
//- /main.rs
macro_rules! foo {
() => {}
}
fn main() {
let x: <|>
}
"
),
@r##"[
CompletionItem {
label: "foo",
source_range: [57; 57),
delete: [57; 57),
insert: "foo!",
kind: Macro,
detail: "macro_rules! foo",
},
CompletionItem {
label: "main",
source_range: [57; 57),
delete: [57; 57),
insert: "main()$0",
kind: Function,
detail: "fn main()",
},
]"##
);
}
#[test]
fn completes_macros_as_stmt() {
assert_debug_snapshot!(
do_reference_completion(
"
//- /main.rs
macro_rules! foo {
() => {}
}
fn main() {
<|>
}
"
),
@r##"[
CompletionItem {
label: "foo",
source_range: [50; 50),
delete: [50; 50),
insert: "foo!",
kind: Macro,
detail: "macro_rules! foo",
},
CompletionItem {
label: "main",
source_range: [50; 50),
delete: [50; 50),
insert: "main()$0",
kind: Function,
detail: "fn main()",
},
]"##
);
}
}

View file

@ -8,7 +8,7 @@ use crate::completion::{
CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, Completions,
};
use crate::display::{const_label, function_label, type_label};
use crate::display::{const_label, function_label, macro_label, type_label};
impl Completions {
pub(crate) fn add_field(
@ -43,8 +43,14 @@ impl Completions {
) {
use hir::ModuleDef::*;
if let Some(macro_) = resolution.get_macros() {
self.add_macro(ctx, Some(local_name.clone()), macro_);
}
let def = resolution.as_ref().take_types().or_else(|| resolution.as_ref().take_values());
let def = match def {
// Only insert once if it is just a macro name
None if resolution.get_macros().is_some() => return,
None => {
self.add(CompletionItem::new(
CompletionKind::Reference,
@ -98,6 +104,22 @@ impl Completions {
self.add_function_with_name(ctx, None, func)
}
fn add_macro(&mut self, ctx: &CompletionContext, name: Option<String>, macro_: hir::MacroDef) {
let ast_node = macro_.source(ctx.db).ast;
if let Some(name) = name {
let detail = macro_label(&ast_node);
let builder =
CompletionItem::new(CompletionKind::Reference, ctx.source_range(), name.clone())
.kind(CompletionItemKind::Macro)
.set_documentation(macro_.docs(ctx.db))
.detail(detail)
.insert_snippet(format!("{}!", name));
self.add(builder);
}
}
fn add_function_with_name(
&mut self,
ctx: &CompletionContext,

View file

@ -7,7 +7,7 @@ mod structure;
mod short_label;
use ra_syntax::{
ast::{self, AstNode, TypeParamsOwner},
ast::{self, AstNode, AttrsOwner, NameOwner, TypeParamsOwner},
SyntaxKind::{ATTR, COMMENT},
};
@ -61,6 +61,12 @@ pub(crate) fn where_predicates<N: TypeParamsOwner>(node: &N) -> Vec<String> {
res
}
pub(crate) fn macro_label(node: &ast::MacroCall) -> String {
let name = node.name().map(|name| name.syntax().text().to_string()).unwrap_or_default();
let vis = if node.has_atom_attr("macro_export") { "#[macro_export]\n" } else { "" };
format!("{}macro_rules! {}", vis, name)
}
pub(crate) fn rust_code_markup<CODE: AsRef<str>>(val: CODE) -> String {
rust_code_markup_with_doc::<_, &str>(val, None)
}

View file

@ -12,8 +12,8 @@ use ra_syntax::{
use crate::{
db::RootDatabase,
display::{
description_from_symbol, docs_from_symbol, rust_code_markup, rust_code_markup_with_doc,
ShortLabel,
description_from_symbol, docs_from_symbol, macro_label, rust_code_markup,
rust_code_markup_with_doc, ShortLabel,
},
name_ref_kind::{classify_name_ref, NameRefKind::*},
FilePosition, FileRange, RangeInfo,
@ -108,7 +108,7 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeIn
Some(Method(it)) => res.extend(from_def_source(db, it)),
Some(Macro(it)) => {
let src = it.source(db);
res.extend(hover_text(src.ast.doc_comment_text(), None));
res.extend(hover_text(src.ast.doc_comment_text(), Some(macro_label(&src.ast))));
}
Some(FieldAccess(it)) => {
let src = it.source(db);
@ -700,4 +700,22 @@ fn func(foo: i32) { if true { <|>foo; }; }
assert_eq!(trim_markup_opt(hover.info.first()), Some("i32"));
assert_eq!(hover.info.is_exact(), true);
}
#[test]
fn test_hover_macro_invocation() {
let (analysis, position) = single_file_with_position(
"
macro_rules! foo {
() => {}
}
fn f() {
fo<|>o!();
}
",
);
let hover = analysis.hover(position).unwrap().unwrap();
assert_eq!(trim_markup_opt(hover.info.first()), Some("macro_rules! foo"));
assert_eq!(hover.info.is_exact(), true);
}
}