diff options
-rw-r--r-- | crates/completion/src/completions.rs | 33 | ||||
-rw-r--r-- | crates/completion/src/completions/pattern.rs | 154 | ||||
-rw-r--r-- | crates/completion/src/context.rs | 21 | ||||
-rw-r--r-- | crates/completion/src/render.rs | 1 | ||||
-rw-r--r-- | crates/completion/src/render/pattern.rs | 128 | ||||
-rw-r--r-- | crates/hir/src/code_model.rs | 4 |
6 files changed, 315 insertions, 26 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..496f0b040 100644 --- a/crates/completion/src/completions/pattern.rs +++ b/crates/completion/src/completions/pattern.rs | |||
@@ -1,10 +1,12 @@ | |||
1 | //! Completes constats and paths in patterns. | 1 | //! Completes constats and paths in patterns. |
2 | 2 | ||
3 | use hir::StructKind; | ||
4 | |||
3 | use crate::{CompletionContext, Completions}; | 5 | use crate::{CompletionContext, Completions}; |
4 | 6 | ||
5 | /// Completes constats and paths in patterns. | 7 | /// Completes constants and paths in patterns. |
6 | pub(crate) fn complete_pattern(acc: &mut Completions, ctx: &CompletionContext) { | 8 | pub(crate) fn complete_pattern(acc: &mut Completions, ctx: &CompletionContext) { |
7 | if !(ctx.is_pat_binding_or_const || ctx.is_irrefutable_let_pat_binding) { | 9 | if !(ctx.is_pat_binding_or_const || ctx.is_irrefutable_pat_binding) { |
8 | return; | 10 | return; |
9 | } | 11 | } |
10 | if ctx.record_pat_syntax.is_some() { | 12 | if ctx.record_pat_syntax.is_some() { |
@@ -15,20 +17,25 @@ pub(crate) fn complete_pattern(acc: &mut Completions, ctx: &CompletionContext) { | |||
15 | // suggest variants + auto-imports | 17 | // suggest variants + auto-imports |
16 | ctx.scope.process_all_names(&mut |name, res| { | 18 | ctx.scope.process_all_names(&mut |name, res| { |
17 | let add_resolution = match &res { | 19 | let add_resolution = match &res { |
18 | hir::ScopeDef::ModuleDef(def) => { | 20 | hir::ScopeDef::ModuleDef(def) => match def { |
19 | if ctx.is_irrefutable_let_pat_binding { | 21 | hir::ModuleDef::Adt(hir::Adt::Struct(strukt)) => { |
20 | matches!(def, hir::ModuleDef::Adt(hir::Adt::Struct(_))) | 22 | acc.add_struct_pat(ctx, strukt.clone(), Some(name.clone())); |
21 | } else { | 23 | true |
22 | matches!( | 24 | } |
23 | def, | 25 | hir::ModuleDef::Variant(variant) |
24 | hir::ModuleDef::Adt(hir::Adt::Enum(..)) | 26 | if !ctx.is_irrefutable_pat_binding |
25 | | hir::ModuleDef::Adt(hir::Adt::Struct(..)) | 27 | // render_resolution already does some pattern completion tricks for tuple variants |
26 | | hir::ModuleDef::Variant(..) | 28 | && variant.kind(ctx.db) == StructKind::Record => |
27 | | hir::ModuleDef::Const(..) | 29 | { |
28 | | hir::ModuleDef::Module(..) | 30 | acc.add_variant_pat(ctx, variant.clone(), Some(name.clone())); |
29 | ) | 31 | true |
30 | } | 32 | } |
31 | } | 33 | hir::ModuleDef::Adt(hir::Adt::Enum(..)) |
34 | | hir::ModuleDef::Variant(..) | ||
35 | | hir::ModuleDef::Const(..) | ||
36 | | hir::ModuleDef::Module(..) => !ctx.is_irrefutable_pat_binding, | ||
37 | _ => false, | ||
38 | }, | ||
32 | hir::ScopeDef::MacroDef(_) => true, | 39 | hir::ScopeDef::MacroDef(_) => true, |
33 | _ => false, | 40 | _ => false, |
34 | }; | 41 | }; |
@@ -49,6 +56,11 @@ mod tests { | |||
49 | expect.assert_eq(&actual) | 56 | expect.assert_eq(&actual) |
50 | } | 57 | } |
51 | 58 | ||
59 | fn check_snippet(ra_fixture: &str, expect: Expect) { | ||
60 | let actual = completion_list(ra_fixture, CompletionKind::Snippet); | ||
61 | expect.assert_eq(&actual) | ||
62 | } | ||
63 | |||
52 | #[test] | 64 | #[test] |
53 | fn completes_enum_variants_and_modules() { | 65 | fn completes_enum_variants_and_modules() { |
54 | check( | 66 | check( |
@@ -114,4 +126,116 @@ fn foo() { | |||
114 | "#]], | 126 | "#]], |
115 | ); | 127 | ); |
116 | } | 128 | } |
129 | |||
130 | #[test] | ||
131 | fn completes_in_param() { | ||
132 | check( | ||
133 | r#" | ||
134 | enum E { X } | ||
135 | |||
136 | static FOO: E = E::X; | ||
137 | struct Bar { f: u32 } | ||
138 | |||
139 | fn foo(<|>) { | ||
140 | } | ||
141 | "#, | ||
142 | expect![[r#" | ||
143 | st Bar | ||
144 | "#]], | ||
145 | ); | ||
146 | } | ||
147 | |||
148 | #[test] | ||
149 | fn completes_pat_in_let() { | ||
150 | check_snippet( | ||
151 | r#" | ||
152 | struct Bar { f: u32 } | ||
153 | |||
154 | fn foo() { | ||
155 | let <|> | ||
156 | } | ||
157 | "#, | ||
158 | expect![[r#" | ||
159 | bn Bar Bar { f }$0 | ||
160 | "#]], | ||
161 | ); | ||
162 | } | ||
163 | |||
164 | #[test] | ||
165 | fn completes_param_pattern() { | ||
166 | check_snippet( | ||
167 | r#" | ||
168 | struct Foo { bar: String, baz: String } | ||
169 | struct Bar(String, String); | ||
170 | struct Baz; | ||
171 | fn outer(<|>) {} | ||
172 | "#, | ||
173 | expect![[r#" | ||
174 | bn Foo Foo { bar, baz }: Foo$0 | ||
175 | bn Bar Bar($1, $2): Bar$0 | ||
176 | "#]], | ||
177 | ) | ||
178 | } | ||
179 | |||
180 | #[test] | ||
181 | fn completes_let_pattern() { | ||
182 | check_snippet( | ||
183 | r#" | ||
184 | struct Foo { bar: String, baz: String } | ||
185 | struct Bar(String, String); | ||
186 | struct Baz; | ||
187 | fn outer() { | ||
188 | let <|> | ||
189 | } | ||
190 | "#, | ||
191 | expect![[r#" | ||
192 | bn Foo Foo { bar, baz }$0 | ||
193 | bn Bar Bar($1, $2)$0 | ||
194 | "#]], | ||
195 | ) | ||
196 | } | ||
197 | |||
198 | #[test] | ||
199 | fn completes_refutable_pattern() { | ||
200 | check_snippet( | ||
201 | r#" | ||
202 | struct Foo { bar: i32, baz: i32 } | ||
203 | struct Bar(String, String); | ||
204 | struct Baz; | ||
205 | fn outer() { | ||
206 | match () { | ||
207 | <|> | ||
208 | } | ||
209 | } | ||
210 | "#, | ||
211 | expect![[r#" | ||
212 | bn Foo Foo { bar, baz }$0 | ||
213 | bn Bar Bar($1, $2)$0 | ||
214 | "#]], | ||
215 | ) | ||
216 | } | ||
217 | |||
218 | #[test] | ||
219 | fn omits_private_fields_pat() { | ||
220 | check_snippet( | ||
221 | r#" | ||
222 | mod foo { | ||
223 | pub struct Foo { pub bar: i32, baz: i32 } | ||
224 | pub struct Bar(pub String, String); | ||
225 | pub struct Invisible(String, String); | ||
226 | } | ||
227 | use foo::*; | ||
228 | |||
229 | fn outer() { | ||
230 | match () { | ||
231 | <|> | ||
232 | } | ||
233 | } | ||
234 | "#, | ||
235 | expect![[r#" | ||
236 | bn Foo Foo { bar, .. }$0 | ||
237 | bn Bar Bar($1, ..)$0 | ||
238 | "#]], | ||
239 | ) | ||
240 | } | ||
117 | } | 241 | } |
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..945158d10 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; |
diff --git a/crates/completion/src/render/pattern.rs b/crates/completion/src/render/pattern.rs new file mode 100644 index 000000000..e20b0027b --- /dev/null +++ b/crates/completion/src/render/pattern.rs | |||
@@ -0,0 +1,128 @@ | |||
1 | //! Renderer for patterns. | ||
2 | |||
3 | use hir::{db::HirDatabase, HasVisibility, Name, StructKind}; | ||
4 | use itertools::Itertools; | ||
5 | |||
6 | use crate::{item::CompletionKind, render::RenderContext, CompletionItem, CompletionItemKind}; | ||
7 | |||
8 | pub(crate) fn render_struct_pat<'a>( | ||
9 | ctx: RenderContext<'a>, | ||
10 | strukt: hir::Struct, | ||
11 | local_name: Option<Name>, | ||
12 | ) -> Option<CompletionItem> { | ||
13 | let _p = profile::span("render_struct_pat"); | ||
14 | |||
15 | let module = ctx.completion.scope.module()?; | ||
16 | let fields = strukt.fields(ctx.db()); | ||
17 | let n_fields = fields.len(); | ||
18 | let fields = fields | ||
19 | .into_iter() | ||
20 | .filter(|field| field.is_visible_from(ctx.db(), module)) | ||
21 | .collect::<Vec<_>>(); | ||
22 | |||
23 | if fields.is_empty() { | ||
24 | // Matching a struct without matching its fields is pointless, unlike matching a Variant without its fields | ||
25 | return None; | ||
26 | } | ||
27 | let fields_omitted = n_fields - fields.len() > 0; | ||
28 | |||
29 | let name = local_name.unwrap_or_else(|| strukt.name(ctx.db())).to_string(); | ||
30 | let mut pat = match strukt.kind(ctx.db()) { | ||
31 | StructKind::Tuple if ctx.snippet_cap().is_some() => { | ||
32 | render_tuple_as_pat(&fields, &name, fields_omitted) | ||
33 | } | ||
34 | StructKind::Record => render_record_as_pat(ctx.db(), &fields, &name, fields_omitted), | ||
35 | _ => return None, | ||
36 | }; | ||
37 | |||
38 | if ctx.completion.is_param { | ||
39 | pat.push(':'); | ||
40 | pat.push(' '); | ||
41 | pat.push_str(&name); | ||
42 | } | ||
43 | if ctx.snippet_cap().is_some() { | ||
44 | pat.push_str("$0"); | ||
45 | } | ||
46 | |||
47 | let mut completion = CompletionItem::new(CompletionKind::Snippet, ctx.source_range(), name) | ||
48 | .kind(CompletionItemKind::Binding) | ||
49 | .set_documentation(ctx.docs(strukt)) | ||
50 | .set_deprecated(ctx.is_deprecated(strukt)) | ||
51 | .detail(&pat); | ||
52 | if let Some(snippet_cap) = ctx.snippet_cap() { | ||
53 | completion = completion.insert_snippet(snippet_cap, pat); | ||
54 | } else { | ||
55 | completion = completion.insert_text(pat); | ||
56 | } | ||
57 | Some(completion.build()) | ||
58 | } | ||
59 | |||
60 | pub(crate) fn render_variant_pat<'a>( | ||
61 | ctx: RenderContext<'a>, | ||
62 | variant: hir::Variant, | ||
63 | local_name: Option<Name>, | ||
64 | ) -> Option<CompletionItem> { | ||
65 | let _p = profile::span("render_variant_pat"); | ||
66 | |||
67 | let module = ctx.completion.scope.module()?; | ||
68 | let fields = variant.fields(ctx.db()); | ||
69 | let n_fields = fields.len(); | ||
70 | let fields = fields | ||
71 | .into_iter() | ||
72 | .filter(|field| field.is_visible_from(ctx.db(), module)) | ||
73 | .collect::<Vec<_>>(); | ||
74 | |||
75 | let fields_omitted = n_fields - fields.len() > 0; | ||
76 | |||
77 | let name = local_name.unwrap_or_else(|| variant.name(ctx.db())).to_string(); | ||
78 | let mut pat = match variant.kind(ctx.db()) { | ||
79 | StructKind::Tuple if ctx.snippet_cap().is_some() => { | ||
80 | render_tuple_as_pat(&fields, &name, fields_omitted) | ||
81 | } | ||
82 | StructKind::Record => render_record_as_pat(ctx.db(), &fields, &name, fields_omitted), | ||
83 | _ => return None, | ||
84 | }; | ||
85 | |||
86 | if ctx.completion.is_param { | ||
87 | pat.push(':'); | ||
88 | pat.push(' '); | ||
89 | pat.push_str(&name); | ||
90 | } | ||
91 | if ctx.snippet_cap().is_some() { | ||
92 | pat.push_str("$0"); | ||
93 | } | ||
94 | let mut completion = CompletionItem::new(CompletionKind::Snippet, ctx.source_range(), name) | ||
95 | .kind(CompletionItemKind::Binding) | ||
96 | .set_documentation(ctx.docs(variant)) | ||
97 | .set_deprecated(ctx.is_deprecated(variant)) | ||
98 | .detail(&pat); | ||
99 | if let Some(snippet_cap) = ctx.snippet_cap() { | ||
100 | completion = completion.insert_snippet(snippet_cap, pat); | ||
101 | } else { | ||
102 | completion = completion.insert_text(pat); | ||
103 | } | ||
104 | Some(completion.build()) | ||
105 | } | ||
106 | |||
107 | fn render_record_as_pat( | ||
108 | db: &dyn HirDatabase, | ||
109 | fields: &[hir::Field], | ||
110 | name: &str, | ||
111 | fields_omitted: bool, | ||
112 | ) -> String { | ||
113 | format!( | ||
114 | "{name} {{ {}{} }}", | ||
115 | fields.into_iter().map(|field| field.name(db)).format(", "), | ||
116 | if fields_omitted { ", .." } else { "" }, | ||
117 | name = name | ||
118 | ) | ||
119 | } | ||
120 | |||
121 | fn render_tuple_as_pat(fields: &[hir::Field], name: &str, fields_omitted: bool) -> String { | ||
122 | format!( | ||
123 | "{name}({}{})", | ||
124 | fields.into_iter().enumerate().map(|(idx, _)| format!("${}", idx + 1)).format(", "), | ||
125 | if fields_omitted { ", .." } else { "" }, | ||
126 | name = name | ||
127 | ) | ||
128 | } | ||
diff --git a/crates/hir/src/code_model.rs b/crates/hir/src/code_model.rs index d6c7e71ea..dbc937e4f 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 | } |