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, format_literal_lookup, visible_fields},
12 RenderContext,
13 },
14 CompletionItem, CompletionItemKind,
15 };
16
render_struct_pat( ctx: RenderContext<'_>, pattern_ctx: &PatternContext, strukt: hir::Struct, local_name: Option<Name>, ) -> Option<CompletionItem>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.unescaped().to_smol_str(), name.to_smol_str());
35 let kind = strukt.kind(ctx.db());
36 let label = format_literal_label(name.as_str(), kind, ctx.snippet_cap());
37 let lookup = format_literal_lookup(name.as_str(), kind);
38 let pat = render_pat(&ctx, pattern_ctx, &escaped_name, kind, &visible_fields, fields_omitted)?;
39
40 let db = ctx.db();
41
42 Some(build_completion(ctx, label, lookup, pat, strukt, strukt.ty(db), false))
43 }
44
render_variant_pat( ctx: RenderContext<'_>, pattern_ctx: &PatternContext, path_ctx: Option<&PathCompletionCtx>, variant: hir::Variant, local_name: Option<Name>, path: Option<&hir::ModPath>, ) -> Option<CompletionItem>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)?;
57 let enum_ty = variant.parent_enum(ctx.db()).ty(ctx.db());
58
59 let (name, escaped_name) = match path {
60 Some(path) => (
61 path.unescaped().display(ctx.db()).to_string().into(),
62 path.display(ctx.db()).to_string().into(),
63 ),
64 None => {
65 let name = local_name.unwrap_or_else(|| variant.name(ctx.db()));
66 (name.unescaped().to_smol_str(), name.to_smol_str())
67 }
68 };
69
70 let (label, lookup, pat) = match path_ctx {
71 Some(PathCompletionCtx { has_call_parens: true, .. }) => {
72 (name.clone(), name, escaped_name.to_string())
73 }
74 _ => {
75 let kind = variant.kind(ctx.db());
76 let label = format_literal_label(name.as_str(), kind, ctx.snippet_cap());
77 let lookup = format_literal_lookup(name.as_str(), kind);
78 let pat = render_pat(
79 &ctx,
80 pattern_ctx,
81 &escaped_name,
82 kind,
83 &visible_fields,
84 fields_omitted,
85 )?;
86 (label, lookup, pat)
87 }
88 };
89
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 ))
99 }
100
build_completion( ctx: RenderContext<'_>, label: SmolStr, lookup: SmolStr, pat: String, def: impl HasAttrs + Copy, adt_ty: hir::Type, is_variant_missing: bool, ) -> CompletionItem101 fn build_completion(
102 ctx: RenderContext<'_>,
103 label: SmolStr,
104 lookup: SmolStr,
105 pat: String,
106 def: impl HasAttrs + Copy,
107 adt_ty: hir::Type,
108 // Missing in context of match statement completions
109 is_variant_missing: bool,
110 ) -> CompletionItem {
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
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)
121 .lookup_by(lookup)
122 .set_relevance(relevance);
123 match ctx.snippet_cap() {
124 Some(snippet_cap) => item.insert_snippet(snippet_cap, pat),
125 None => item.insert_text(pat),
126 };
127 item.build(ctx.db())
128 }
129
render_pat( ctx: &RenderContext<'_>, pattern_ctx: &PatternContext, name: &str, kind: StructKind, fields: &[hir::Field], fields_omitted: bool, ) -> Option<String>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
render_record_as_pat( db: &dyn HirDatabase, snippet_cap: Option<SnippetCap>, fields: &[hir::Field], name: &str, fields_omitted: bool, ) -> String165 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| {
178 f(&format_args!("{}${}", field.name(db).display(db.upcast()), idx + 1))
179 }),
180 if fields_omitted { ", .." } else { "" },
181 name = name
182 )
183 }
184 None => {
185 format!(
186 "{name} {{ {}{} }}",
187 fields.map(|field| field.name(db).to_smol_str()).format(", "),
188 if fields_omitted { ", .." } else { "" },
189 name = name
190 )
191 }
192 }
193 }
194
render_tuple_as_pat( snippet_cap: Option<SnippetCap>, fields: &[hir::Field], name: &str, fields_omitted: bool, ) -> String195 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 }
223