]>
Commit | Line | Data |
---|---|---|
064997fb FG |
1 | //! Renderer for patterns. |
2 | ||
781aab86 FG |
3 | use hir::{db::HirDatabase, Name, StructKind}; |
4 | use ide_db::{documentation::HasDocs, SnippetCap}; | |
064997fb FG |
5 | use itertools::Itertools; |
6 | use syntax::SmolStr; | |
7 | ||
8 | use crate::{ | |
9 | context::{ParamContext, ParamKind, PathCompletionCtx, PatternContext}, | |
10 | render::{ | |
f2b60f7d | 11 | variant::{format_literal_label, format_literal_lookup, visible_fields}, |
064997fb FG |
12 | RenderContext, |
13 | }, | |
14 | CompletionItem, CompletionItemKind, | |
15 | }; | |
16 | ||
17 | pub(crate) fn render_struct_pat( | |
18 | ctx: RenderContext<'_>, | |
19 | pattern_ctx: &PatternContext, | |
20 | strukt: hir::Struct, | |
21 | local_name: Option<Name>, | |
22 | ) -> Option<CompletionItem> { | |
23 | let _p = profile::span("render_struct_pat"); | |
24 | ||
25 | let fields = strukt.fields(ctx.db()); | |
26 | let (visible_fields, fields_omitted) = visible_fields(ctx.completion, &fields, strukt)?; | |
27 | ||
28 | if visible_fields.is_empty() { | |
29 | // Matching a struct without matching its fields is pointless, unlike matching a Variant without its fields | |
30 | return None; | |
31 | } | |
32 | ||
33 | let name = local_name.unwrap_or_else(|| strukt.name(ctx.db())); | |
f2b60f7d | 34 | let (name, escaped_name) = (name.unescaped().to_smol_str(), name.to_smol_str()); |
064997fb | 35 | let kind = strukt.kind(ctx.db()); |
9c376795 | 36 | let label = format_literal_label(name.as_str(), kind, ctx.snippet_cap()); |
f2b60f7d | 37 | let lookup = format_literal_lookup(name.as_str(), kind); |
064997fb FG |
38 | let pat = render_pat(&ctx, pattern_ctx, &escaped_name, kind, &visible_fields, fields_omitted)?; |
39 | ||
353b0b11 FG |
40 | let db = ctx.db(); |
41 | ||
42 | Some(build_completion(ctx, label, lookup, pat, strukt, strukt.ty(db), false)) | |
064997fb FG |
43 | } |
44 | ||
45 | pub(crate) fn render_variant_pat( | |
46 | ctx: RenderContext<'_>, | |
47 | pattern_ctx: &PatternContext, | |
48 | path_ctx: Option<&PathCompletionCtx>, | |
49 | variant: hir::Variant, | |
50 | local_name: Option<Name>, | |
51 | path: Option<&hir::ModPath>, | |
52 | ) -> Option<CompletionItem> { | |
53 | let _p = profile::span("render_variant_pat"); | |
54 | ||
55 | let fields = variant.fields(ctx.db()); | |
56 | let (visible_fields, fields_omitted) = visible_fields(ctx.completion, &fields, variant)?; | |
353b0b11 | 57 | let enum_ty = variant.parent_enum(ctx.db()).ty(ctx.db()); |
064997fb FG |
58 | |
59 | let (name, escaped_name) = match path { | |
fe692bf9 FG |
60 | Some(path) => ( |
61 | path.unescaped().display(ctx.db()).to_string().into(), | |
62 | path.display(ctx.db()).to_string().into(), | |
63 | ), | |
064997fb FG |
64 | None => { |
65 | let name = local_name.unwrap_or_else(|| variant.name(ctx.db())); | |
f2b60f7d | 66 | (name.unescaped().to_smol_str(), name.to_smol_str()) |
064997fb FG |
67 | } |
68 | }; | |
69 | ||
f2b60f7d FG |
70 | let (label, lookup, pat) = match path_ctx { |
71 | Some(PathCompletionCtx { has_call_parens: true, .. }) => { | |
72 | (name.clone(), name, escaped_name.to_string()) | |
73 | } | |
064997fb FG |
74 | _ => { |
75 | let kind = variant.kind(ctx.db()); | |
9c376795 | 76 | let label = format_literal_label(name.as_str(), kind, ctx.snippet_cap()); |
f2b60f7d | 77 | let lookup = format_literal_lookup(name.as_str(), kind); |
064997fb FG |
78 | let pat = render_pat( |
79 | &ctx, | |
80 | pattern_ctx, | |
81 | &escaped_name, | |
82 | kind, | |
83 | &visible_fields, | |
84 | fields_omitted, | |
85 | )?; | |
f2b60f7d | 86 | (label, lookup, pat) |
064997fb FG |
87 | } |
88 | }; | |
89 | ||
353b0b11 FG |
90 | Some(build_completion( |
91 | ctx, | |
92 | label, | |
93 | lookup, | |
94 | pat, | |
95 | variant, | |
96 | enum_ty, | |
97 | pattern_ctx.missing_variants.contains(&variant), | |
98 | )) | |
064997fb FG |
99 | } |
100 | ||
101 | fn build_completion( | |
102 | ctx: RenderContext<'_>, | |
103 | label: SmolStr, | |
f2b60f7d | 104 | lookup: SmolStr, |
064997fb | 105 | pat: String, |
781aab86 | 106 | def: impl HasDocs + Copy, |
353b0b11 FG |
107 | adt_ty: hir::Type, |
108 | // Missing in context of match statement completions | |
109 | is_variant_missing: bool, | |
064997fb | 110 | ) -> CompletionItem { |
353b0b11 FG |
111 | let mut relevance = ctx.completion_relevance(); |
112 | ||
113 | if is_variant_missing { | |
114 | relevance.type_match = super::compute_type_match(ctx.completion, &adt_ty); | |
115 | } | |
116 | ||
064997fb FG |
117 | let mut item = CompletionItem::new(CompletionItemKind::Binding, ctx.source_range(), label); |
118 | item.set_documentation(ctx.docs(def)) | |
119 | .set_deprecated(ctx.is_deprecated(def)) | |
120 | .detail(&pat) | |
f2b60f7d | 121 | .lookup_by(lookup) |
353b0b11 | 122 | .set_relevance(relevance); |
064997fb FG |
123 | match ctx.snippet_cap() { |
124 | Some(snippet_cap) => item.insert_snippet(snippet_cap, pat), | |
125 | None => item.insert_text(pat), | |
126 | }; | |
fe692bf9 | 127 | item.build(ctx.db()) |
064997fb FG |
128 | } |
129 | ||
130 | fn render_pat( | |
131 | ctx: &RenderContext<'_>, | |
132 | pattern_ctx: &PatternContext, | |
133 | name: &str, | |
134 | kind: StructKind, | |
135 | fields: &[hir::Field], | |
136 | fields_omitted: bool, | |
137 | ) -> Option<String> { | |
138 | let mut pat = match kind { | |
139 | StructKind::Tuple => render_tuple_as_pat(ctx.snippet_cap(), fields, name, fields_omitted), | |
140 | StructKind::Record => { | |
141 | render_record_as_pat(ctx.db(), ctx.snippet_cap(), fields, name, fields_omitted) | |
142 | } | |
143 | StructKind::Unit => name.to_string(), | |
144 | }; | |
145 | ||
146 | let needs_ascription = matches!( | |
147 | pattern_ctx, | |
148 | PatternContext { | |
149 | param_ctx: Some(ParamContext { kind: ParamKind::Function(_), .. }), | |
150 | has_type_ascription: false, | |
151 | .. | |
152 | } | |
153 | ); | |
154 | if needs_ascription { | |
155 | pat.push(':'); | |
156 | pat.push(' '); | |
157 | pat.push_str(name); | |
158 | } | |
159 | if ctx.snippet_cap().is_some() { | |
160 | pat.push_str("$0"); | |
161 | } | |
162 | Some(pat) | |
163 | } | |
164 | ||
165 | fn render_record_as_pat( | |
166 | db: &dyn HirDatabase, | |
167 | snippet_cap: Option<SnippetCap>, | |
168 | fields: &[hir::Field], | |
169 | name: &str, | |
170 | fields_omitted: bool, | |
171 | ) -> String { | |
172 | let fields = fields.iter(); | |
173 | match snippet_cap { | |
174 | Some(_) => { | |
175 | format!( | |
176 | "{name} {{ {}{} }}", | |
177 | fields.enumerate().format_with(", ", |(idx, field), f| { | |
fe692bf9 | 178 | f(&format_args!("{}${}", field.name(db).display(db.upcast()), idx + 1)) |
064997fb FG |
179 | }), |
180 | if fields_omitted { ", .." } else { "" }, | |
181 | name = name | |
182 | ) | |
183 | } | |
184 | None => { | |
185 | format!( | |
186 | "{name} {{ {}{} }}", | |
f2b60f7d | 187 | fields.map(|field| field.name(db).to_smol_str()).format(", "), |
064997fb FG |
188 | if fields_omitted { ", .." } else { "" }, |
189 | name = name | |
190 | ) | |
191 | } | |
192 | } | |
193 | } | |
194 | ||
195 | fn render_tuple_as_pat( | |
196 | snippet_cap: Option<SnippetCap>, | |
197 | fields: &[hir::Field], | |
198 | name: &str, | |
199 | fields_omitted: bool, | |
200 | ) -> String { | |
201 | let fields = fields.iter(); | |
202 | match snippet_cap { | |
203 | Some(_) => { | |
204 | format!( | |
205 | "{name}({}{})", | |
206 | fields | |
207 | .enumerate() | |
208 | .format_with(", ", |(idx, _), f| { f(&format_args!("${}", idx + 1)) }), | |
209 | if fields_omitted { ", .." } else { "" }, | |
210 | name = name | |
211 | ) | |
212 | } | |
213 | None => { | |
214 | format!( | |
215 | "{name}({}{})", | |
216 | fields.enumerate().map(|(idx, _)| idx).format(", "), | |
217 | if fields_omitted { ", .." } else { "" }, | |
218 | name = name | |
219 | ) | |
220 | } | |
221 | } | |
222 | } |