diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2020-12-22 18:03:51 +0000 |
---|---|---|
committer | GitHub <[email protected]> | 2020-12-22 18:03:51 +0000 |
commit | 4a2f60cb7b83e9ef95d97201d210ff6943b660eb (patch) | |
tree | 365de67418685046fdddb1445efab6552cbeb625 | |
parent | 94f661c62a41674d9ee04c2f3cd030d639acc926 (diff) | |
parent | 83121efcd577124a992dc8bd304690b36bda2931 (diff) |
Merge #6964
6964: Add full pattern completions for Struct and Variant patterns r=matklad a=Veykril
Just gonna call it full pattern completion as pattern completion is already implemented in a sense by showing idents in pattern position. What this does is basically complete struct and variant patterns where applicable(function params, let statements and refutable pattern locations).
This does not replace just completing the corresponding idents of the structs and variants, instead two completions are shown for these, a completion for the ident itself and a completion for the pattern(if the pattern make sense to be used that is). I figured in some cases one would rather type out the pattern manually if it has a lot of fields but you only care about one since this completion would cause one more work in the end since you would have to delete all the extra matched fields again.
These completions are tagged as `CompletionKind::Snippet`, not sure if that is the right one here.
<details>
<summary>some gifs</summary>
![dx2lxgzhj3](https://user-images.githubusercontent.com/3757771/102719967-6987ef80-42f1-11eb-8ae0-8aff53777860.gif)
![EP2E7sJLkB](https://user-images.githubusercontent.com/3757771/102785777-c7264580-439e-11eb-8a64-f142e19fb65b.gif)
![JMNHHWknr9](https://user-images.githubusercontent.com/3757771/102785796-d1e0da80-439e-11eb-934b-218ada31b51c.gif)
</details>
Co-authored-by: Lukas Wirth <[email protected]>
-rw-r--r-- | crates/completion/src/completions.rs | 33 | ||||
-rw-r--r-- | crates/completion/src/completions/pattern.rs | 178 | ||||
-rw-r--r-- | crates/completion/src/context.rs | 21 | ||||
-rw-r--r-- | crates/completion/src/render.rs | 7 | ||||
-rw-r--r-- | crates/completion/src/render/builder_ext.rs | 1 | ||||
-rw-r--r-- | crates/completion/src/render/enum_variant.rs | 45 | ||||
-rw-r--r-- | crates/completion/src/render/pattern.rs | 148 | ||||
-rw-r--r-- | crates/hir/src/code_model.rs | 4 |
8 files changed, 363 insertions, 74 deletions
diff --git a/crates/completion/src/completions.rs b/crates/completion/src/completions.rs index 1ef6b5f48..d9fe13485 100644 --- a/crates/completion/src/completions.rs +++ b/crates/completion/src/completions.rs | |||
@@ -19,9 +19,14 @@ use hir::{ModPath, ScopeDef, Type}; | |||
19 | use crate::{ | 19 | use crate::{ |
20 | item::Builder, | 20 | item::Builder, |
21 | render::{ | 21 | render::{ |
22 | const_::render_const, enum_variant::render_variant, function::render_fn, | 22 | const_::render_const, |
23 | macro_::render_macro, render_field, render_resolution, render_tuple_field, | 23 | enum_variant::render_variant, |
24 | type_alias::render_type_alias, RenderContext, | 24 | function::render_fn, |
25 | macro_::render_macro, | ||
26 | pattern::{render_struct_pat, render_variant_pat}, | ||
27 | render_field, render_resolution, render_tuple_field, | ||
28 | type_alias::render_type_alias, | ||
29 | RenderContext, | ||
25 | }, | 30 | }, |
26 | CompletionContext, CompletionItem, | 31 | CompletionContext, CompletionItem, |
27 | }; | 32 | }; |
@@ -105,6 +110,28 @@ impl Completions { | |||
105 | self.add(item) | 110 | self.add(item) |
106 | } | 111 | } |
107 | 112 | ||
113 | pub(crate) fn add_variant_pat( | ||
114 | &mut self, | ||
115 | ctx: &CompletionContext, | ||
116 | variant: hir::Variant, | ||
117 | local_name: Option<hir::Name>, | ||
118 | ) { | ||
119 | if let Some(item) = render_variant_pat(RenderContext::new(ctx), variant, local_name) { | ||
120 | self.add(item); | ||
121 | } | ||
122 | } | ||
123 | |||
124 | pub(crate) fn add_struct_pat( | ||
125 | &mut self, | ||
126 | ctx: &CompletionContext, | ||
127 | strukt: hir::Struct, | ||
128 | local_name: Option<hir::Name>, | ||
129 | ) { | ||
130 | if let Some(item) = render_struct_pat(RenderContext::new(ctx), strukt, local_name) { | ||
131 | self.add(item); | ||
132 | } | ||
133 | } | ||
134 | |||
108 | pub(crate) fn add_const(&mut self, ctx: &CompletionContext, constant: hir::Const) { | 135 | pub(crate) fn add_const(&mut self, ctx: &CompletionContext, constant: hir::Const) { |
109 | if let Some(item) = render_const(RenderContext::new(ctx), constant) { | 136 | if let Some(item) = render_const(RenderContext::new(ctx), constant) { |
110 | self.add(item); | 137 | self.add(item); |
diff --git a/crates/completion/src/completions/pattern.rs b/crates/completion/src/completions/pattern.rs index 4d56731ec..eee31098d 100644 --- a/crates/completion/src/completions/pattern.rs +++ b/crates/completion/src/completions/pattern.rs | |||
@@ -2,9 +2,9 @@ | |||
2 | 2 | ||
3 | use crate::{CompletionContext, Completions}; | 3 | use crate::{CompletionContext, Completions}; |
4 | 4 | ||
5 | /// Completes constats and paths in patterns. | 5 | /// Completes constants and paths in patterns. |
6 | pub(crate) fn complete_pattern(acc: &mut Completions, ctx: &CompletionContext) { | 6 | pub(crate) fn complete_pattern(acc: &mut Completions, ctx: &CompletionContext) { |
7 | if !(ctx.is_pat_binding_or_const || ctx.is_irrefutable_let_pat_binding) { | 7 | if !(ctx.is_pat_binding_or_const || ctx.is_irrefutable_pat_binding) { |
8 | return; | 8 | return; |
9 | } | 9 | } |
10 | if ctx.record_pat_syntax.is_some() { | 10 | if ctx.record_pat_syntax.is_some() { |
@@ -15,20 +15,21 @@ pub(crate) fn complete_pattern(acc: &mut Completions, ctx: &CompletionContext) { | |||
15 | // suggest variants + auto-imports | 15 | // suggest variants + auto-imports |
16 | ctx.scope.process_all_names(&mut |name, res| { | 16 | ctx.scope.process_all_names(&mut |name, res| { |
17 | let add_resolution = match &res { | 17 | let add_resolution = match &res { |
18 | hir::ScopeDef::ModuleDef(def) => { | 18 | hir::ScopeDef::ModuleDef(def) => match def { |
19 | if ctx.is_irrefutable_let_pat_binding { | 19 | hir::ModuleDef::Adt(hir::Adt::Struct(strukt)) => { |
20 | matches!(def, hir::ModuleDef::Adt(hir::Adt::Struct(_))) | 20 | acc.add_struct_pat(ctx, strukt.clone(), Some(name.clone())); |
21 | } else { | 21 | true |
22 | matches!( | ||
23 | def, | ||
24 | hir::ModuleDef::Adt(hir::Adt::Enum(..)) | ||
25 | | hir::ModuleDef::Adt(hir::Adt::Struct(..)) | ||
26 | | hir::ModuleDef::Variant(..) | ||
27 | | hir::ModuleDef::Const(..) | ||
28 | | hir::ModuleDef::Module(..) | ||
29 | ) | ||
30 | } | 22 | } |
31 | } | 23 | hir::ModuleDef::Variant(variant) if !ctx.is_irrefutable_pat_binding => { |
24 | acc.add_variant_pat(ctx, variant.clone(), Some(name.clone())); | ||
25 | true | ||
26 | } | ||
27 | hir::ModuleDef::Adt(hir::Adt::Enum(..)) | ||
28 | | hir::ModuleDef::Variant(..) | ||
29 | | hir::ModuleDef::Const(..) | ||
30 | | hir::ModuleDef::Module(..) => !ctx.is_irrefutable_pat_binding, | ||
31 | _ => false, | ||
32 | }, | ||
32 | hir::ScopeDef::MacroDef(_) => true, | 33 | hir::ScopeDef::MacroDef(_) => true, |
33 | _ => false, | 34 | _ => false, |
34 | }; | 35 | }; |
@@ -42,13 +43,21 @@ pub(crate) fn complete_pattern(acc: &mut Completions, ctx: &CompletionContext) { | |||
42 | mod tests { | 43 | mod tests { |
43 | use expect_test::{expect, Expect}; | 44 | use expect_test::{expect, Expect}; |
44 | 45 | ||
45 | use crate::{test_utils::completion_list, CompletionKind}; | 46 | use crate::{ |
47 | test_utils::{check_edit, completion_list}, | ||
48 | CompletionKind, | ||
49 | }; | ||
46 | 50 | ||
47 | fn check(ra_fixture: &str, expect: Expect) { | 51 | fn check(ra_fixture: &str, expect: Expect) { |
48 | let actual = completion_list(ra_fixture, CompletionKind::Reference); | 52 | let actual = completion_list(ra_fixture, CompletionKind::Reference); |
49 | expect.assert_eq(&actual) | 53 | expect.assert_eq(&actual) |
50 | } | 54 | } |
51 | 55 | ||
56 | fn check_snippet(ra_fixture: &str, expect: Expect) { | ||
57 | let actual = completion_list(ra_fixture, CompletionKind::Snippet); | ||
58 | expect.assert_eq(&actual) | ||
59 | } | ||
60 | |||
52 | #[test] | 61 | #[test] |
53 | fn completes_enum_variants_and_modules() { | 62 | fn completes_enum_variants_and_modules() { |
54 | check( | 63 | check( |
@@ -69,7 +78,7 @@ fn foo() { | |||
69 | en E | 78 | en E |
70 | ct Z | 79 | ct Z |
71 | st Bar | 80 | st Bar |
72 | ev X () | 81 | ev X |
73 | md m | 82 | md m |
74 | "#]], | 83 | "#]], |
75 | ); | 84 | ); |
@@ -114,4 +123,139 @@ fn foo() { | |||
114 | "#]], | 123 | "#]], |
115 | ); | 124 | ); |
116 | } | 125 | } |
126 | |||
127 | #[test] | ||
128 | fn completes_in_param() { | ||
129 | check( | ||
130 | r#" | ||
131 | enum E { X } | ||
132 | |||
133 | static FOO: E = E::X; | ||
134 | struct Bar { f: u32 } | ||
135 | |||
136 | fn foo(<|>) { | ||
137 | } | ||
138 | "#, | ||
139 | expect![[r#" | ||
140 | st Bar | ||
141 | "#]], | ||
142 | ); | ||
143 | } | ||
144 | |||
145 | #[test] | ||
146 | fn completes_pat_in_let() { | ||
147 | check_snippet( | ||
148 | r#" | ||
149 | struct Bar { f: u32 } | ||
150 | |||
151 | fn foo() { | ||
152 | let <|> | ||
153 | } | ||
154 | "#, | ||
155 | expect![[r#" | ||
156 | bn Bar Bar { f$1 }$0 | ||
157 | "#]], | ||
158 | ); | ||
159 | } | ||
160 | |||
161 | #[test] | ||
162 | fn completes_param_pattern() { | ||
163 | check_snippet( | ||
164 | r#" | ||
165 | struct Foo { bar: String, baz: String } | ||
166 | struct Bar(String, String); | ||
167 | struct Baz; | ||
168 | fn outer(<|>) {} | ||
169 | "#, | ||
170 | expect![[r#" | ||
171 | bn Foo Foo { bar$1, baz$2 }: Foo$0 | ||
172 | bn Bar Bar($1, $2): Bar$0 | ||
173 | "#]], | ||
174 | ) | ||
175 | } | ||
176 | |||
177 | #[test] | ||
178 | fn completes_let_pattern() { | ||
179 | check_snippet( | ||
180 | r#" | ||
181 | struct Foo { bar: String, baz: String } | ||
182 | struct Bar(String, String); | ||
183 | struct Baz; | ||
184 | fn outer() { | ||
185 | let <|> | ||
186 | } | ||
187 | "#, | ||
188 | expect![[r#" | ||
189 | bn Foo Foo { bar$1, baz$2 }$0 | ||
190 | bn Bar Bar($1, $2)$0 | ||
191 | "#]], | ||
192 | ) | ||
193 | } | ||
194 | |||
195 | #[test] | ||
196 | fn completes_refutable_pattern() { | ||
197 | check_snippet( | ||
198 | r#" | ||
199 | struct Foo { bar: i32, baz: i32 } | ||
200 | struct Bar(String, String); | ||
201 | struct Baz; | ||
202 | fn outer() { | ||
203 | match () { | ||
204 | <|> | ||
205 | } | ||
206 | } | ||
207 | "#, | ||
208 | expect![[r#" | ||
209 | bn Foo Foo { bar$1, baz$2 }$0 | ||
210 | bn Bar Bar($1, $2)$0 | ||
211 | "#]], | ||
212 | ) | ||
213 | } | ||
214 | |||
215 | #[test] | ||
216 | fn omits_private_fields_pat() { | ||
217 | check_snippet( | ||
218 | r#" | ||
219 | mod foo { | ||
220 | pub struct Foo { pub bar: i32, baz: i32 } | ||
221 | pub struct Bar(pub String, String); | ||
222 | pub struct Invisible(String, String); | ||
223 | } | ||
224 | use foo::*; | ||
225 | |||
226 | fn outer() { | ||
227 | match () { | ||
228 | <|> | ||
229 | } | ||
230 | } | ||
231 | "#, | ||
232 | expect![[r#" | ||
233 | bn Foo Foo { bar$1, .. }$0 | ||
234 | bn Bar Bar($1, ..)$0 | ||
235 | "#]], | ||
236 | ) | ||
237 | } | ||
238 | |||
239 | #[test] | ||
240 | fn only_shows_ident_completion() { | ||
241 | check_edit( | ||
242 | "Foo", | ||
243 | r#" | ||
244 | struct Foo(i32); | ||
245 | fn main() { | ||
246 | match Foo(92) { | ||
247 | <|>(92) => (), | ||
248 | } | ||
249 | } | ||
250 | "#, | ||
251 | r#" | ||
252 | struct Foo(i32); | ||
253 | fn main() { | ||
254 | match Foo(92) { | ||
255 | Foo(92) => (), | ||
256 | } | ||
257 | } | ||
258 | "#, | ||
259 | ); | ||
260 | } | ||
117 | } | 261 | } |
diff --git a/crates/completion/src/context.rs b/crates/completion/src/context.rs index 5cd11cf77..41de324d8 100644 --- a/crates/completion/src/context.rs +++ b/crates/completion/src/context.rs | |||
@@ -51,7 +51,7 @@ pub(crate) struct CompletionContext<'a> { | |||
51 | /// If a name-binding or reference to a const in a pattern. | 51 | /// If a name-binding or reference to a const in a pattern. |
52 | /// Irrefutable patterns (like let) are excluded. | 52 | /// Irrefutable patterns (like let) are excluded. |
53 | pub(super) is_pat_binding_or_const: bool, | 53 | pub(super) is_pat_binding_or_const: bool, |
54 | pub(super) is_irrefutable_let_pat_binding: bool, | 54 | pub(super) is_irrefutable_pat_binding: bool, |
55 | /// A single-indent path, like `foo`. `::foo` should not be considered a trivial path. | 55 | /// A single-indent path, like `foo`. `::foo` should not be considered a trivial path. |
56 | pub(super) is_trivial_path: bool, | 56 | pub(super) is_trivial_path: bool, |
57 | /// If not a trivial path, the prefix (qualifier). | 57 | /// If not a trivial path, the prefix (qualifier). |
@@ -147,7 +147,7 @@ impl<'a> CompletionContext<'a> { | |||
147 | active_parameter: ActiveParameter::at(db, position), | 147 | active_parameter: ActiveParameter::at(db, position), |
148 | is_param: false, | 148 | is_param: false, |
149 | is_pat_binding_or_const: false, | 149 | is_pat_binding_or_const: false, |
150 | is_irrefutable_let_pat_binding: false, | 150 | is_irrefutable_pat_binding: false, |
151 | is_trivial_path: false, | 151 | is_trivial_path: false, |
152 | path_qual: None, | 152 | path_qual: None, |
153 | after_if: false, | 153 | after_if: false, |
@@ -327,14 +327,19 @@ impl<'a> CompletionContext<'a> { | |||
327 | if bind_pat.syntax().parent().and_then(ast::RecordPatFieldList::cast).is_some() { | 327 | if bind_pat.syntax().parent().and_then(ast::RecordPatFieldList::cast).is_some() { |
328 | self.is_pat_binding_or_const = false; | 328 | self.is_pat_binding_or_const = false; |
329 | } | 329 | } |
330 | if let Some(let_stmt) = bind_pat.syntax().ancestors().find_map(ast::LetStmt::cast) { | 330 | if let Some(Some(pat)) = bind_pat.syntax().ancestors().find_map(|node| { |
331 | if let Some(pat) = let_stmt.pat() { | 331 | match_ast! { |
332 | if pat.syntax().text_range().contains_range(bind_pat.syntax().text_range()) | 332 | match node { |
333 | { | 333 | ast::LetStmt(it) => Some(it.pat()), |
334 | self.is_pat_binding_or_const = false; | 334 | ast::Param(it) => Some(it.pat()), |
335 | self.is_irrefutable_let_pat_binding = true; | 335 | _ => None, |
336 | } | 336 | } |
337 | } | 337 | } |
338 | }) { | ||
339 | if pat.syntax().text_range().contains_range(bind_pat.syntax().text_range()) { | ||
340 | self.is_pat_binding_or_const = false; | ||
341 | self.is_irrefutable_pat_binding = true; | ||
342 | } | ||
338 | } | 343 | } |
339 | } | 344 | } |
340 | if is_node::<ast::Param>(name.syntax()) { | 345 | if is_node::<ast::Param>(name.syntax()) { |
diff --git a/crates/completion/src/render.rs b/crates/completion/src/render.rs index 1092a4825..1ba7201a1 100644 --- a/crates/completion/src/render.rs +++ b/crates/completion/src/render.rs | |||
@@ -5,6 +5,7 @@ pub(crate) mod macro_; | |||
5 | pub(crate) mod function; | 5 | pub(crate) mod function; |
6 | pub(crate) mod enum_variant; | 6 | pub(crate) mod enum_variant; |
7 | pub(crate) mod const_; | 7 | pub(crate) mod const_; |
8 | pub(crate) mod pattern; | ||
8 | pub(crate) mod type_alias; | 9 | pub(crate) mod type_alias; |
9 | 10 | ||
10 | mod builder_ext; | 11 | mod builder_ext; |
@@ -159,6 +160,12 @@ impl<'a> Render<'a> { | |||
159 | let item = render_fn(self.ctx, import_to_add, Some(local_name), *func); | 160 | let item = render_fn(self.ctx, import_to_add, Some(local_name), *func); |
160 | return Some(item); | 161 | return Some(item); |
161 | } | 162 | } |
163 | ScopeDef::ModuleDef(Variant(_)) | ||
164 | if self.ctx.completion.is_pat_binding_or_const | ||
165 | | self.ctx.completion.is_irrefutable_pat_binding => | ||
166 | { | ||
167 | CompletionItemKind::EnumVariant | ||
168 | } | ||
162 | ScopeDef::ModuleDef(Variant(var)) => { | 169 | ScopeDef::ModuleDef(Variant(var)) => { |
163 | let item = render_variant(self.ctx, import_to_add, Some(local_name), *var, None); | 170 | let item = render_variant(self.ctx, import_to_add, Some(local_name), *var, None); |
164 | return Some(item); | 171 | return Some(item); |
diff --git a/crates/completion/src/render/builder_ext.rs b/crates/completion/src/render/builder_ext.rs index ce8718bd5..d053a988b 100644 --- a/crates/completion/src/render/builder_ext.rs +++ b/crates/completion/src/render/builder_ext.rs | |||
@@ -34,7 +34,6 @@ impl Builder { | |||
34 | return false; | 34 | return false; |
35 | } | 35 | } |
36 | if ctx.is_pattern_call { | 36 | if ctx.is_pattern_call { |
37 | mark::hit!(dont_duplicate_pattern_parens); | ||
38 | return false; | 37 | return false; |
39 | } | 38 | } |
40 | if ctx.is_call { | 39 | if ctx.is_call { |
diff --git a/crates/completion/src/render/enum_variant.rs b/crates/completion/src/render/enum_variant.rs index 7176fd9b3..732e139ec 100644 --- a/crates/completion/src/render/enum_variant.rs +++ b/crates/completion/src/render/enum_variant.rs | |||
@@ -126,50 +126,5 @@ fn main() -> Option<i32> { | |||
126 | } | 126 | } |
127 | "#, | 127 | "#, |
128 | ); | 128 | ); |
129 | check_edit( | ||
130 | "Some", | ||
131 | r#" | ||
132 | enum Option<T> { Some(T), None } | ||
133 | use Option::*; | ||
134 | fn main(value: Option<i32>) { | ||
135 | match value { | ||
136 | Som<|> | ||
137 | } | ||
138 | } | ||
139 | "#, | ||
140 | r#" | ||
141 | enum Option<T> { Some(T), None } | ||
142 | use Option::*; | ||
143 | fn main(value: Option<i32>) { | ||
144 | match value { | ||
145 | Some($0) | ||
146 | } | ||
147 | } | ||
148 | "#, | ||
149 | ); | ||
150 | } | ||
151 | |||
152 | #[test] | ||
153 | fn dont_duplicate_pattern_parens() { | ||
154 | mark::check!(dont_duplicate_pattern_parens); | ||
155 | check_edit( | ||
156 | "Var", | ||
157 | r#" | ||
158 | enum E { Var(i32) } | ||
159 | fn main() { | ||
160 | match E::Var(92) { | ||
161 | E::<|>(92) => (), | ||
162 | } | ||
163 | } | ||
164 | "#, | ||
165 | r#" | ||
166 | enum E { Var(i32) } | ||
167 | fn main() { | ||
168 | match E::Var(92) { | ||
169 | E::Var(92) => (), | ||
170 | } | ||
171 | } | ||
172 | "#, | ||
173 | ); | ||
174 | } | 129 | } |
175 | } | 130 | } |
diff --git a/crates/completion/src/render/pattern.rs b/crates/completion/src/render/pattern.rs new file mode 100644 index 000000000..a3b6a3cac --- /dev/null +++ b/crates/completion/src/render/pattern.rs | |||
@@ -0,0 +1,148 @@ | |||
1 | //! Renderer for patterns. | ||
2 | |||
3 | use hir::{db::HirDatabase, HasAttrs, HasVisibility, Name, StructKind}; | ||
4 | use itertools::Itertools; | ||
5 | |||
6 | use crate::{ | ||
7 | config::SnippetCap, item::CompletionKind, render::RenderContext, CompletionItem, | ||
8 | CompletionItemKind, | ||
9 | }; | ||
10 | |||
11 | fn visible_fields( | ||
12 | ctx: &RenderContext<'_>, | ||
13 | fields: &[hir::Field], | ||
14 | item: impl HasAttrs, | ||
15 | ) -> Option<(Vec<hir::Field>, bool)> { | ||
16 | let module = ctx.completion.scope.module()?; | ||
17 | let n_fields = fields.len(); | ||
18 | let fields = fields | ||
19 | .into_iter() | ||
20 | .filter(|field| field.is_visible_from(ctx.db(), module)) | ||
21 | .copied() | ||
22 | .collect::<Vec<_>>(); | ||
23 | |||
24 | let fields_omitted = | ||
25 | n_fields - fields.len() > 0 || item.attrs(ctx.db()).by_key("non_exhaustive").exists(); | ||
26 | Some((fields, fields_omitted)) | ||
27 | } | ||
28 | |||
29 | pub(crate) fn render_struct_pat( | ||
30 | ctx: RenderContext<'_>, | ||
31 | strukt: hir::Struct, | ||
32 | local_name: Option<Name>, | ||
33 | ) -> Option<CompletionItem> { | ||
34 | let _p = profile::span("render_struct_pat"); | ||
35 | |||
36 | let fields = strukt.fields(ctx.db()); | ||
37 | let (visible_fields, fields_omitted) = visible_fields(&ctx, &fields, strukt)?; | ||
38 | |||
39 | if visible_fields.is_empty() { | ||
40 | // Matching a struct without matching its fields is pointless, unlike matching a Variant without its fields | ||
41 | return None; | ||
42 | } | ||
43 | |||
44 | let name = local_name.unwrap_or_else(|| strukt.name(ctx.db())).to_string(); | ||
45 | let pat = render_pat(&ctx, &name, strukt.kind(ctx.db()), &visible_fields, fields_omitted)?; | ||
46 | |||
47 | Some(build_completion(ctx, name, pat, strukt)) | ||
48 | } | ||
49 | |||
50 | pub(crate) fn render_variant_pat( | ||
51 | ctx: RenderContext<'_>, | ||
52 | variant: hir::Variant, | ||
53 | local_name: Option<Name>, | ||
54 | ) -> Option<CompletionItem> { | ||
55 | let _p = profile::span("render_variant_pat"); | ||
56 | |||
57 | let fields = variant.fields(ctx.db()); | ||
58 | let (visible_fields, fields_omitted) = visible_fields(&ctx, &fields, variant)?; | ||
59 | |||
60 | let name = local_name.unwrap_or_else(|| variant.name(ctx.db())).to_string(); | ||
61 | let pat = render_pat(&ctx, &name, variant.kind(ctx.db()), &visible_fields, fields_omitted)?; | ||
62 | |||
63 | Some(build_completion(ctx, name, pat, variant)) | ||
64 | } | ||
65 | |||
66 | fn build_completion( | ||
67 | ctx: RenderContext<'_>, | ||
68 | name: String, | ||
69 | pat: String, | ||
70 | item: impl HasAttrs + Copy, | ||
71 | ) -> CompletionItem { | ||
72 | let completion = CompletionItem::new(CompletionKind::Snippet, ctx.source_range(), name) | ||
73 | .kind(CompletionItemKind::Binding) | ||
74 | .set_documentation(ctx.docs(item)) | ||
75 | .set_deprecated(ctx.is_deprecated(item)) | ||
76 | .detail(&pat); | ||
77 | let completion = if let Some(snippet_cap) = ctx.snippet_cap() { | ||
78 | completion.insert_snippet(snippet_cap, pat) | ||
79 | } else { | ||
80 | completion.insert_text(pat) | ||
81 | }; | ||
82 | completion.build() | ||
83 | } | ||
84 | |||
85 | fn render_pat( | ||
86 | ctx: &RenderContext<'_>, | ||
87 | name: &str, | ||
88 | kind: StructKind, | ||
89 | fields: &[hir::Field], | ||
90 | fields_omitted: bool, | ||
91 | ) -> Option<String> { | ||
92 | let mut pat = match kind { | ||
93 | StructKind::Tuple if ctx.snippet_cap().is_some() => { | ||
94 | render_tuple_as_pat(&fields, &name, fields_omitted) | ||
95 | } | ||
96 | StructKind::Record => { | ||
97 | render_record_as_pat(ctx.db(), ctx.snippet_cap(), &fields, &name, fields_omitted) | ||
98 | } | ||
99 | _ => return None, | ||
100 | }; | ||
101 | |||
102 | if ctx.completion.is_param { | ||
103 | pat.push(':'); | ||
104 | pat.push(' '); | ||
105 | pat.push_str(&name); | ||
106 | } | ||
107 | if ctx.snippet_cap().is_some() { | ||
108 | pat.push_str("$0"); | ||
109 | } | ||
110 | Some(pat) | ||
111 | } | ||
112 | |||
113 | fn render_record_as_pat( | ||
114 | db: &dyn HirDatabase, | ||
115 | snippet_cap: Option<SnippetCap>, | ||
116 | fields: &[hir::Field], | ||
117 | name: &str, | ||
118 | fields_omitted: bool, | ||
119 | ) -> String { | ||
120 | let fields = fields.iter(); | ||
121 | if snippet_cap.is_some() { | ||
122 | format!( | ||
123 | "{name} {{ {}{} }}", | ||
124 | fields | ||
125 | .enumerate() | ||
126 | .map(|(idx, field)| format!("{}${}", field.name(db), idx + 1)) | ||
127 | .format(", "), | ||
128 | if fields_omitted { ", .." } else { "" }, | ||
129 | name = name | ||
130 | ) | ||
131 | } else { | ||
132 | format!( | ||
133 | "{name} {{ {}{} }}", | ||
134 | fields.map(|field| field.name(db)).format(", "), | ||
135 | if fields_omitted { ", .." } else { "" }, | ||
136 | name = name | ||
137 | ) | ||
138 | } | ||
139 | } | ||
140 | |||
141 | fn render_tuple_as_pat(fields: &[hir::Field], name: &str, fields_omitted: bool) -> String { | ||
142 | format!( | ||
143 | "{name}({}{})", | ||
144 | fields.iter().enumerate().map(|(idx, _)| format!("${}", idx + 1)).format(", "), | ||
145 | if fields_omitted { ", .." } else { "" }, | ||
146 | name = name | ||
147 | ) | ||
148 | } | ||
diff --git a/crates/hir/src/code_model.rs b/crates/hir/src/code_model.rs index 73ca6ba9f..1d7e5ddd7 100644 --- a/crates/hir/src/code_model.rs +++ b/crates/hir/src/code_model.rs | |||
@@ -511,6 +511,10 @@ impl Struct { | |||
511 | db.struct_data(self.id).repr.clone() | 511 | db.struct_data(self.id).repr.clone() |
512 | } | 512 | } |
513 | 513 | ||
514 | pub fn kind(self, db: &dyn HirDatabase) -> StructKind { | ||
515 | self.variant_data(db).kind() | ||
516 | } | ||
517 | |||
514 | fn variant_data(self, db: &dyn HirDatabase) -> Arc<VariantData> { | 518 | fn variant_data(self, db: &dyn HirDatabase) -> Arc<VariantData> { |
515 | db.struct_data(self.id).variant_data.clone() | 519 | db.struct_data(self.id).variant_data.clone() |
516 | } | 520 | } |