]>
Commit | Line | Data |
---|---|---|
064997fb FG |
1 | //! Renderer for patterns. |
2 | ||
3 | use hir::{db::HirDatabase, HasAttrs, Name, StructKind}; | |
4 | use ide_db::SnippetCap; | |
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 { | |
f2b60f7d | 60 | Some(path) => (path.unescaped().to_string().into(), path.to_string().into()), |
064997fb FG |
61 | None => { |
62 | let name = local_name.unwrap_or_else(|| variant.name(ctx.db())); | |
f2b60f7d | 63 | (name.unescaped().to_smol_str(), name.to_smol_str()) |
064997fb FG |
64 | } |
65 | }; | |
66 | ||
f2b60f7d FG |
67 | let (label, lookup, pat) = match path_ctx { |
68 | Some(PathCompletionCtx { has_call_parens: true, .. }) => { | |
69 | (name.clone(), name, escaped_name.to_string()) | |
70 | } | |
064997fb FG |
71 | _ => { |
72 | let kind = variant.kind(ctx.db()); | |
9c376795 | 73 | let label = format_literal_label(name.as_str(), kind, ctx.snippet_cap()); |
f2b60f7d | 74 | let lookup = format_literal_lookup(name.as_str(), kind); |
064997fb FG |
75 | let pat = render_pat( |
76 | &ctx, | |
77 | pattern_ctx, | |
78 | &escaped_name, | |
79 | kind, | |
80 | &visible_fields, | |
81 | fields_omitted, | |
82 | )?; | |
f2b60f7d | 83 | (label, lookup, pat) |
064997fb FG |
84 | } |
85 | }; | |
86 | ||
353b0b11 FG |
87 | Some(build_completion( |
88 | ctx, | |
89 | label, | |
90 | lookup, | |
91 | pat, | |
92 | variant, | |
93 | enum_ty, | |
94 | pattern_ctx.missing_variants.contains(&variant), | |
95 | )) | |
064997fb FG |
96 | } |
97 | ||
98 | fn build_completion( | |
99 | ctx: RenderContext<'_>, | |
100 | label: SmolStr, | |
f2b60f7d | 101 | lookup: SmolStr, |
064997fb FG |
102 | pat: String, |
103 | def: impl HasAttrs + Copy, | |
353b0b11 FG |
104 | adt_ty: hir::Type, |
105 | // Missing in context of match statement completions | |
106 | is_variant_missing: bool, | |
064997fb | 107 | ) -> CompletionItem { |
353b0b11 FG |
108 | let mut relevance = ctx.completion_relevance(); |
109 | ||
110 | if is_variant_missing { | |
111 | relevance.type_match = super::compute_type_match(ctx.completion, &adt_ty); | |
112 | } | |
113 | ||
064997fb FG |
114 | let mut item = CompletionItem::new(CompletionItemKind::Binding, ctx.source_range(), label); |
115 | item.set_documentation(ctx.docs(def)) | |
116 | .set_deprecated(ctx.is_deprecated(def)) | |
117 | .detail(&pat) | |
f2b60f7d | 118 | .lookup_by(lookup) |
353b0b11 | 119 | .set_relevance(relevance); |
064997fb FG |
120 | match ctx.snippet_cap() { |
121 | Some(snippet_cap) => item.insert_snippet(snippet_cap, pat), | |
122 | None => item.insert_text(pat), | |
123 | }; | |
124 | item.build() | |
125 | } | |
126 | ||
127 | fn render_pat( | |
128 | ctx: &RenderContext<'_>, | |
129 | pattern_ctx: &PatternContext, | |
130 | name: &str, | |
131 | kind: StructKind, | |
132 | fields: &[hir::Field], | |
133 | fields_omitted: bool, | |
134 | ) -> Option<String> { | |
135 | let mut pat = match kind { | |
136 | StructKind::Tuple => render_tuple_as_pat(ctx.snippet_cap(), fields, name, fields_omitted), | |
137 | StructKind::Record => { | |
138 | render_record_as_pat(ctx.db(), ctx.snippet_cap(), fields, name, fields_omitted) | |
139 | } | |
140 | StructKind::Unit => name.to_string(), | |
141 | }; | |
142 | ||
143 | let needs_ascription = matches!( | |
144 | pattern_ctx, | |
145 | PatternContext { | |
146 | param_ctx: Some(ParamContext { kind: ParamKind::Function(_), .. }), | |
147 | has_type_ascription: false, | |
148 | .. | |
149 | } | |
150 | ); | |
151 | if needs_ascription { | |
152 | pat.push(':'); | |
153 | pat.push(' '); | |
154 | pat.push_str(name); | |
155 | } | |
156 | if ctx.snippet_cap().is_some() { | |
157 | pat.push_str("$0"); | |
158 | } | |
159 | Some(pat) | |
160 | } | |
161 | ||
162 | fn render_record_as_pat( | |
163 | db: &dyn HirDatabase, | |
164 | snippet_cap: Option<SnippetCap>, | |
165 | fields: &[hir::Field], | |
166 | name: &str, | |
167 | fields_omitted: bool, | |
168 | ) -> String { | |
169 | let fields = fields.iter(); | |
170 | match snippet_cap { | |
171 | Some(_) => { | |
172 | format!( | |
173 | "{name} {{ {}{} }}", | |
174 | fields.enumerate().format_with(", ", |(idx, field), f| { | |
f2b60f7d | 175 | f(&format_args!("{}${}", field.name(db), idx + 1)) |
064997fb FG |
176 | }), |
177 | if fields_omitted { ", .." } else { "" }, | |
178 | name = name | |
179 | ) | |
180 | } | |
181 | None => { | |
182 | format!( | |
183 | "{name} {{ {}{} }}", | |
f2b60f7d | 184 | fields.map(|field| field.name(db).to_smol_str()).format(", "), |
064997fb FG |
185 | if fields_omitted { ", .." } else { "" }, |
186 | name = name | |
187 | ) | |
188 | } | |
189 | } | |
190 | } | |
191 | ||
192 | fn render_tuple_as_pat( | |
193 | snippet_cap: Option<SnippetCap>, | |
194 | fields: &[hir::Field], | |
195 | name: &str, | |
196 | fields_omitted: bool, | |
197 | ) -> String { | |
198 | let fields = fields.iter(); | |
199 | match snippet_cap { | |
200 | Some(_) => { | |
201 | format!( | |
202 | "{name}({}{})", | |
203 | fields | |
204 | .enumerate() | |
205 | .format_with(", ", |(idx, _), f| { f(&format_args!("${}", idx + 1)) }), | |
206 | if fields_omitted { ", .." } else { "" }, | |
207 | name = name | |
208 | ) | |
209 | } | |
210 | None => { | |
211 | format!( | |
212 | "{name}({}{})", | |
213 | fields.enumerate().map(|(idx, _)| idx).format(", "), | |
214 | if fields_omitted { ", .." } else { "" }, | |
215 | name = name | |
216 | ) | |
217 | } | |
218 | } | |
219 | } |