2039: Guess macro braces from docs r=matklad a=oxalica

Instead of hard-code `vec` to have snippet with bracket `vec![]`, 
we try to find the "most common used brace kind" from documentation of the macro, 
which usually contains some example code.
It also works better with extern macros.

We can suggest braces for `thread_local! {}` now.


Co-authored-by: oxalica <oxalicc@pm.me>
This commit is contained in:
bors[bot] 2019-10-21 11:42:05 +00:00 committed by GitHub
commit e4810a302b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 91 additions and 7 deletions

View file

@ -56,6 +56,16 @@ mod tests {
do_reference_completion(
"
//- /main.rs
/// Creates a [`Vec`] containing the arguments.
///
/// - Create a [`Vec`] containing a given list of elements:
///
/// ```
/// let v = vec![1, 2, 3];
/// assert_eq!(v[0], 1);
/// assert_eq!(v[1], 2);
/// assert_eq!(v[2], 3);
/// ```
macro_rules! vec {
() => {}
}
@ -68,13 +78,61 @@ mod tests {
@r##"[
CompletionItem {
label: "vec!",
source_range: [46; 46),
delete: [46; 46),
source_range: [280; 280),
delete: [280; 280),
insert: "vec![$0]",
kind: Macro,
detail: "macro_rules! vec",
documentation: Documentation(
"Creates a [`Vec`] containing the arguments.\n\n- Create a [`Vec`] containing a given list of elements:\n\n```\nlet v = vec![1, 2, 3];\nassert_eq!(v[0], 1);\nassert_eq!(v[1], 2);\nassert_eq!(v[2], 3);\n```",
),
},
]"##
);
}
#[test]
fn completes_macros_braces_guessing() {
assert_debug_snapshot!(
do_reference_completion(
"
//- /main.rs
/// Foo
///
/// Not call `fooo!()` `fooo!()`, or `_foo![]` `_foo![]`.
/// Call as `let _=foo! { hello world };`
macro_rules! foo {
() => {}
}
fn main() {
<|>
}
"
),
@r###"[
CompletionItem {
label: "foo!",
source_range: [163; 163),
delete: [163; 163),
insert: "foo! {$0}",
kind: Macro,
detail: "macro_rules! foo",
documentation: Documentation(
"Foo\n\nNot call `fooo!()` `fooo!()`, or `_foo![]` `_foo![]`.\nCall as `let _=foo! { hello world };`",
),
},
CompletionItem {
label: "main()",
source_range: [163; 163),
delete: [163; 163),
insert: "main()$0",
kind: Function,
lookup: "main",
detail: "fn main()",
},
]
"###
);
}
}

View file

@ -131,6 +131,33 @@ impl Completions {
self.add_function_with_name(ctx, None, func)
}
fn guess_macro_braces(&self, macro_name: &str, docs: &str) -> &'static str {
let mut votes = [0, 0, 0];
for (idx, s) in docs.match_indices(&macro_name) {
let (before, after) = (&docs[..idx], &docs[idx + s.len()..]);
// Ensure to match the full word
if after.starts_with("!")
&& before
.chars()
.rev()
.next()
.map_or(true, |c| c != '_' && !c.is_ascii_alphanumeric())
{
// It may have spaces before the braces like `foo! {}`
match after[1..].chars().find(|&c| !c.is_whitespace()) {
Some('{') => votes[0] += 1,
Some('[') => votes[1] += 1,
Some('(') => votes[2] += 1,
_ => {}
}
}
}
// Insert a space before `{}`.
// We prefer the last one when some votes equal.
*votes.iter().zip(&[" {$0}", "[$0]", "($0)"]).max_by_key(|&(&vote, _)| vote).unwrap().1
}
pub(crate) fn add_macro(
&mut self,
ctx: &CompletionContext,
@ -141,10 +168,9 @@ impl Completions {
if let Some(name) = name {
let detail = macro_label(&ast_node);
let macro_braces_to_insert = match name.as_str() {
"vec" => "[$0]",
_ => "($0)",
};
let docs = macro_.docs(ctx.db);
let macro_braces_to_insert =
self.guess_macro_braces(&name, docs.as_ref().map_or("", |s| s.as_str()));
let macro_declaration = name + "!";
let builder = CompletionItem::new(
@ -153,7 +179,7 @@ impl Completions {
&macro_declaration,
)
.kind(CompletionItemKind::Macro)
.set_documentation(macro_.docs(ctx.db))
.set_documentation(docs)
.detail(detail)
.insert_snippet(macro_declaration + macro_braces_to_insert);