diff options
-rw-r--r-- | crates/completion/src/completions/complete_magic.rs | 66 | ||||
-rw-r--r-- | crates/completion/src/render/macro_.rs | 72 |
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 | ||
3 | use assists::utils::{insert_use, mod_path_to_ast, ImportScope, MergeBehaviour}; | 3 | use assists::utils::{insert_use, mod_path_to_ast, ImportScope, MergeBehaviour}; |
4 | use hir::Query; | 4 | use either::Either; |
5 | use hir::{db::HirDatabase, MacroDef, ModuleDef, Query}; | ||
5 | use itertools::Itertools; | 6 | use itertools::Itertools; |
6 | use syntax::{algo, AstNode}; | 7 | use syntax::{algo, AstNode}; |
7 | use text_edit::TextEdit; | 8 | use 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 | ||
73 | fn 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)] |
64 | mod tests { | 89 | mod tests { |
65 | use crate::test_utils::check_edit; | 90 | use crate::test_utils::check_edit; |
@@ -83,7 +108,34 @@ fn main() { | |||
83 | use dep::io::stdin; | 108 | use dep::io::stdin; |
84 | 109 | ||
85 | fn main() { | 110 | fn 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] | ||
125 | macro_rules! macro_with_curlies { | ||
126 | () => {} | ||
127 | } | ||
128 | |||
129 | //- /main.rs crate:main deps:dep | ||
130 | fn main() { | ||
131 | curli<|> | ||
132 | } | ||
133 | "#, | ||
134 | r#" | ||
135 | use dep::macro_with_curlies; | ||
136 | |||
137 | fn 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 | ||
3 | use hir::{Documentation, HasSource}; | 3 | use hir::{db::HirDatabase, Documentation, HasAttrs, HasSource}; |
4 | use syntax::display::macro_label; | 4 | use syntax::display::macro_label; |
5 | use test_utils::mark; | 5 | use test_utils::mark; |
6 | 6 | ||
@@ -27,12 +27,48 @@ struct MacroRender<'a> { | |||
27 | ket: &'static str, | 27 | ket: &'static str, |
28 | } | 28 | } |
29 | 29 | ||
30 | pub 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(¯o_name) { | ||
43 | let (before, after) = (¯o_docs[..idx], ¯o_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 | |||
30 | impl<'a> MacroRender<'a> { | 68 | impl<'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 | ||
100 | fn 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(¯o_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)] |
129 | mod tests { | 137 | mod tests { |
130 | use test_utils::mark; | 138 | use test_utils::mark; |