1use hir::{Name, StructKind, db::HirDatabase};
4use ide_db::{SnippetCap, documentation::HasDocs};
5use itertools::Itertools;
6use syntax::{Edition, SmolStr, ToSmolStr};
7
8use crate::{
9 CompletionItem, CompletionItemKind,
10 context::{ParamContext, ParamKind, PathCompletionCtx, PatternContext},
11 render::{
12 RenderContext,
13 variant::{format_literal_label, format_literal_lookup, visible_fields},
14 },
15};
16
17pub(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 = tracing::info_span!("render_struct_pat").entered();
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 return None;
31 }
32
33 let name = local_name.unwrap_or_else(|| strukt.name(ctx.db()));
34 let (name, escaped_name) =
35 (name.as_str(), name.display(ctx.db(), ctx.completion.edition).to_smolstr());
36 let kind = strukt.kind(ctx.db());
37 let label = format_literal_label(name, kind, ctx.snippet_cap());
38 let lookup = format_literal_lookup(name, kind);
39 let pat = render_pat(&ctx, pattern_ctx, &escaped_name, kind, &visible_fields, fields_omitted)?;
40
41 let db = ctx.db();
42
43 Some(build_completion(ctx, label, lookup, pat, strukt, strukt.ty(db), false))
44}
45
46pub(crate) fn render_variant_pat(
47 ctx: RenderContext<'_, '_>,
48 pattern_ctx: &PatternContext,
49 path_ctx: Option<&PathCompletionCtx<'_>>,
50 variant: hir::EnumVariant,
51 local_name: Option<Name>,
52 path: Option<&hir::ModPath>,
53) -> Option<CompletionItem> {
54 let _p = tracing::info_span!("render_variant_pat").entered();
55
56 let fields = variant.fields(ctx.db());
57 let (visible_fields, fields_omitted) = visible_fields(ctx.completion, &fields, variant)?;
58 let enum_ty = variant.parent_enum(ctx.db()).ty(ctx.db());
59
60 let (name, escaped_name) = match path {
61 Some(path) => (
62 path.display_verbatim(ctx.db()).to_smolstr(),
63 path.display(ctx.db(), ctx.completion.edition).to_smolstr(),
64 ),
65 None => {
66 let name = local_name.unwrap_or_else(|| variant.name(ctx.db()));
67
68 (
69 name.as_str().to_smolstr(),
70 name.display(ctx.db(), ctx.completion.edition).to_smolstr(),
71 )
72 }
73 };
74
75 let (label, lookup, pat) = match path_ctx {
76 Some(PathCompletionCtx { has_call_parens: true, .. }) => {
77 (name.clone(), name, escaped_name.to_string())
78 }
79 _ => {
80 let kind = variant.kind(ctx.db());
81 let label = format_literal_label(name.as_str(), kind, ctx.snippet_cap());
82 let lookup = format_literal_lookup(name.as_str(), kind);
83 let pat = render_pat(
84 &ctx,
85 pattern_ctx,
86 &escaped_name,
87 kind,
88 &visible_fields,
89 fields_omitted,
90 )?;
91 (label, lookup, pat)
92 }
93 };
94
95 Some(build_completion(
96 ctx,
97 label,
98 lookup,
99 pat,
100 variant,
101 enum_ty,
102 pattern_ctx.missing_variants.contains(&variant),
103 ))
104}
105
106fn build_completion(
107 ctx: RenderContext<'_, '_>,
108 label: SmolStr,
109 lookup: SmolStr,
110 pat: String,
111 def: impl HasDocs,
112 adt_ty: hir::Type<'_>,
113 is_variant_missing: bool,
115) -> CompletionItem {
116 let mut relevance = ctx.completion_relevance();
117
118 if is_variant_missing {
119 relevance.type_match = super::compute_type_match(ctx.completion, &adt_ty);
120 }
121
122 let mut item = CompletionItem::new(
123 CompletionItemKind::Binding,
124 ctx.source_range(),
125 label,
126 ctx.completion.edition,
127 );
128 item.set_documentation(ctx.docs(def))
129 .set_deprecated(
130 ctx.is_deprecated(def, None ),
131 )
132 .detail(&pat)
133 .lookup_by(lookup)
134 .set_relevance(relevance);
135 match ctx.snippet_cap() {
136 Some(snippet_cap) => item.insert_snippet(snippet_cap, pat),
137 None => item.insert_text(pat),
138 };
139 item.build(ctx.db())
140}
141
142fn render_pat(
143 ctx: &RenderContext<'_, '_>,
144 pattern_ctx: &PatternContext,
145 name: &str,
146 kind: StructKind,
147 fields: &[hir::Field],
148 fields_omitted: bool,
149) -> Option<String> {
150 let mut pat = match kind {
151 StructKind::Tuple => render_tuple_as_pat(ctx.snippet_cap(), fields, name, fields_omitted),
152 StructKind::Record => render_record_as_pat(
153 ctx.db(),
154 ctx.snippet_cap(),
155 fields,
156 name,
157 fields_omitted,
158 ctx.completion.edition,
159 ),
160 StructKind::Unit => name.to_owned(),
161 };
162
163 let needs_ascription = matches!(
164 pattern_ctx,
165 PatternContext {
166 param_ctx: Some(ParamContext { kind: ParamKind::Function(_), .. }),
167 has_type_ascription: false,
168 parent_pat: None,
169 ..
170 }
171 );
172 if needs_ascription {
173 pat.push(':');
174 pat.push(' ');
175 pat.push_str(name);
176 }
177 if ctx.snippet_cap().is_some() {
178 pat.push_str("$0");
179 }
180 Some(pat)
181}
182
183fn render_record_as_pat(
184 db: &dyn HirDatabase,
185 snippet_cap: Option<SnippetCap>,
186 fields: &[hir::Field],
187 name: &str,
188 fields_omitted: bool,
189 edition: Edition,
190) -> String {
191 let fields = fields.iter();
192 match snippet_cap {
193 Some(_) => {
194 format!(
195 "{name} {{ {}{} }}",
196 fields.enumerate().format_with(", ", |(idx, field), f| {
197 f(&format_args!("{}${}", field.name(db).display(db, edition), idx + 1))
198 }),
199 if fields_omitted { ", .." } else { "" },
200 name = name
201 )
202 }
203 None => {
204 format!(
205 "{name} {{ {}{} }}",
206 fields.map(|field| field.name(db).display_no_db(edition).to_smolstr()).format(", "),
207 if fields_omitted { ", .." } else { "" },
208 name = name
209 )
210 }
211 }
212}
213
214fn render_tuple_as_pat(
215 snippet_cap: Option<SnippetCap>,
216 fields: &[hir::Field],
217 name: &str,
218 fields_omitted: bool,
219) -> String {
220 let fields = fields.iter();
221 match snippet_cap {
222 Some(_) => {
223 format!(
224 "{name}({}{})",
225 fields
226 .enumerate()
227 .format_with(", ", |(idx, _), f| { f(&format_args!("${}", idx + 1)) }),
228 if fields_omitted { ", .." } else { "" },
229 name = name
230 )
231 }
232 None => {
233 format!(
234 "{name}({}{})",
235 fields.enumerate().map(|(idx, _)| idx).format(", "),
236 if fields_omitted { ", .." } else { "" },
237 name = name
238 )
239 }
240 }
241}