aboutsummaryrefslogtreecommitdiff
path: root/crates/ide_completion/src/render
diff options
context:
space:
mode:
authorAleksey Kladov <[email protected]>2021-02-17 14:53:31 +0000
committerAleksey Kladov <[email protected]>2021-02-17 14:53:31 +0000
commit3db64a400c78bbd2708e67ddc07df1001fff3f29 (patch)
tree5386aab9c452981be09bc3e4362643a34e6e3617 /crates/ide_completion/src/render
parent6334ce866ab095215381c4b72692b20a84d26e96 (diff)
rename completion -> ide_completion
We don't have completion-related PRs in flight, so lets do it
Diffstat (limited to 'crates/ide_completion/src/render')
-rw-r--r--crates/ide_completion/src/render/builder_ext.rs94
-rw-r--r--crates/ide_completion/src/render/const_.rs59
-rw-r--r--crates/ide_completion/src/render/enum_variant.rs131
-rw-r--r--crates/ide_completion/src/render/function.rs345
-rw-r--r--crates/ide_completion/src/render/macro_.rs214
-rw-r--r--crates/ide_completion/src/render/pattern.rs150
-rw-r--r--crates/ide_completion/src/render/type_alias.rs59
7 files changed, 1052 insertions, 0 deletions
diff --git a/crates/ide_completion/src/render/builder_ext.rs b/crates/ide_completion/src/render/builder_ext.rs
new file mode 100644
index 000000000..d053a988b
--- /dev/null
+++ b/crates/ide_completion/src/render/builder_ext.rs
@@ -0,0 +1,94 @@
1//! Extensions for `Builder` structure required for item rendering.
2
3use itertools::Itertools;
4use test_utils::mark;
5
6use crate::{item::Builder, CompletionContext};
7
8#[derive(Debug)]
9pub(super) enum Params {
10 Named(Vec<String>),
11 Anonymous(usize),
12}
13
14impl Params {
15 pub(super) fn len(&self) -> usize {
16 match self {
17 Params::Named(xs) => xs.len(),
18 Params::Anonymous(len) => *len,
19 }
20 }
21
22 pub(super) fn is_empty(&self) -> bool {
23 self.len() == 0
24 }
25}
26
27impl Builder {
28 fn should_add_parens(&self, ctx: &CompletionContext) -> bool {
29 if !ctx.config.add_call_parenthesis {
30 return false;
31 }
32 if ctx.use_item_syntax.is_some() {
33 mark::hit!(no_parens_in_use_item);
34 return false;
35 }
36 if ctx.is_pattern_call {
37 return false;
38 }
39 if ctx.is_call {
40 return false;
41 }
42
43 // Don't add parentheses if the expected type is some function reference.
44 if let Some(ty) = &ctx.expected_type {
45 if ty.is_fn() {
46 mark::hit!(no_call_parens_if_fn_ptr_needed);
47 return false;
48 }
49 }
50
51 // Nothing prevents us from adding parentheses
52 true
53 }
54
55 pub(super) fn add_call_parens(
56 mut self,
57 ctx: &CompletionContext,
58 name: String,
59 params: Params,
60 ) -> Builder {
61 if !self.should_add_parens(ctx) {
62 return self;
63 }
64
65 let cap = match ctx.config.snippet_cap {
66 Some(it) => it,
67 None => return self,
68 };
69 // If not an import, add parenthesis automatically.
70 mark::hit!(inserts_parens_for_function_calls);
71
72 let (snippet, label) = if params.is_empty() {
73 (format!("{}()$0", name), format!("{}()", name))
74 } else {
75 self = self.trigger_call_info();
76 let snippet = match (ctx.config.add_call_argument_snippets, params) {
77 (true, Params::Named(params)) => {
78 let function_params_snippet =
79 params.iter().enumerate().format_with(", ", |(index, param_name), f| {
80 f(&format_args!("${{{}:{}}}", index + 1, param_name))
81 });
82 format!("{}({})$0", name, function_params_snippet)
83 }
84 _ => {
85 mark::hit!(suppress_arg_snippets);
86 format!("{}($0)", name)
87 }
88 };
89
90 (snippet, format!("{}(…)", name))
91 };
92 self.lookup_by(name).label(label).insert_snippet(cap, snippet)
93 }
94}
diff --git a/crates/ide_completion/src/render/const_.rs b/crates/ide_completion/src/render/const_.rs
new file mode 100644
index 000000000..5010b642a
--- /dev/null
+++ b/crates/ide_completion/src/render/const_.rs
@@ -0,0 +1,59 @@
1//! Renderer for `const` fields.
2
3use hir::HasSource;
4use ide_db::SymbolKind;
5use syntax::{
6 ast::{Const, NameOwner},
7 display::const_label,
8};
9
10use crate::{
11 item::{CompletionItem, CompletionKind},
12 render::RenderContext,
13};
14
15pub(crate) fn render_const<'a>(
16 ctx: RenderContext<'a>,
17 const_: hir::Const,
18) -> Option<CompletionItem> {
19 ConstRender::new(ctx, const_)?.render()
20}
21
22#[derive(Debug)]
23struct ConstRender<'a> {
24 ctx: RenderContext<'a>,
25 const_: hir::Const,
26 ast_node: Const,
27}
28
29impl<'a> ConstRender<'a> {
30 fn new(ctx: RenderContext<'a>, const_: hir::Const) -> Option<ConstRender<'a>> {
31 let ast_node = const_.source(ctx.db())?.value;
32 Some(ConstRender { ctx, const_, ast_node })
33 }
34
35 fn render(self) -> Option<CompletionItem> {
36 let name = self.name()?;
37 let detail = self.detail();
38
39 let item = CompletionItem::new(CompletionKind::Reference, self.ctx.source_range(), name)
40 .kind(SymbolKind::Const)
41 .set_documentation(self.ctx.docs(self.const_))
42 .set_deprecated(
43 self.ctx.is_deprecated(self.const_)
44 || self.ctx.is_deprecated_assoc_item(self.const_),
45 )
46 .detail(detail)
47 .build();
48
49 Some(item)
50 }
51
52 fn name(&self) -> Option<String> {
53 self.ast_node.name().map(|name| name.text().to_string())
54 }
55
56 fn detail(&self) -> String {
57 const_label(&self.ast_node)
58 }
59}
diff --git a/crates/ide_completion/src/render/enum_variant.rs b/crates/ide_completion/src/render/enum_variant.rs
new file mode 100644
index 000000000..9214193b4
--- /dev/null
+++ b/crates/ide_completion/src/render/enum_variant.rs
@@ -0,0 +1,131 @@
1//! Renderer for `enum` variants.
2
3use hir::{HasAttrs, HirDisplay, ModPath, StructKind};
4use ide_db::SymbolKind;
5use itertools::Itertools;
6use test_utils::mark;
7
8use crate::{
9 item::{CompletionItem, CompletionKind, ImportEdit},
10 render::{builder_ext::Params, RenderContext},
11};
12
13pub(crate) fn render_variant<'a>(
14 ctx: RenderContext<'a>,
15 import_to_add: Option<ImportEdit>,
16 local_name: Option<String>,
17 variant: hir::Variant,
18 path: Option<ModPath>,
19) -> CompletionItem {
20 let _p = profile::span("render_enum_variant");
21 EnumRender::new(ctx, local_name, variant, path).render(import_to_add)
22}
23
24#[derive(Debug)]
25struct EnumRender<'a> {
26 ctx: RenderContext<'a>,
27 name: String,
28 variant: hir::Variant,
29 path: Option<ModPath>,
30 qualified_name: String,
31 short_qualified_name: String,
32 variant_kind: StructKind,
33}
34
35impl<'a> EnumRender<'a> {
36 fn new(
37 ctx: RenderContext<'a>,
38 local_name: Option<String>,
39 variant: hir::Variant,
40 path: Option<ModPath>,
41 ) -> EnumRender<'a> {
42 let name = local_name.unwrap_or_else(|| variant.name(ctx.db()).to_string());
43 let variant_kind = variant.kind(ctx.db());
44
45 let (qualified_name, short_qualified_name) = match &path {
46 Some(path) => {
47 let full = path.to_string();
48 let segments = path.segments();
49 let short = segments[segments.len().saturating_sub(2)..].iter().join("::");
50 (full, short)
51 }
52 None => (name.to_string(), name.to_string()),
53 };
54
55 EnumRender { ctx, name, variant, path, qualified_name, short_qualified_name, variant_kind }
56 }
57
58 fn render(self, import_to_add: Option<ImportEdit>) -> CompletionItem {
59 let mut builder = CompletionItem::new(
60 CompletionKind::Reference,
61 self.ctx.source_range(),
62 self.qualified_name.clone(),
63 )
64 .kind(SymbolKind::Variant)
65 .set_documentation(self.variant.docs(self.ctx.db()))
66 .set_deprecated(self.ctx.is_deprecated(self.variant))
67 .add_import(import_to_add)
68 .detail(self.detail());
69
70 if self.variant_kind == StructKind::Tuple {
71 mark::hit!(inserts_parens_for_tuple_enums);
72 let params = Params::Anonymous(self.variant.fields(self.ctx.db()).len());
73 builder =
74 builder.add_call_parens(self.ctx.completion, self.short_qualified_name, params);
75 } else if self.path.is_some() {
76 builder = builder.lookup_by(self.short_qualified_name);
77 }
78
79 builder.build()
80 }
81
82 fn detail(&self) -> String {
83 let detail_types = self
84 .variant
85 .fields(self.ctx.db())
86 .into_iter()
87 .map(|field| (field.name(self.ctx.db()), field.signature_ty(self.ctx.db())));
88
89 match self.variant_kind {
90 StructKind::Tuple | StructKind::Unit => format!(
91 "({})",
92 detail_types.map(|(_, t)| t.display(self.ctx.db()).to_string()).format(", ")
93 ),
94 StructKind::Record => format!(
95 "{{ {} }}",
96 detail_types
97 .map(|(n, t)| format!("{}: {}", n, t.display(self.ctx.db()).to_string()))
98 .format(", ")
99 ),
100 }
101 }
102}
103
104#[cfg(test)]
105mod tests {
106 use test_utils::mark;
107
108 use crate::test_utils::check_edit;
109
110 #[test]
111 fn inserts_parens_for_tuple_enums() {
112 mark::check!(inserts_parens_for_tuple_enums);
113 check_edit(
114 "Some",
115 r#"
116enum Option<T> { Some(T), None }
117use Option::*;
118fn main() -> Option<i32> {
119 Som$0
120}
121"#,
122 r#"
123enum Option<T> { Some(T), None }
124use Option::*;
125fn main() -> Option<i32> {
126 Some($0)
127}
128"#,
129 );
130 }
131}
diff --git a/crates/ide_completion/src/render/function.rs b/crates/ide_completion/src/render/function.rs
new file mode 100644
index 000000000..e46e21d24
--- /dev/null
+++ b/crates/ide_completion/src/render/function.rs
@@ -0,0 +1,345 @@
1//! Renderer for function calls.
2
3use hir::{HasSource, HirDisplay, Type};
4use ide_db::SymbolKind;
5use syntax::ast::Fn;
6use test_utils::mark;
7
8use crate::{
9 item::{CompletionItem, CompletionItemKind, CompletionKind, ImportEdit},
10 render::{builder_ext::Params, RenderContext},
11};
12
13pub(crate) fn render_fn<'a>(
14 ctx: RenderContext<'a>,
15 import_to_add: Option<ImportEdit>,
16 local_name: Option<String>,
17 fn_: hir::Function,
18) -> Option<CompletionItem> {
19 let _p = profile::span("render_fn");
20 Some(FunctionRender::new(ctx, local_name, fn_)?.render(import_to_add))
21}
22
23#[derive(Debug)]
24struct FunctionRender<'a> {
25 ctx: RenderContext<'a>,
26 name: String,
27 func: hir::Function,
28 ast_node: Fn,
29}
30
31impl<'a> FunctionRender<'a> {
32 fn new(
33 ctx: RenderContext<'a>,
34 local_name: Option<String>,
35 fn_: hir::Function,
36 ) -> Option<FunctionRender<'a>> {
37 let name = local_name.unwrap_or_else(|| fn_.name(ctx.db()).to_string());
38 let ast_node = fn_.source(ctx.db())?.value;
39
40 Some(FunctionRender { ctx, name, func: fn_, ast_node })
41 }
42
43 fn render(self, import_to_add: Option<ImportEdit>) -> CompletionItem {
44 let params = self.params();
45 CompletionItem::new(CompletionKind::Reference, self.ctx.source_range(), self.name.clone())
46 .kind(self.kind())
47 .set_documentation(self.ctx.docs(self.func))
48 .set_deprecated(
49 self.ctx.is_deprecated(self.func) || self.ctx.is_deprecated_assoc_item(self.func),
50 )
51 .detail(self.detail())
52 .add_call_parens(self.ctx.completion, self.name, params)
53 .add_import(import_to_add)
54 .build()
55 }
56
57 fn detail(&self) -> String {
58 let ty = self.func.ret_type(self.ctx.db());
59 format!("-> {}", ty.display(self.ctx.db()))
60 }
61
62 fn add_arg(&self, arg: &str, ty: &Type) -> String {
63 if let Some(derefed_ty) = ty.remove_ref() {
64 for (name, local) in self.ctx.completion.locals.iter() {
65 if name == arg && local.ty(self.ctx.db()) == derefed_ty {
66 let mutability = if ty.is_mutable_reference() { "&mut " } else { "&" };
67 return format!("{}{}", mutability, arg);
68 }
69 }
70 }
71 arg.to_string()
72 }
73
74 fn params(&self) -> Params {
75 let ast_params = match self.ast_node.param_list() {
76 Some(it) => it,
77 None => return Params::Named(Vec::new()),
78 };
79
80 let mut params_pats = Vec::new();
81 let params_ty = if self.ctx.completion.dot_receiver.is_some() {
82 self.func.method_params(self.ctx.db()).unwrap_or_default()
83 } else {
84 if let Some(s) = ast_params.self_param() {
85 mark::hit!(parens_for_method_call_as_assoc_fn);
86 params_pats.push(Some(s.to_string()));
87 }
88 self.func.assoc_fn_params(self.ctx.db())
89 };
90 params_pats
91 .extend(ast_params.params().into_iter().map(|it| it.pat().map(|it| it.to_string())));
92
93 let params = params_pats
94 .into_iter()
95 .zip(params_ty)
96 .flat_map(|(pat, param_ty)| {
97 let pat = pat?;
98 let name = pat;
99 let arg = name.trim_start_matches("mut ").trim_start_matches('_');
100 Some(self.add_arg(arg, param_ty.ty()))
101 })
102 .collect();
103 Params::Named(params)
104 }
105
106 fn kind(&self) -> CompletionItemKind {
107 if self.func.self_param(self.ctx.db()).is_some() {
108 CompletionItemKind::Method
109 } else {
110 SymbolKind::Function.into()
111 }
112 }
113}
114
115#[cfg(test)]
116mod tests {
117 use test_utils::mark;
118
119 use crate::{
120 test_utils::{check_edit, check_edit_with_config, TEST_CONFIG},
121 CompletionConfig,
122 };
123
124 #[test]
125 fn inserts_parens_for_function_calls() {
126 mark::check!(inserts_parens_for_function_calls);
127 check_edit(
128 "no_args",
129 r#"
130fn no_args() {}
131fn main() { no_$0 }
132"#,
133 r#"
134fn no_args() {}
135fn main() { no_args()$0 }
136"#,
137 );
138
139 check_edit(
140 "with_args",
141 r#"
142fn with_args(x: i32, y: String) {}
143fn main() { with_$0 }
144"#,
145 r#"
146fn with_args(x: i32, y: String) {}
147fn main() { with_args(${1:x}, ${2:y})$0 }
148"#,
149 );
150
151 check_edit(
152 "foo",
153 r#"
154struct S;
155impl S {
156 fn foo(&self) {}
157}
158fn bar(s: &S) { s.f$0 }
159"#,
160 r#"
161struct S;
162impl S {
163 fn foo(&self) {}
164}
165fn bar(s: &S) { s.foo()$0 }
166"#,
167 );
168
169 check_edit(
170 "foo",
171 r#"
172struct S {}
173impl S {
174 fn foo(&self, x: i32) {}
175}
176fn bar(s: &S) {
177 s.f$0
178}
179"#,
180 r#"
181struct S {}
182impl S {
183 fn foo(&self, x: i32) {}
184}
185fn bar(s: &S) {
186 s.foo(${1:x})$0
187}
188"#,
189 );
190 }
191
192 #[test]
193 fn parens_for_method_call_as_assoc_fn() {
194 mark::check!(parens_for_method_call_as_assoc_fn);
195 check_edit(
196 "foo",
197 r#"
198struct S;
199impl S {
200 fn foo(&self) {}
201}
202fn main() { S::f$0 }
203"#,
204 r#"
205struct S;
206impl S {
207 fn foo(&self) {}
208}
209fn main() { S::foo(${1:&self})$0 }
210"#,
211 );
212 }
213
214 #[test]
215 fn suppress_arg_snippets() {
216 mark::check!(suppress_arg_snippets);
217 check_edit_with_config(
218 CompletionConfig { add_call_argument_snippets: false, ..TEST_CONFIG },
219 "with_args",
220 r#"
221fn with_args(x: i32, y: String) {}
222fn main() { with_$0 }
223"#,
224 r#"
225fn with_args(x: i32, y: String) {}
226fn main() { with_args($0) }
227"#,
228 );
229 }
230
231 #[test]
232 fn strips_underscores_from_args() {
233 check_edit(
234 "foo",
235 r#"
236fn foo(_foo: i32, ___bar: bool, ho_ge_: String) {}
237fn main() { f$0 }
238"#,
239 r#"
240fn foo(_foo: i32, ___bar: bool, ho_ge_: String) {}
241fn main() { foo(${1:foo}, ${2:bar}, ${3:ho_ge_})$0 }
242"#,
243 );
244 }
245
246 #[test]
247 fn insert_ref_when_matching_local_in_scope() {
248 check_edit(
249 "ref_arg",
250 r#"
251struct Foo {}
252fn ref_arg(x: &Foo) {}
253fn main() {
254 let x = Foo {};
255 ref_ar$0
256}
257"#,
258 r#"
259struct Foo {}
260fn ref_arg(x: &Foo) {}
261fn main() {
262 let x = Foo {};
263 ref_arg(${1:&x})$0
264}
265"#,
266 );
267 }
268
269 #[test]
270 fn insert_mut_ref_when_matching_local_in_scope() {
271 check_edit(
272 "ref_arg",
273 r#"
274struct Foo {}
275fn ref_arg(x: &mut Foo) {}
276fn main() {
277 let x = Foo {};
278 ref_ar$0
279}
280"#,
281 r#"
282struct Foo {}
283fn ref_arg(x: &mut Foo) {}
284fn main() {
285 let x = Foo {};
286 ref_arg(${1:&mut x})$0
287}
288"#,
289 );
290 }
291
292 #[test]
293 fn insert_ref_when_matching_local_in_scope_for_method() {
294 check_edit(
295 "apply_foo",
296 r#"
297struct Foo {}
298struct Bar {}
299impl Bar {
300 fn apply_foo(&self, x: &Foo) {}
301}
302
303fn main() {
304 let x = Foo {};
305 let y = Bar {};
306 y.$0
307}
308"#,
309 r#"
310struct Foo {}
311struct Bar {}
312impl Bar {
313 fn apply_foo(&self, x: &Foo) {}
314}
315
316fn main() {
317 let x = Foo {};
318 let y = Bar {};
319 y.apply_foo(${1:&x})$0
320}
321"#,
322 );
323 }
324
325 #[test]
326 fn trim_mut_keyword_in_func_completion() {
327 check_edit(
328 "take_mutably",
329 r#"
330fn take_mutably(mut x: &i32) {}
331
332fn main() {
333 take_m$0
334}
335"#,
336 r#"
337fn take_mutably(mut x: &i32) {}
338
339fn main() {
340 take_mutably(${1:x})$0
341}
342"#,
343 );
344 }
345}
diff --git a/crates/ide_completion/src/render/macro_.rs b/crates/ide_completion/src/render/macro_.rs
new file mode 100644
index 000000000..a4535786f
--- /dev/null
+++ b/crates/ide_completion/src/render/macro_.rs
@@ -0,0 +1,214 @@
1//! Renderer for macro invocations.
2
3use hir::{Documentation, HasSource};
4use ide_db::SymbolKind;
5use syntax::display::macro_label;
6use test_utils::mark;
7
8use crate::{
9 item::{CompletionItem, CompletionKind, ImportEdit},
10 render::RenderContext,
11};
12
13pub(crate) fn render_macro<'a>(
14 ctx: RenderContext<'a>,
15 import_to_add: Option<ImportEdit>,
16 name: String,
17 macro_: hir::MacroDef,
18) -> Option<CompletionItem> {
19 let _p = profile::span("render_macro");
20 MacroRender::new(ctx, name, macro_).render(import_to_add)
21}
22
23#[derive(Debug)]
24struct MacroRender<'a> {
25 ctx: RenderContext<'a>,
26 name: String,
27 macro_: hir::MacroDef,
28 docs: Option<Documentation>,
29 bra: &'static str,
30 ket: &'static str,
31}
32
33impl<'a> MacroRender<'a> {
34 fn new(ctx: RenderContext<'a>, name: String, macro_: hir::MacroDef) -> MacroRender<'a> {
35 let docs = ctx.docs(macro_);
36 let docs_str = docs.as_ref().map_or("", |s| s.as_str());
37 let (bra, ket) = guess_macro_braces(&name, docs_str);
38
39 MacroRender { ctx, name, macro_, docs, bra, ket }
40 }
41
42 fn render(&self, import_to_add: Option<ImportEdit>) -> Option<CompletionItem> {
43 let mut builder =
44 CompletionItem::new(CompletionKind::Reference, self.ctx.source_range(), &self.label())
45 .kind(SymbolKind::Macro)
46 .set_documentation(self.docs.clone())
47 .set_deprecated(self.ctx.is_deprecated(self.macro_))
48 .add_import(import_to_add)
49 .set_detail(self.detail());
50
51 let needs_bang = self.needs_bang();
52 builder = match self.ctx.snippet_cap() {
53 Some(cap) if needs_bang => {
54 let snippet = self.snippet();
55 let lookup = self.lookup();
56 builder.insert_snippet(cap, snippet).lookup_by(lookup)
57 }
58 None if needs_bang => builder.insert_text(self.banged_name()),
59 _ => {
60 mark::hit!(dont_insert_macro_call_parens_unncessary);
61 builder.insert_text(&self.name)
62 }
63 };
64
65 Some(builder.build())
66 }
67
68 fn needs_bang(&self) -> bool {
69 self.ctx.completion.use_item_syntax.is_none() && !self.ctx.completion.is_macro_call
70 }
71
72 fn label(&self) -> String {
73 if self.needs_bang() && self.ctx.snippet_cap().is_some() {
74 format!("{}!{}…{}", self.name, self.bra, self.ket)
75 } else {
76 self.banged_name()
77 }
78 }
79
80 fn snippet(&self) -> String {
81 format!("{}!{}$0{}", self.name, self.bra, self.ket)
82 }
83
84 fn lookup(&self) -> String {
85 self.banged_name()
86 }
87
88 fn banged_name(&self) -> String {
89 format!("{}!", self.name)
90 }
91
92 fn detail(&self) -> Option<String> {
93 let ast_node = self.macro_.source(self.ctx.db())?.value;
94 Some(macro_label(&ast_node))
95 }
96}
97
98fn guess_macro_braces(macro_name: &str, docs: &str) -> (&'static str, &'static str) {
99 let mut votes = [0, 0, 0];
100 for (idx, s) in docs.match_indices(&macro_name) {
101 let (before, after) = (&docs[..idx], &docs[idx + s.len()..]);
102 // Ensure to match the full word
103 if after.starts_with('!')
104 && !before.ends_with(|c: char| c == '_' || c.is_ascii_alphanumeric())
105 {
106 // It may have spaces before the braces like `foo! {}`
107 match after[1..].chars().find(|&c| !c.is_whitespace()) {
108 Some('{') => votes[0] += 1,
109 Some('[') => votes[1] += 1,
110 Some('(') => votes[2] += 1,
111 _ => {}
112 }
113 }
114 }
115
116 // Insert a space before `{}`.
117 // We prefer the last one when some votes equal.
118 let (_vote, (bra, ket)) = votes
119 .iter()
120 .zip(&[(" {", "}"), ("[", "]"), ("(", ")")])
121 .max_by_key(|&(&vote, _)| vote)
122 .unwrap();
123 (*bra, *ket)
124}
125
126#[cfg(test)]
127mod tests {
128 use test_utils::mark;
129
130 use crate::test_utils::check_edit;
131
132 #[test]
133 fn dont_insert_macro_call_parens_unncessary() {
134 mark::check!(dont_insert_macro_call_parens_unncessary);
135 check_edit(
136 "frobnicate!",
137 r#"
138//- /main.rs crate:main deps:foo
139use foo::$0;
140//- /foo/lib.rs crate:foo
141#[macro_export]
142macro_rules! frobnicate { () => () }
143"#,
144 r#"
145use foo::frobnicate;
146"#,
147 );
148
149 check_edit(
150 "frobnicate!",
151 r#"
152macro_rules! frobnicate { () => () }
153fn main() { frob$0!(); }
154"#,
155 r#"
156macro_rules! frobnicate { () => () }
157fn main() { frobnicate!(); }
158"#,
159 );
160 }
161
162 #[test]
163 fn guesses_macro_braces() {
164 check_edit(
165 "vec!",
166 r#"
167/// Creates a [`Vec`] containing the arguments.
168///
169/// ```
170/// let v = vec![1, 2, 3];
171/// assert_eq!(v[0], 1);
172/// assert_eq!(v[1], 2);
173/// assert_eq!(v[2], 3);
174/// ```
175macro_rules! vec { () => {} }
176
177fn fn main() { v$0 }
178"#,
179 r#"
180/// Creates a [`Vec`] containing the arguments.
181///
182/// ```
183/// let v = vec![1, 2, 3];
184/// assert_eq!(v[0], 1);
185/// assert_eq!(v[1], 2);
186/// assert_eq!(v[2], 3);
187/// ```
188macro_rules! vec { () => {} }
189
190fn fn main() { vec![$0] }
191"#,
192 );
193
194 check_edit(
195 "foo!",
196 r#"
197/// Foo
198///
199/// Don't call `fooo!()` `fooo!()`, or `_foo![]` `_foo![]`,
200/// call as `let _=foo! { hello world };`
201macro_rules! foo { () => {} }
202fn main() { $0 }
203"#,
204 r#"
205/// Foo
206///
207/// Don't call `fooo!()` `fooo!()`, or `_foo![]` `_foo![]`,
208/// call as `let _=foo! { hello world };`
209macro_rules! foo { () => {} }
210fn main() { foo! {$0} }
211"#,
212 )
213 }
214}
diff --git a/crates/ide_completion/src/render/pattern.rs b/crates/ide_completion/src/render/pattern.rs
new file mode 100644
index 000000000..465dfe00c
--- /dev/null
+++ b/crates/ide_completion/src/render/pattern.rs
@@ -0,0 +1,150 @@
1//! Renderer for patterns.
2
3use hir::{db::HirDatabase, HasAttrs, HasVisibility, Name, StructKind};
4use ide_db::helpers::SnippetCap;
5use itertools::Itertools;
6
7use crate::{item::CompletionKind, render::RenderContext, CompletionItem, CompletionItemKind};
8
9fn visible_fields(
10 ctx: &RenderContext<'_>,
11 fields: &[hir::Field],
12 item: impl HasAttrs,
13) -> Option<(Vec<hir::Field>, bool)> {
14 let module = ctx.completion.scope.module()?;
15 let n_fields = fields.len();
16 let fields = fields
17 .into_iter()
18 .filter(|field| field.is_visible_from(ctx.db(), module))
19 .copied()
20 .collect::<Vec<_>>();
21
22 let fields_omitted =
23 n_fields - fields.len() > 0 || item.attrs(ctx.db()).by_key("non_exhaustive").exists();
24 Some((fields, fields_omitted))
25}
26
27pub(crate) fn render_struct_pat(
28 ctx: RenderContext<'_>,
29 strukt: hir::Struct,
30 local_name: Option<Name>,
31) -> Option<CompletionItem> {
32 let _p = profile::span("render_struct_pat");
33
34 let fields = strukt.fields(ctx.db());
35 let (visible_fields, fields_omitted) = visible_fields(&ctx, &fields, strukt)?;
36
37 if visible_fields.is_empty() {
38 // Matching a struct without matching its fields is pointless, unlike matching a Variant without its fields
39 return None;
40 }
41
42 let name = local_name.unwrap_or_else(|| strukt.name(ctx.db())).to_string();
43 let pat = render_pat(&ctx, &name, strukt.kind(ctx.db()), &visible_fields, fields_omitted)?;
44
45 Some(build_completion(ctx, name, pat, strukt))
46}
47
48pub(crate) fn render_variant_pat(
49 ctx: RenderContext<'_>,
50 variant: hir::Variant,
51 local_name: Option<Name>,
52 path: Option<hir::ModPath>,
53) -> Option<CompletionItem> {
54 let _p = profile::span("render_variant_pat");
55
56 let fields = variant.fields(ctx.db());
57 let (visible_fields, fields_omitted) = visible_fields(&ctx, &fields, variant)?;
58
59 let name = match &path {
60 Some(path) => path.to_string(),
61 None => local_name.unwrap_or_else(|| variant.name(ctx.db())).to_string(),
62 };
63 let pat = render_pat(&ctx, &name, variant.kind(ctx.db()), &visible_fields, fields_omitted)?;
64
65 Some(build_completion(ctx, name, pat, variant))
66}
67
68fn build_completion(
69 ctx: RenderContext<'_>,
70 name: String,
71 pat: String,
72 item: impl HasAttrs + Copy,
73) -> CompletionItem {
74 let completion = CompletionItem::new(CompletionKind::Snippet, ctx.source_range(), name)
75 .kind(CompletionItemKind::Binding)
76 .set_documentation(ctx.docs(item))
77 .set_deprecated(ctx.is_deprecated(item))
78 .detail(&pat);
79 let completion = if let Some(snippet_cap) = ctx.snippet_cap() {
80 completion.insert_snippet(snippet_cap, pat)
81 } else {
82 completion.insert_text(pat)
83 };
84 completion.build()
85}
86
87fn render_pat(
88 ctx: &RenderContext<'_>,
89 name: &str,
90 kind: StructKind,
91 fields: &[hir::Field],
92 fields_omitted: bool,
93) -> Option<String> {
94 let mut pat = match kind {
95 StructKind::Tuple if ctx.snippet_cap().is_some() => {
96 render_tuple_as_pat(&fields, &name, fields_omitted)
97 }
98 StructKind::Record => {
99 render_record_as_pat(ctx.db(), ctx.snippet_cap(), &fields, &name, fields_omitted)
100 }
101 _ => return None,
102 };
103
104 if ctx.completion.is_param {
105 pat.push(':');
106 pat.push(' ');
107 pat.push_str(&name);
108 }
109 if ctx.snippet_cap().is_some() {
110 pat.push_str("$0");
111 }
112 Some(pat)
113}
114
115fn render_record_as_pat(
116 db: &dyn HirDatabase,
117 snippet_cap: Option<SnippetCap>,
118 fields: &[hir::Field],
119 name: &str,
120 fields_omitted: bool,
121) -> String {
122 let fields = fields.iter();
123 if snippet_cap.is_some() {
124 format!(
125 "{name} {{ {}{} }}",
126 fields
127 .enumerate()
128 .map(|(idx, field)| format!("{}${}", field.name(db), idx + 1))
129 .format(", "),
130 if fields_omitted { ", .." } else { "" },
131 name = name
132 )
133 } else {
134 format!(
135 "{name} {{ {}{} }}",
136 fields.map(|field| field.name(db)).format(", "),
137 if fields_omitted { ", .." } else { "" },
138 name = name
139 )
140 }
141}
142
143fn render_tuple_as_pat(fields: &[hir::Field], name: &str, fields_omitted: bool) -> String {
144 format!(
145 "{name}({}{})",
146 fields.iter().enumerate().map(|(idx, _)| format!("${}", idx + 1)).format(", "),
147 if fields_omitted { ", .." } else { "" },
148 name = name
149 )
150}
diff --git a/crates/ide_completion/src/render/type_alias.rs b/crates/ide_completion/src/render/type_alias.rs
new file mode 100644
index 000000000..bd97c3692
--- /dev/null
+++ b/crates/ide_completion/src/render/type_alias.rs
@@ -0,0 +1,59 @@
1//! Renderer for type aliases.
2
3use hir::HasSource;
4use ide_db::SymbolKind;
5use syntax::{
6 ast::{NameOwner, TypeAlias},
7 display::type_label,
8};
9
10use crate::{
11 item::{CompletionItem, CompletionKind},
12 render::RenderContext,
13};
14
15pub(crate) fn render_type_alias<'a>(
16 ctx: RenderContext<'a>,
17 type_alias: hir::TypeAlias,
18) -> Option<CompletionItem> {
19 TypeAliasRender::new(ctx, type_alias)?.render()
20}
21
22#[derive(Debug)]
23struct TypeAliasRender<'a> {
24 ctx: RenderContext<'a>,
25 type_alias: hir::TypeAlias,
26 ast_node: TypeAlias,
27}
28
29impl<'a> TypeAliasRender<'a> {
30 fn new(ctx: RenderContext<'a>, type_alias: hir::TypeAlias) -> Option<TypeAliasRender<'a>> {
31 let ast_node = type_alias.source(ctx.db())?.value;
32 Some(TypeAliasRender { ctx, type_alias, ast_node })
33 }
34
35 fn render(self) -> Option<CompletionItem> {
36 let name = self.name()?;
37 let detail = self.detail();
38
39 let item = CompletionItem::new(CompletionKind::Reference, self.ctx.source_range(), name)
40 .kind(SymbolKind::TypeAlias)
41 .set_documentation(self.ctx.docs(self.type_alias))
42 .set_deprecated(
43 self.ctx.is_deprecated(self.type_alias)
44 || self.ctx.is_deprecated_assoc_item(self.type_alias),
45 )
46 .detail(detail)
47 .build();
48
49 Some(item)
50 }
51
52 fn name(&self) -> Option<String> {
53 self.ast_node.name().map(|name| name.text().to_string())
54 }
55
56 fn detail(&self) -> String {
57 type_label(&self.ast_node)
58 }
59}