aboutsummaryrefslogtreecommitdiff
path: root/crates/completion
diff options
context:
space:
mode:
Diffstat (limited to 'crates/completion')
-rw-r--r--crates/completion/src/completions/complete_magic.rs66
-rw-r--r--crates/completion/src/render/macro_.rs72
2 files changed, 99 insertions, 39 deletions
diff --git a/crates/completion/src/completions/complete_magic.rs b/crates/completion/src/completions/complete_magic.rs
index 15af2190d..4cf21e19d 100644
--- a/crates/completion/src/completions/complete_magic.rs
+++ b/crates/completion/src/completions/complete_magic.rs
@@ -1,7 +1,8 @@
1//! TODO kb move this into the complete_unqualified_path when starts to work properly 1//! TODO kb move this into the complete_unqualified_path when starts to work properly
2 2
3use assists::utils::{insert_use, mod_path_to_ast, ImportScope, MergeBehaviour}; 3use assists::utils::{insert_use, mod_path_to_ast, ImportScope, MergeBehaviour};
4use hir::Query; 4use either::Either;
5use hir::{db::HirDatabase, MacroDef, ModuleDef, Query};
5use itertools::Itertools; 6use itertools::Itertools;
6use syntax::{algo, AstNode}; 7use syntax::{algo, AstNode};
7use text_edit::TextEdit; 8use text_edit::TextEdit;
@@ -28,14 +29,23 @@ pub(crate) fn complete_magic(acc: &mut Completions, ctx: &CompletionContext) ->
28 // TODO kb use imports_locator instead? 29 // TODO kb use imports_locator instead?
29 .query_external_importables(ctx.db, Query::new(&potential_import_name).limit(40)) 30 .query_external_importables(ctx.db, Query::new(&potential_import_name).limit(40))
30 .unique() 31 .unique()
31 .filter_map(|import_candidate| match import_candidate { 32 .filter_map(|import_candidate| {
32 either::Either::Left(module_def) => current_module.find_use_path(ctx.db, module_def), 33 let use_path = match import_candidate {
33 either::Either::Right(macro_def) => current_module.find_use_path(ctx.db, macro_def), 34 Either::Left(module_def) => current_module.find_use_path(ctx.db, module_def),
35 Either::Right(macro_def) => current_module.find_use_path(ctx.db, macro_def),
36 }?;
37 // TODO kb need to omit braces when there are some already.
38 // maybe remove braces completely?
39 Some((use_path, additional_completion(ctx.db, import_candidate)))
34 }) 40 })
35 .filter_map(|mod_path| { 41 .filter_map(|(mod_path, additional_completion)| {
36 let mut builder = TextEdit::builder(); 42 let mut builder = TextEdit::builder();
37 43
38 let correct_qualifier = mod_path.segments.last()?.to_string(); 44 let correct_qualifier = format!(
45 "{}{}",
46 mod_path.segments.last()?,
47 additional_completion.unwrap_or_default()
48 );
39 builder.replace(anchor.syntax().text_range(), correct_qualifier); 49 builder.replace(anchor.syntax().text_range(), correct_qualifier);
40 50
41 // TODO kb: assists already have the merge behaviour setting, need to unite both 51 // TODO kb: assists already have the merge behaviour setting, need to unite both
@@ -60,6 +70,21 @@ pub(crate) fn complete_magic(acc: &mut Completions, ctx: &CompletionContext) ->
60 Some(()) 70 Some(())
61} 71}
62 72
73fn additional_completion(
74 db: &dyn HirDatabase,
75 import_candidate: Either<ModuleDef, MacroDef>,
76) -> Option<String> {
77 match import_candidate {
78 Either::Left(ModuleDef::Function(_)) => Some("()".to_string()),
79 Either::Right(macro_def) => {
80 let (left_brace, right_brace) =
81 crate::render::macro_::guess_macro_braces(db, macro_def);
82 Some(format!("!{}{}", left_brace, right_brace))
83 }
84 _ => None,
85 }
86}
87
63#[cfg(test)] 88#[cfg(test)]
64mod tests { 89mod tests {
65 use crate::test_utils::check_edit; 90 use crate::test_utils::check_edit;
@@ -83,7 +108,34 @@ fn main() {
83use dep::io::stdin; 108use dep::io::stdin;
84 109
85fn main() { 110fn main() {
86 stdin 111 stdin()
112}
113"#,
114 );
115 }
116
117 #[test]
118 fn macro_magic_completion() {
119 check_edit(
120 "dep::macro_with_curlies",
121 r#"
122//- /lib.rs crate:dep
123/// Please call me as macro_with_curlies! {}
124#[macro_export]
125macro_rules! macro_with_curlies {
126 () => {}
127}
128
129//- /main.rs crate:main deps:dep
130fn main() {
131 curli<|>
132}
133"#,
134 r#"
135use dep::macro_with_curlies;
136
137fn main() {
138 macro_with_curlies! {}
87} 139}
88"#, 140"#,
89 ); 141 );
diff --git a/crates/completion/src/render/macro_.rs b/crates/completion/src/render/macro_.rs
index 96be59cc3..b41c00b98 100644
--- a/crates/completion/src/render/macro_.rs
+++ b/crates/completion/src/render/macro_.rs
@@ -1,6 +1,6 @@
1//! Renderer for macro invocations. 1//! Renderer for macro invocations.
2 2
3use hir::{Documentation, HasSource}; 3use hir::{db::HirDatabase, Documentation, HasAttrs, HasSource};
4use syntax::display::macro_label; 4use syntax::display::macro_label;
5use test_utils::mark; 5use test_utils::mark;
6 6
@@ -27,12 +27,48 @@ struct MacroRender<'a> {
27 ket: &'static str, 27 ket: &'static str,
28} 28}
29 29
30pub fn guess_macro_braces(
31 db: &dyn HirDatabase,
32 macro_: hir::MacroDef,
33) -> (&'static str, &'static str) {
34 let macro_name = match macro_.name(db) {
35 Some(name) => name.to_string(),
36 None => return ("(", ")"),
37 };
38 let macro_docs = macro_.docs(db);
39 let macro_docs = macro_docs.as_ref().map(Documentation::as_str).unwrap_or("");
40
41 let mut votes = [0, 0, 0];
42 for (idx, s) in macro_docs.match_indices(&macro_name) {
43 let (before, after) = (&macro_docs[..idx], &macro_docs[idx + s.len()..]);
44 // Ensure to match the full word
45 if after.starts_with('!')
46 && !before.ends_with(|c: char| c == '_' || c.is_ascii_alphanumeric())
47 {
48 // It may have spaces before the braces like `foo! {}`
49 match after[1..].chars().find(|&c| !c.is_whitespace()) {
50 Some('{') => votes[0] += 1,
51 Some('[') => votes[1] += 1,
52 Some('(') => votes[2] += 1,
53 _ => {}
54 }
55 }
56 }
57
58 // Insert a space before `{}`.
59 // We prefer the last one when some votes equal.
60 let (_vote, (bra, ket)) = votes
61 .iter()
62 .zip(&[(" {", "}"), ("[", "]"), ("(", ")")])
63 .max_by_key(|&(&vote, _)| vote)
64 .unwrap();
65 (*bra, *ket)
66}
67
30impl<'a> MacroRender<'a> { 68impl<'a> MacroRender<'a> {
31 fn new(ctx: RenderContext<'a>, name: String, macro_: hir::MacroDef) -> MacroRender<'a> { 69 fn new(ctx: RenderContext<'a>, name: String, macro_: hir::MacroDef) -> MacroRender<'a> {
32 let docs = ctx.docs(macro_); 70 let docs = ctx.docs(macro_);
33 let docs_str = docs.as_ref().map_or("", |s| s.as_str()); 71 let (bra, ket) = guess_macro_braces(ctx.db(), macro_);
34 let (bra, ket) = guess_macro_braces(&name, docs_str);
35
36 MacroRender { ctx, name, macro_, docs, bra, ket } 72 MacroRender { ctx, name, macro_, docs, bra, ket }
37 } 73 }
38 74
@@ -97,34 +133,6 @@ impl<'a> MacroRender<'a> {
97 } 133 }
98} 134}
99 135
100fn guess_macro_braces(macro_name: &str, docs: &str) -> (&'static str, &'static str) {
101 let mut votes = [0, 0, 0];
102 for (idx, s) in docs.match_indices(&macro_name) {
103 let (before, after) = (&docs[..idx], &docs[idx + s.len()..]);
104 // Ensure to match the full word
105 if after.starts_with('!')
106 && !before.ends_with(|c: char| c == '_' || c.is_ascii_alphanumeric())
107 {
108 // It may have spaces before the braces like `foo! {}`
109 match after[1..].chars().find(|&c| !c.is_whitespace()) {
110 Some('{') => votes[0] += 1,
111 Some('[') => votes[1] += 1,
112 Some('(') => votes[2] += 1,
113 _ => {}
114 }
115 }
116 }
117
118 // Insert a space before `{}`.
119 // We prefer the last one when some votes equal.
120 let (_vote, (bra, ket)) = votes
121 .iter()
122 .zip(&[(" {", "}"), ("[", "]"), ("(", ")")])
123 .max_by_key(|&(&vote, _)| vote)
124 .unwrap();
125 (*bra, *ket)
126}
127
128#[cfg(test)] 136#[cfg(test)]
129mod tests { 137mod tests {
130 use test_utils::mark; 138 use test_utils::mark;