aboutsummaryrefslogtreecommitdiff
path: root/crates/completion/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/completion/src')
-rw-r--r--crates/completion/src/completions.rs33
-rw-r--r--crates/completion/src/completions/pattern.rs178
-rw-r--r--crates/completion/src/context.rs21
-rw-r--r--crates/completion/src/render.rs7
-rw-r--r--crates/completion/src/render/builder_ext.rs1
-rw-r--r--crates/completion/src/render/enum_variant.rs45
-rw-r--r--crates/completion/src/render/pattern.rs148
7 files changed, 359 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};
19use crate::{ 19use 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
3use crate::{CompletionContext, Completions}; 3use crate::{CompletionContext, Completions};
4 4
5/// Completes constats and paths in patterns. 5/// Completes constants and paths in patterns.
6pub(crate) fn complete_pattern(acc: &mut Completions, ctx: &CompletionContext) { 6pub(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) {
42mod tests { 43mod 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#"
131enum E { X }
132
133static FOO: E = E::X;
134struct Bar { f: u32 }
135
136fn 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#"
149struct Bar { f: u32 }
150
151fn 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#"
165struct Foo { bar: String, baz: String }
166struct Bar(String, String);
167struct Baz;
168fn 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#"
181struct Foo { bar: String, baz: String }
182struct Bar(String, String);
183struct Baz;
184fn 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#"
199struct Foo { bar: i32, baz: i32 }
200struct Bar(String, String);
201struct Baz;
202fn 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#"
219mod foo {
220 pub struct Foo { pub bar: i32, baz: i32 }
221 pub struct Bar(pub String, String);
222 pub struct Invisible(String, String);
223}
224use foo::*;
225
226fn 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#"
244struct Foo(i32);
245fn main() {
246 match Foo(92) {
247 <|>(92) => (),
248 }
249}
250"#,
251 r#"
252struct Foo(i32);
253fn 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_;
5pub(crate) mod function; 5pub(crate) mod function;
6pub(crate) mod enum_variant; 6pub(crate) mod enum_variant;
7pub(crate) mod const_; 7pub(crate) mod const_;
8pub(crate) mod pattern;
8pub(crate) mod type_alias; 9pub(crate) mod type_alias;
9 10
10mod builder_ext; 11mod 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#"
132enum Option<T> { Some(T), None }
133use Option::*;
134fn main(value: Option<i32>) {
135 match value {
136 Som<|>
137 }
138}
139"#,
140 r#"
141enum Option<T> { Some(T), None }
142use Option::*;
143fn 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#"
158enum E { Var(i32) }
159fn main() {
160 match E::Var(92) {
161 E::<|>(92) => (),
162 }
163}
164"#,
165 r#"
166enum E { Var(i32) }
167fn 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
3use hir::{db::HirDatabase, HasAttrs, HasVisibility, Name, StructKind};
4use itertools::Itertools;
5
6use crate::{
7 config::SnippetCap, item::CompletionKind, render::RenderContext, CompletionItem,
8 CompletionItemKind,
9};
10
11fn 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
29pub(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
50pub(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
66fn 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
85fn 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
113fn 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
141fn 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}