diff options
author | Aleksey Kladov <[email protected]> | 2021-02-17 14:53:31 +0000 |
---|---|---|
committer | Aleksey Kladov <[email protected]> | 2021-02-17 14:53:31 +0000 |
commit | 3db64a400c78bbd2708e67ddc07df1001fff3f29 (patch) | |
tree | 5386aab9c452981be09bc3e4362643a34e6e3617 /crates/ide_completion/src/render | |
parent | 6334ce866ab095215381c4b72692b20a84d26e96 (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.rs | 94 | ||||
-rw-r--r-- | crates/ide_completion/src/render/const_.rs | 59 | ||||
-rw-r--r-- | crates/ide_completion/src/render/enum_variant.rs | 131 | ||||
-rw-r--r-- | crates/ide_completion/src/render/function.rs | 345 | ||||
-rw-r--r-- | crates/ide_completion/src/render/macro_.rs | 214 | ||||
-rw-r--r-- | crates/ide_completion/src/render/pattern.rs | 150 | ||||
-rw-r--r-- | crates/ide_completion/src/render/type_alias.rs | 59 |
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 | |||
3 | use itertools::Itertools; | ||
4 | use test_utils::mark; | ||
5 | |||
6 | use crate::{item::Builder, CompletionContext}; | ||
7 | |||
8 | #[derive(Debug)] | ||
9 | pub(super) enum Params { | ||
10 | Named(Vec<String>), | ||
11 | Anonymous(usize), | ||
12 | } | ||
13 | |||
14 | impl 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 | |||
27 | impl 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 | |||
3 | use hir::HasSource; | ||
4 | use ide_db::SymbolKind; | ||
5 | use syntax::{ | ||
6 | ast::{Const, NameOwner}, | ||
7 | display::const_label, | ||
8 | }; | ||
9 | |||
10 | use crate::{ | ||
11 | item::{CompletionItem, CompletionKind}, | ||
12 | render::RenderContext, | ||
13 | }; | ||
14 | |||
15 | pub(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)] | ||
23 | struct ConstRender<'a> { | ||
24 | ctx: RenderContext<'a>, | ||
25 | const_: hir::Const, | ||
26 | ast_node: Const, | ||
27 | } | ||
28 | |||
29 | impl<'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 | |||
3 | use hir::{HasAttrs, HirDisplay, ModPath, StructKind}; | ||
4 | use ide_db::SymbolKind; | ||
5 | use itertools::Itertools; | ||
6 | use test_utils::mark; | ||
7 | |||
8 | use crate::{ | ||
9 | item::{CompletionItem, CompletionKind, ImportEdit}, | ||
10 | render::{builder_ext::Params, RenderContext}, | ||
11 | }; | ||
12 | |||
13 | pub(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)] | ||
25 | struct 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 | |||
35 | impl<'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)] | ||
105 | mod 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#" | ||
116 | enum Option<T> { Some(T), None } | ||
117 | use Option::*; | ||
118 | fn main() -> Option<i32> { | ||
119 | Som$0 | ||
120 | } | ||
121 | "#, | ||
122 | r#" | ||
123 | enum Option<T> { Some(T), None } | ||
124 | use Option::*; | ||
125 | fn 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 | |||
3 | use hir::{HasSource, HirDisplay, Type}; | ||
4 | use ide_db::SymbolKind; | ||
5 | use syntax::ast::Fn; | ||
6 | use test_utils::mark; | ||
7 | |||
8 | use crate::{ | ||
9 | item::{CompletionItem, CompletionItemKind, CompletionKind, ImportEdit}, | ||
10 | render::{builder_ext::Params, RenderContext}, | ||
11 | }; | ||
12 | |||
13 | pub(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)] | ||
24 | struct FunctionRender<'a> { | ||
25 | ctx: RenderContext<'a>, | ||
26 | name: String, | ||
27 | func: hir::Function, | ||
28 | ast_node: Fn, | ||
29 | } | ||
30 | |||
31 | impl<'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)] | ||
116 | mod 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#" | ||
130 | fn no_args() {} | ||
131 | fn main() { no_$0 } | ||
132 | "#, | ||
133 | r#" | ||
134 | fn no_args() {} | ||
135 | fn main() { no_args()$0 } | ||
136 | "#, | ||
137 | ); | ||
138 | |||
139 | check_edit( | ||
140 | "with_args", | ||
141 | r#" | ||
142 | fn with_args(x: i32, y: String) {} | ||
143 | fn main() { with_$0 } | ||
144 | "#, | ||
145 | r#" | ||
146 | fn with_args(x: i32, y: String) {} | ||
147 | fn main() { with_args(${1:x}, ${2:y})$0 } | ||
148 | "#, | ||
149 | ); | ||
150 | |||
151 | check_edit( | ||
152 | "foo", | ||
153 | r#" | ||
154 | struct S; | ||
155 | impl S { | ||
156 | fn foo(&self) {} | ||
157 | } | ||
158 | fn bar(s: &S) { s.f$0 } | ||
159 | "#, | ||
160 | r#" | ||
161 | struct S; | ||
162 | impl S { | ||
163 | fn foo(&self) {} | ||
164 | } | ||
165 | fn bar(s: &S) { s.foo()$0 } | ||
166 | "#, | ||
167 | ); | ||
168 | |||
169 | check_edit( | ||
170 | "foo", | ||
171 | r#" | ||
172 | struct S {} | ||
173 | impl S { | ||
174 | fn foo(&self, x: i32) {} | ||
175 | } | ||
176 | fn bar(s: &S) { | ||
177 | s.f$0 | ||
178 | } | ||
179 | "#, | ||
180 | r#" | ||
181 | struct S {} | ||
182 | impl S { | ||
183 | fn foo(&self, x: i32) {} | ||
184 | } | ||
185 | fn 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#" | ||
198 | struct S; | ||
199 | impl S { | ||
200 | fn foo(&self) {} | ||
201 | } | ||
202 | fn main() { S::f$0 } | ||
203 | "#, | ||
204 | r#" | ||
205 | struct S; | ||
206 | impl S { | ||
207 | fn foo(&self) {} | ||
208 | } | ||
209 | fn 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#" | ||
221 | fn with_args(x: i32, y: String) {} | ||
222 | fn main() { with_$0 } | ||
223 | "#, | ||
224 | r#" | ||
225 | fn with_args(x: i32, y: String) {} | ||
226 | fn main() { with_args($0) } | ||
227 | "#, | ||
228 | ); | ||
229 | } | ||
230 | |||
231 | #[test] | ||
232 | fn strips_underscores_from_args() { | ||
233 | check_edit( | ||
234 | "foo", | ||
235 | r#" | ||
236 | fn foo(_foo: i32, ___bar: bool, ho_ge_: String) {} | ||
237 | fn main() { f$0 } | ||
238 | "#, | ||
239 | r#" | ||
240 | fn foo(_foo: i32, ___bar: bool, ho_ge_: String) {} | ||
241 | fn 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#" | ||
251 | struct Foo {} | ||
252 | fn ref_arg(x: &Foo) {} | ||
253 | fn main() { | ||
254 | let x = Foo {}; | ||
255 | ref_ar$0 | ||
256 | } | ||
257 | "#, | ||
258 | r#" | ||
259 | struct Foo {} | ||
260 | fn ref_arg(x: &Foo) {} | ||
261 | fn 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#" | ||
274 | struct Foo {} | ||
275 | fn ref_arg(x: &mut Foo) {} | ||
276 | fn main() { | ||
277 | let x = Foo {}; | ||
278 | ref_ar$0 | ||
279 | } | ||
280 | "#, | ||
281 | r#" | ||
282 | struct Foo {} | ||
283 | fn ref_arg(x: &mut Foo) {} | ||
284 | fn 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#" | ||
297 | struct Foo {} | ||
298 | struct Bar {} | ||
299 | impl Bar { | ||
300 | fn apply_foo(&self, x: &Foo) {} | ||
301 | } | ||
302 | |||
303 | fn main() { | ||
304 | let x = Foo {}; | ||
305 | let y = Bar {}; | ||
306 | y.$0 | ||
307 | } | ||
308 | "#, | ||
309 | r#" | ||
310 | struct Foo {} | ||
311 | struct Bar {} | ||
312 | impl Bar { | ||
313 | fn apply_foo(&self, x: &Foo) {} | ||
314 | } | ||
315 | |||
316 | fn 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#" | ||
330 | fn take_mutably(mut x: &i32) {} | ||
331 | |||
332 | fn main() { | ||
333 | take_m$0 | ||
334 | } | ||
335 | "#, | ||
336 | r#" | ||
337 | fn take_mutably(mut x: &i32) {} | ||
338 | |||
339 | fn 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 | |||
3 | use hir::{Documentation, HasSource}; | ||
4 | use ide_db::SymbolKind; | ||
5 | use syntax::display::macro_label; | ||
6 | use test_utils::mark; | ||
7 | |||
8 | use crate::{ | ||
9 | item::{CompletionItem, CompletionKind, ImportEdit}, | ||
10 | render::RenderContext, | ||
11 | }; | ||
12 | |||
13 | pub(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)] | ||
24 | struct 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 | |||
33 | impl<'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 | |||
98 | fn 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(¯o_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)] | ||
127 | mod 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 | ||
139 | use foo::$0; | ||
140 | //- /foo/lib.rs crate:foo | ||
141 | #[macro_export] | ||
142 | macro_rules! frobnicate { () => () } | ||
143 | "#, | ||
144 | r#" | ||
145 | use foo::frobnicate; | ||
146 | "#, | ||
147 | ); | ||
148 | |||
149 | check_edit( | ||
150 | "frobnicate!", | ||
151 | r#" | ||
152 | macro_rules! frobnicate { () => () } | ||
153 | fn main() { frob$0!(); } | ||
154 | "#, | ||
155 | r#" | ||
156 | macro_rules! frobnicate { () => () } | ||
157 | fn 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 | /// ``` | ||
175 | macro_rules! vec { () => {} } | ||
176 | |||
177 | fn 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 | /// ``` | ||
188 | macro_rules! vec { () => {} } | ||
189 | |||
190 | fn 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 };` | ||
201 | macro_rules! foo { () => {} } | ||
202 | fn main() { $0 } | ||
203 | "#, | ||
204 | r#" | ||
205 | /// Foo | ||
206 | /// | ||
207 | /// Don't call `fooo!()` `fooo!()`, or `_foo![]` `_foo![]`, | ||
208 | /// call as `let _=foo! { hello world };` | ||
209 | macro_rules! foo { () => {} } | ||
210 | fn 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 | |||
3 | use hir::{db::HirDatabase, HasAttrs, HasVisibility, Name, StructKind}; | ||
4 | use ide_db::helpers::SnippetCap; | ||
5 | use itertools::Itertools; | ||
6 | |||
7 | use crate::{item::CompletionKind, render::RenderContext, CompletionItem, CompletionItemKind}; | ||
8 | |||
9 | fn 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 | |||
27 | pub(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 | |||
48 | pub(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 | |||
68 | fn 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 | |||
87 | fn 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 | |||
115 | fn 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 | |||
143 | fn 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 | |||
3 | use hir::HasSource; | ||
4 | use ide_db::SymbolKind; | ||
5 | use syntax::{ | ||
6 | ast::{NameOwner, TypeAlias}, | ||
7 | display::type_label, | ||
8 | }; | ||
9 | |||
10 | use crate::{ | ||
11 | item::{CompletionItem, CompletionKind}, | ||
12 | render::RenderContext, | ||
13 | }; | ||
14 | |||
15 | pub(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)] | ||
23 | struct TypeAliasRender<'a> { | ||
24 | ctx: RenderContext<'a>, | ||
25 | type_alias: hir::TypeAlias, | ||
26 | ast_node: TypeAlias, | ||
27 | } | ||
28 | |||
29 | impl<'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 | } | ||