• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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