]> git.proxmox.com Git - rustc.git/blame - src/tools/rust-analyzer/crates/ide-completion/src/render/pattern.rs
New upstream version 1.64.0+dfsg1
[rustc.git] / src / tools / rust-analyzer / crates / ide-completion / src / render / pattern.rs
CommitLineData
064997fb
FG
1//! Renderer for patterns.
2
3use hir::{db::HirDatabase, HasAttrs, Name, StructKind};
4use ide_db::SnippetCap;
5use itertools::Itertools;
6use syntax::SmolStr;
7
8use crate::{
9 context::{ParamContext, ParamKind, PathCompletionCtx, PatternContext},
10 render::{
11 variant::{format_literal_label, visible_fields},
12 RenderContext,
13 },
14 CompletionItem, CompletionItemKind,
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 = 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.to_smol_str(), name.escaped().to_smol_str());
35 let kind = strukt.kind(ctx.db());
36 let label = format_literal_label(name.as_str(), kind);
37 let pat = render_pat(&ctx, pattern_ctx, &escaped_name, kind, &visible_fields, fields_omitted)?;
38
39 Some(build_completion(ctx, label, pat, strukt))
40}
41
42pub(crate) fn render_variant_pat(
43 ctx: RenderContext<'_>,
44 pattern_ctx: &PatternContext,
45 path_ctx: Option<&PathCompletionCtx>,
46 variant: hir::Variant,
47 local_name: Option<Name>,
48 path: Option<&hir::ModPath>,
49) -> Option<CompletionItem> {
50 let _p = profile::span("render_variant_pat");
51
52 let fields = variant.fields(ctx.db());
53 let (visible_fields, fields_omitted) = visible_fields(ctx.completion, &fields, variant)?;
54
55 let (name, escaped_name) = match path {
56 Some(path) => (path.to_string().into(), path.escaped().to_string().into()),
57 None => {
58 let name = local_name.unwrap_or_else(|| variant.name(ctx.db()));
59 (name.to_smol_str(), name.escaped().to_smol_str())
60 }
61 };
62
63 let (label, pat) = match path_ctx {
64 Some(PathCompletionCtx { has_call_parens: true, .. }) => (name, escaped_name.to_string()),
65 _ => {
66 let kind = variant.kind(ctx.db());
67 let label = format_literal_label(name.as_str(), kind);
68 let pat = render_pat(
69 &ctx,
70 pattern_ctx,
71 &escaped_name,
72 kind,
73 &visible_fields,
74 fields_omitted,
75 )?;
76 (label, pat)
77 }
78 };
79
80 Some(build_completion(ctx, label, pat, variant))
81}
82
83fn build_completion(
84 ctx: RenderContext<'_>,
85 label: SmolStr,
86 pat: String,
87 def: impl HasAttrs + Copy,
88) -> CompletionItem {
89 let mut item = CompletionItem::new(CompletionItemKind::Binding, ctx.source_range(), label);
90 item.set_documentation(ctx.docs(def))
91 .set_deprecated(ctx.is_deprecated(def))
92 .detail(&pat)
93 .set_relevance(ctx.completion_relevance());
94 match ctx.snippet_cap() {
95 Some(snippet_cap) => item.insert_snippet(snippet_cap, pat),
96 None => item.insert_text(pat),
97 };
98 item.build()
99}
100
101fn render_pat(
102 ctx: &RenderContext<'_>,
103 pattern_ctx: &PatternContext,
104 name: &str,
105 kind: StructKind,
106 fields: &[hir::Field],
107 fields_omitted: bool,
108) -> Option<String> {
109 let mut pat = match kind {
110 StructKind::Tuple => render_tuple_as_pat(ctx.snippet_cap(), fields, name, fields_omitted),
111 StructKind::Record => {
112 render_record_as_pat(ctx.db(), ctx.snippet_cap(), fields, name, fields_omitted)
113 }
114 StructKind::Unit => name.to_string(),
115 };
116
117 let needs_ascription = matches!(
118 pattern_ctx,
119 PatternContext {
120 param_ctx: Some(ParamContext { kind: ParamKind::Function(_), .. }),
121 has_type_ascription: false,
122 ..
123 }
124 );
125 if needs_ascription {
126 pat.push(':');
127 pat.push(' ');
128 pat.push_str(name);
129 }
130 if ctx.snippet_cap().is_some() {
131 pat.push_str("$0");
132 }
133 Some(pat)
134}
135
136fn render_record_as_pat(
137 db: &dyn HirDatabase,
138 snippet_cap: Option<SnippetCap>,
139 fields: &[hir::Field],
140 name: &str,
141 fields_omitted: bool,
142) -> String {
143 let fields = fields.iter();
144 match snippet_cap {
145 Some(_) => {
146 format!(
147 "{name} {{ {}{} }}",
148 fields.enumerate().format_with(", ", |(idx, field), f| {
149 f(&format_args!("{}${}", field.name(db).escaped(), idx + 1))
150 }),
151 if fields_omitted { ", .." } else { "" },
152 name = name
153 )
154 }
155 None => {
156 format!(
157 "{name} {{ {}{} }}",
158 fields.map(|field| field.name(db).escaped().to_smol_str()).format(", "),
159 if fields_omitted { ", .." } else { "" },
160 name = name
161 )
162 }
163 }
164}
165
166fn render_tuple_as_pat(
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
178 .enumerate()
179 .format_with(", ", |(idx, _), f| { f(&format_args!("${}", idx + 1)) }),
180 if fields_omitted { ", .." } else { "" },
181 name = name
182 )
183 }
184 None => {
185 format!(
186 "{name}({}{})",
187 fields.enumerate().map(|(idx, _)| idx).format(", "),
188 if fields_omitted { ", .." } else { "" },
189 name = name
190 )
191 }
192 }
193}