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::Variant,
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(ctx.is_deprecated(def))
130        .detail(&pat)
131        .lookup_by(lookup)
132        .set_relevance(relevance);
133    match ctx.snippet_cap() {
134        Some(snippet_cap) => item.insert_snippet(snippet_cap, pat),
135        None => item.insert_text(pat),
136    };
137    item.build(ctx.db())
138}
139
140fn render_pat(
141    ctx: &RenderContext<'_>,
142    pattern_ctx: &PatternContext,
143    name: &str,
144    kind: StructKind,
145    fields: &[hir::Field],
146    fields_omitted: bool,
147) -> Option<String> {
148    let mut pat = match kind {
149        StructKind::Tuple => render_tuple_as_pat(ctx.snippet_cap(), fields, name, fields_omitted),
150        StructKind::Record => render_record_as_pat(
151            ctx.db(),
152            ctx.snippet_cap(),
153            fields,
154            name,
155            fields_omitted,
156            ctx.completion.edition,
157        ),
158        StructKind::Unit => name.to_owned(),
159    };
160
161    let needs_ascription = matches!(
162        pattern_ctx,
163        PatternContext {
164            param_ctx: Some(ParamContext { kind: ParamKind::Function(_), .. }),
165            has_type_ascription: false,
166            parent_pat: None,
167            ..
168        }
169    );
170    if needs_ascription {
171        pat.push(':');
172        pat.push(' ');
173        pat.push_str(name);
174    }
175    if ctx.snippet_cap().is_some() {
176        pat.push_str("$0");
177    }
178    Some(pat)
179}
180
181fn render_record_as_pat(
182    db: &dyn HirDatabase,
183    snippet_cap: Option<SnippetCap>,
184    fields: &[hir::Field],
185    name: &str,
186    fields_omitted: bool,
187    edition: Edition,
188) -> String {
189    let fields = fields.iter();
190    match snippet_cap {
191        Some(_) => {
192            format!(
193                "{name} {{ {}{} }}",
194                fields.enumerate().format_with(", ", |(idx, field), f| {
195                    f(&format_args!("{}${}", field.name(db).display(db, edition), idx + 1))
196                }),
197                if fields_omitted { ", .." } else { "" },
198                name = name
199            )
200        }
201        None => {
202            format!(
203                "{name} {{ {}{} }}",
204                fields.map(|field| field.name(db).display_no_db(edition).to_smolstr()).format(", "),
205                if fields_omitted { ", .." } else { "" },
206                name = name
207            )
208        }
209    }
210}
211
212fn render_tuple_as_pat(
213    snippet_cap: Option<SnippetCap>,
214    fields: &[hir::Field],
215    name: &str,
216    fields_omitted: bool,
217) -> String {
218    let fields = fields.iter();
219    match snippet_cap {
220        Some(_) => {
221            format!(
222                "{name}({}{})",
223                fields
224                    .enumerate()
225                    .format_with(", ", |(idx, _), f| { f(&format_args!("${}", idx + 1)) }),
226                if fields_omitted { ", .." } else { "" },
227                name = name
228            )
229        }
230        None => {
231            format!(
232                "{name}({}{})",
233                fields.enumerate().map(|(idx, _)| idx).format(", "),
234                if fields_omitted { ", .." } else { "" },
235                name = name
236            )
237        }
238    }
239}