Skip to main content

ide_completion/render/
pattern.rs

1//! Renderer for patterns.
2
3use 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        // 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) =
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    // Missing in context of match statement completions
114    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 /* the two current `def` arguments to this function, `Struct` and `EnumVariant`, both can't be assoc items */),
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}