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