]>
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 FG |
35 | let kind = strukt.kind(ctx.db()); |
36 | let label = format_literal_label(name.as_str(), kind); | |
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 | ||
f2b60f7d | 40 | Some(build_completion(ctx, label, lookup, pat, strukt)) |
064997fb FG |
41 | } |
42 | ||
43 | pub(crate) fn render_variant_pat( | |
44 | ctx: RenderContext<'_>, | |
45 | pattern_ctx: &PatternContext, | |
46 | path_ctx: Option<&PathCompletionCtx>, | |
47 | variant: hir::Variant, | |
48 | local_name: Option<Name>, | |
49 | path: Option<&hir::ModPath>, | |
50 | ) -> Option<CompletionItem> { | |
51 | let _p = profile::span("render_variant_pat"); | |
52 | ||
53 | let fields = variant.fields(ctx.db()); | |
54 | let (visible_fields, fields_omitted) = visible_fields(ctx.completion, &fields, variant)?; | |
55 | ||
56 | let (name, escaped_name) = match path { | |
f2b60f7d | 57 | Some(path) => (path.unescaped().to_string().into(), path.to_string().into()), |
064997fb FG |
58 | None => { |
59 | let name = local_name.unwrap_or_else(|| variant.name(ctx.db())); | |
f2b60f7d | 60 | (name.unescaped().to_smol_str(), name.to_smol_str()) |
064997fb FG |
61 | } |
62 | }; | |
63 | ||
f2b60f7d FG |
64 | let (label, lookup, pat) = match path_ctx { |
65 | Some(PathCompletionCtx { has_call_parens: true, .. }) => { | |
66 | (name.clone(), name, escaped_name.to_string()) | |
67 | } | |
064997fb FG |
68 | _ => { |
69 | let kind = variant.kind(ctx.db()); | |
70 | let label = format_literal_label(name.as_str(), kind); | |
f2b60f7d | 71 | let lookup = format_literal_lookup(name.as_str(), kind); |
064997fb FG |
72 | let pat = render_pat( |
73 | &ctx, | |
74 | pattern_ctx, | |
75 | &escaped_name, | |
76 | kind, | |
77 | &visible_fields, | |
78 | fields_omitted, | |
79 | )?; | |
f2b60f7d | 80 | (label, lookup, pat) |
064997fb FG |
81 | } |
82 | }; | |
83 | ||
f2b60f7d | 84 | Some(build_completion(ctx, label, lookup, pat, variant)) |
064997fb FG |
85 | } |
86 | ||
87 | fn build_completion( | |
88 | ctx: RenderContext<'_>, | |
89 | label: SmolStr, | |
f2b60f7d | 90 | lookup: SmolStr, |
064997fb FG |
91 | pat: String, |
92 | def: impl HasAttrs + Copy, | |
93 | ) -> CompletionItem { | |
94 | let mut item = CompletionItem::new(CompletionItemKind::Binding, ctx.source_range(), label); | |
95 | item.set_documentation(ctx.docs(def)) | |
96 | .set_deprecated(ctx.is_deprecated(def)) | |
97 | .detail(&pat) | |
f2b60f7d | 98 | .lookup_by(lookup) |
064997fb FG |
99 | .set_relevance(ctx.completion_relevance()); |
100 | match ctx.snippet_cap() { | |
101 | Some(snippet_cap) => item.insert_snippet(snippet_cap, pat), | |
102 | None => item.insert_text(pat), | |
103 | }; | |
104 | item.build() | |
105 | } | |
106 | ||
107 | fn render_pat( | |
108 | ctx: &RenderContext<'_>, | |
109 | pattern_ctx: &PatternContext, | |
110 | name: &str, | |
111 | kind: StructKind, | |
112 | fields: &[hir::Field], | |
113 | fields_omitted: bool, | |
114 | ) -> Option<String> { | |
115 | let mut pat = match kind { | |
116 | StructKind::Tuple => render_tuple_as_pat(ctx.snippet_cap(), fields, name, fields_omitted), | |
117 | StructKind::Record => { | |
118 | render_record_as_pat(ctx.db(), ctx.snippet_cap(), fields, name, fields_omitted) | |
119 | } | |
120 | StructKind::Unit => name.to_string(), | |
121 | }; | |
122 | ||
123 | let needs_ascription = matches!( | |
124 | pattern_ctx, | |
125 | PatternContext { | |
126 | param_ctx: Some(ParamContext { kind: ParamKind::Function(_), .. }), | |
127 | has_type_ascription: false, | |
128 | .. | |
129 | } | |
130 | ); | |
131 | if needs_ascription { | |
132 | pat.push(':'); | |
133 | pat.push(' '); | |
134 | pat.push_str(name); | |
135 | } | |
136 | if ctx.snippet_cap().is_some() { | |
137 | pat.push_str("$0"); | |
138 | } | |
139 | Some(pat) | |
140 | } | |
141 | ||
142 | fn render_record_as_pat( | |
143 | db: &dyn HirDatabase, | |
144 | snippet_cap: Option<SnippetCap>, | |
145 | fields: &[hir::Field], | |
146 | name: &str, | |
147 | fields_omitted: bool, | |
148 | ) -> String { | |
149 | let fields = fields.iter(); | |
150 | match snippet_cap { | |
151 | Some(_) => { | |
152 | format!( | |
153 | "{name} {{ {}{} }}", | |
154 | fields.enumerate().format_with(", ", |(idx, field), f| { | |
f2b60f7d | 155 | f(&format_args!("{}${}", field.name(db), idx + 1)) |
064997fb FG |
156 | }), |
157 | if fields_omitted { ", .." } else { "" }, | |
158 | name = name | |
159 | ) | |
160 | } | |
161 | None => { | |
162 | format!( | |
163 | "{name} {{ {}{} }}", | |
f2b60f7d | 164 | fields.map(|field| field.name(db).to_smol_str()).format(", "), |
064997fb FG |
165 | if fields_omitted { ", .." } else { "" }, |
166 | name = name | |
167 | ) | |
168 | } | |
169 | } | |
170 | } | |
171 | ||
172 | fn render_tuple_as_pat( | |
173 | snippet_cap: Option<SnippetCap>, | |
174 | fields: &[hir::Field], | |
175 | name: &str, | |
176 | fields_omitted: bool, | |
177 | ) -> String { | |
178 | let fields = fields.iter(); | |
179 | match snippet_cap { | |
180 | Some(_) => { | |
181 | format!( | |
182 | "{name}({}{})", | |
183 | fields | |
184 | .enumerate() | |
185 | .format_with(", ", |(idx, _), f| { f(&format_args!("${}", idx + 1)) }), | |
186 | if fields_omitted { ", .." } else { "" }, | |
187 | name = name | |
188 | ) | |
189 | } | |
190 | None => { | |
191 | format!( | |
192 | "{name}({}{})", | |
193 | fields.enumerate().map(|(idx, _)| idx).format(", "), | |
194 | if fields_omitted { ", .." } else { "" }, | |
195 | name = name | |
196 | ) | |
197 | } | |
198 | } | |
199 | } |