diff options
author | Kirill Bulatov <[email protected]> | 2020-11-13 19:25:45 +0000 |
---|---|---|
committer | Kirill Bulatov <[email protected]> | 2020-11-16 19:19:06 +0000 |
commit | 1598740292c29613ef2b384a82de3a2735bc5566 (patch) | |
tree | bc2451b9fe6cf7943fcbcaaa762c9591c0bad9c6 | |
parent | 4c8edd003aa447bd2da10fd81b24f582deacdc11 (diff) |
Reuse existing element rendering
-rw-r--r-- | crates/completion/src/completions/magic.rs | 82 | ||||
-rw-r--r-- | crates/completion/src/item.rs | 4 | ||||
-rw-r--r-- | crates/completion/src/render/macro_.rs | 72 | ||||
-rw-r--r-- | crates/text_edit/src/lib.rs | 4 |
4 files changed, 74 insertions, 88 deletions
diff --git a/crates/completion/src/completions/magic.rs b/crates/completion/src/completions/magic.rs index 272c9a354..ef0fc27ba 100644 --- a/crates/completion/src/completions/magic.rs +++ b/crates/completion/src/completions/magic.rs | |||
@@ -2,12 +2,14 @@ | |||
2 | 2 | ||
3 | use assists::utils::{insert_use, mod_path_to_ast, ImportScope}; | 3 | use assists::utils::{insert_use, mod_path_to_ast, ImportScope}; |
4 | use either::Either; | 4 | use either::Either; |
5 | use hir::{db::HirDatabase, MacroDef, ModuleDef}; | 5 | use hir::ScopeDef; |
6 | use ide_db::imports_locator; | 6 | use ide_db::imports_locator; |
7 | use syntax::{algo, AstNode}; | 7 | use syntax::{algo, AstNode}; |
8 | use text_edit::TextEdit; | ||
9 | 8 | ||
10 | use crate::{context::CompletionContext, item::CompletionKind, CompletionItem, CompletionItemKind}; | 9 | use crate::{ |
10 | context::CompletionContext, | ||
11 | render::{render_resolution, RenderContext}, | ||
12 | }; | ||
11 | 13 | ||
12 | use super::Completions; | 14 | use super::Completions; |
13 | 15 | ||
@@ -25,57 +27,41 @@ pub(crate) fn complete_magic(acc: &mut Completions, ctx: &CompletionContext) -> | |||
25 | let possible_imports = | 27 | let possible_imports = |
26 | imports_locator::find_similar_imports(&ctx.sema, ctx.krate?, &potential_import_name) | 28 | imports_locator::find_similar_imports(&ctx.sema, ctx.krate?, &potential_import_name) |
27 | .filter_map(|import_candidate| { | 29 | .filter_map(|import_candidate| { |
28 | let use_path = match import_candidate { | 30 | Some(match import_candidate { |
29 | Either::Left(module_def) => current_module.find_use_path(ctx.db, module_def), | 31 | Either::Left(module_def) => ( |
30 | Either::Right(macro_def) => current_module.find_use_path(ctx.db, macro_def), | 32 | current_module.find_use_path(ctx.db, module_def)?, |
31 | }?; | 33 | ScopeDef::ModuleDef(module_def), |
32 | Some((use_path, additional_completion(ctx.db, import_candidate))) | 34 | ), |
35 | Either::Right(macro_def) => ( | ||
36 | current_module.find_use_path(ctx.db, macro_def)?, | ||
37 | ScopeDef::MacroDef(macro_def), | ||
38 | ), | ||
39 | }) | ||
33 | }) | 40 | }) |
34 | .filter_map(|(mod_path, additional_completion)| { | 41 | .filter_map(|(mod_path, definition)| { |
35 | let mut builder = TextEdit::builder(); | 42 | let mut resolution_with_missing_import = render_resolution( |
43 | RenderContext::new(ctx), | ||
44 | mod_path.segments.last()?.to_string(), | ||
45 | &definition, | ||
46 | )?; | ||
36 | 47 | ||
37 | let correct_qualifier = format!( | 48 | let mut text_edits = |
38 | "{}{}", | 49 | resolution_with_missing_import.text_edit().to_owned().into_builder(); |
39 | mod_path.segments.last()?, | ||
40 | additional_completion.unwrap_or_default() | ||
41 | ); | ||
42 | builder.replace(anchor.syntax().text_range(), correct_qualifier); | ||
43 | 50 | ||
44 | let rewriter = | 51 | let rewriter = |
45 | insert_use(&import_scope, mod_path_to_ast(&mod_path), ctx.config.merge); | 52 | insert_use(&import_scope, mod_path_to_ast(&mod_path), ctx.config.merge); |
46 | let old_ast = rewriter.rewrite_root()?; | 53 | let old_ast = rewriter.rewrite_root()?; |
47 | algo::diff(&old_ast, &rewriter.rewrite(&old_ast)).into_text_edit(&mut builder); | 54 | algo::diff(&old_ast, &rewriter.rewrite(&old_ast)).into_text_edit(&mut text_edits); |
48 | 55 | ||
49 | let completion_item: CompletionItem = CompletionItem::new( | 56 | resolution_with_missing_import.update_text_edit(text_edits.finish()); |
50 | CompletionKind::Magic, | 57 | |
51 | ctx.source_range(), | 58 | Some(resolution_with_missing_import) |
52 | mod_path.to_string(), | ||
53 | ) | ||
54 | .kind(CompletionItemKind::Struct) | ||
55 | .text_edit(builder.finish()) | ||
56 | .into(); | ||
57 | Some(completion_item) | ||
58 | }); | 59 | }); |
59 | acc.add_all(possible_imports); | ||
60 | 60 | ||
61 | acc.add_all(possible_imports); | ||
61 | Some(()) | 62 | Some(()) |
62 | } | 63 | } |
63 | 64 | ||
64 | fn additional_completion( | ||
65 | db: &dyn HirDatabase, | ||
66 | import_candidate: Either<ModuleDef, MacroDef>, | ||
67 | ) -> Option<String> { | ||
68 | match import_candidate { | ||
69 | Either::Left(ModuleDef::Function(_)) => Some("()".to_string()), | ||
70 | Either::Right(macro_def) => { | ||
71 | let (left_brace, right_brace) = | ||
72 | crate::render::macro_::guess_macro_braces(db, macro_def); | ||
73 | Some(format!("!{}{}", left_brace, right_brace)) | ||
74 | } | ||
75 | _ => None, | ||
76 | } | ||
77 | } | ||
78 | |||
79 | #[cfg(test)] | 65 | #[cfg(test)] |
80 | mod tests { | 66 | mod tests { |
81 | use crate::test_utils::check_edit; | 67 | use crate::test_utils::check_edit; |
@@ -83,7 +69,7 @@ mod tests { | |||
83 | #[test] | 69 | #[test] |
84 | fn function_magic_completion() { | 70 | fn function_magic_completion() { |
85 | check_edit( | 71 | check_edit( |
86 | "dep::io::stdin", | 72 | "stdin", |
87 | r#" | 73 | r#" |
88 | //- /lib.rs crate:dep | 74 | //- /lib.rs crate:dep |
89 | pub mod io { | 75 | pub mod io { |
@@ -99,7 +85,7 @@ fn main() { | |||
99 | use dep::io::stdin; | 85 | use dep::io::stdin; |
100 | 86 | ||
101 | fn main() { | 87 | fn main() { |
102 | stdin() | 88 | stdin()$0 |
103 | } | 89 | } |
104 | "#, | 90 | "#, |
105 | ); | 91 | ); |
@@ -108,7 +94,7 @@ fn main() { | |||
108 | #[test] | 94 | #[test] |
109 | fn macro_magic_completion() { | 95 | fn macro_magic_completion() { |
110 | check_edit( | 96 | check_edit( |
111 | "dep::macro_with_curlies", | 97 | "macro_with_curlies!", |
112 | r#" | 98 | r#" |
113 | //- /lib.rs crate:dep | 99 | //- /lib.rs crate:dep |
114 | /// Please call me as macro_with_curlies! {} | 100 | /// Please call me as macro_with_curlies! {} |
@@ -126,7 +112,7 @@ fn main() { | |||
126 | use dep::macro_with_curlies; | 112 | use dep::macro_with_curlies; |
127 | 113 | ||
128 | fn main() { | 114 | fn main() { |
129 | macro_with_curlies! {} | 115 | macro_with_curlies! {$0} |
130 | } | 116 | } |
131 | "#, | 117 | "#, |
132 | ); | 118 | ); |
@@ -135,7 +121,7 @@ fn main() { | |||
135 | #[test] | 121 | #[test] |
136 | fn case_insensitive_magic_completion_works() { | 122 | fn case_insensitive_magic_completion_works() { |
137 | check_edit( | 123 | check_edit( |
138 | "dep::some_module::ThirdStruct", | 124 | "ThirdStruct", |
139 | r#" | 125 | r#" |
140 | //- /lib.rs crate:dep | 126 | //- /lib.rs crate:dep |
141 | pub struct FirstStruct; | 127 | pub struct FirstStruct; |
diff --git a/crates/completion/src/item.rs b/crates/completion/src/item.rs index f23913935..53a12a763 100644 --- a/crates/completion/src/item.rs +++ b/crates/completion/src/item.rs | |||
@@ -218,6 +218,10 @@ impl CompletionItem { | |||
218 | &self.text_edit | 218 | &self.text_edit |
219 | } | 219 | } |
220 | 220 | ||
221 | pub fn update_text_edit(&mut self, new_text_edit: TextEdit) { | ||
222 | self.text_edit = new_text_edit; | ||
223 | } | ||
224 | |||
221 | /// Short one-line additional information, like a type | 225 | /// Short one-line additional information, like a type |
222 | pub fn detail(&self) -> Option<&str> { | 226 | pub fn detail(&self) -> Option<&str> { |
223 | self.detail.as_deref() | 227 | self.detail.as_deref() |
diff --git a/crates/completion/src/render/macro_.rs b/crates/completion/src/render/macro_.rs index b41c00b98..96be59cc3 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::{db::HirDatabase, Documentation, HasAttrs, HasSource}; | 3 | use hir::{Documentation, 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,48 +27,12 @@ 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 | |||
68 | impl<'a> MacroRender<'a> { | 30 | impl<'a> MacroRender<'a> { |
69 | fn new(ctx: RenderContext<'a>, name: String, macro_: hir::MacroDef) -> MacroRender<'a> { | 31 | fn new(ctx: RenderContext<'a>, name: String, macro_: hir::MacroDef) -> MacroRender<'a> { |
70 | let docs = ctx.docs(macro_); | 32 | let docs = ctx.docs(macro_); |
71 | let (bra, ket) = guess_macro_braces(ctx.db(), macro_); | 33 | let docs_str = docs.as_ref().map_or("", |s| s.as_str()); |
34 | let (bra, ket) = guess_macro_braces(&name, docs_str); | ||
35 | |||
72 | MacroRender { ctx, name, macro_, docs, bra, ket } | 36 | MacroRender { ctx, name, macro_, docs, bra, ket } |
73 | } | 37 | } |
74 | 38 | ||
@@ -133,6 +97,34 @@ impl<'a> MacroRender<'a> { | |||
133 | } | 97 | } |
134 | } | 98 | } |
135 | 99 | ||
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 | |||
136 | #[cfg(test)] | 128 | #[cfg(test)] |
137 | mod tests { | 129 | mod tests { |
138 | use test_utils::mark; | 130 | use test_utils::mark; |
diff --git a/crates/text_edit/src/lib.rs b/crates/text_edit/src/lib.rs index eb3c8caa2..9eef7a890 100644 --- a/crates/text_edit/src/lib.rs +++ b/crates/text_edit/src/lib.rs | |||
@@ -48,6 +48,10 @@ impl TextEdit { | |||
48 | TextEditBuilder::default() | 48 | TextEditBuilder::default() |
49 | } | 49 | } |
50 | 50 | ||
51 | pub fn into_builder(self) -> TextEditBuilder { | ||
52 | TextEditBuilder { indels: self.indels } | ||
53 | } | ||
54 | |||
51 | pub fn insert(offset: TextSize, text: String) -> TextEdit { | 55 | pub fn insert(offset: TextSize, text: String) -> TextEdit { |
52 | let mut builder = TextEdit::builder(); | 56 | let mut builder = TextEdit::builder(); |
53 | builder.insert(offset, text); | 57 | builder.insert(offset, text); |