1use hir::{StructKind, db::HirDatabase};
4use ide_db::{
5 SymbolKind,
6 documentation::{Documentation, HasDocs},
7};
8
9use crate::{
10 CompletionItemKind, CompletionRelevance, CompletionRelevanceReturnType,
11 context::{CompletionContext, PathCompletionCtx, PathKind},
12 item::{Builder, CompletionItem, CompletionRelevanceFn},
13 render::{
14 RenderContext, compute_type_match,
15 variant::{
16 RenderedLiteral, format_literal_label, format_literal_lookup, render_record_lit,
17 render_tuple_lit, visible_fields,
18 },
19 },
20};
21
22pub(crate) fn render_variant_lit(
23 ctx: RenderContext<'_>,
24 path_ctx: &PathCompletionCtx<'_>,
25 local_name: Option<hir::Name>,
26 variant: hir::Variant,
27 path: Option<hir::ModPath>,
28) -> Option<Builder> {
29 let _p = tracing::info_span!("render_variant_lit").entered();
30 let db = ctx.db();
31
32 let name = local_name.unwrap_or_else(|| variant.name(db));
33 render(ctx, path_ctx, Variant::EnumVariant(variant), name, path)
34}
35
36pub(crate) fn render_struct_literal(
37 ctx: RenderContext<'_>,
38 path_ctx: &PathCompletionCtx<'_>,
39 strukt: hir::Struct,
40 path: Option<hir::ModPath>,
41 local_name: Option<hir::Name>,
42) -> Option<Builder> {
43 let _p = tracing::info_span!("render_struct_literal").entered();
44 let db = ctx.db();
45
46 let name = local_name.unwrap_or_else(|| strukt.name(db));
47 render(ctx, path_ctx, Variant::Struct(strukt), name, path)
48}
49
50fn render(
51 ctx @ RenderContext { completion, .. }: RenderContext<'_>,
52 path_ctx: &PathCompletionCtx<'_>,
53 thing: Variant,
54 name: hir::Name,
55 path: Option<hir::ModPath>,
56) -> Option<Builder> {
57 let db = completion.db;
58 let mut kind = thing.kind(db);
59 let should_add_parens = !matches!(
60 path_ctx,
61 PathCompletionCtx { has_call_parens: true, .. }
62 | PathCompletionCtx { kind: PathKind::Use | PathKind::Type { .. }, .. }
63 );
64
65 let fields = thing.fields(completion)?;
66 let (qualified_name, short_qualified_name, qualified) = match path {
67 Some(path) => {
68 let short = hir::ModPath::from_segments(
69 hir::PathKind::Plain,
70 path.segments().iter().skip(path.segments().len().saturating_sub(2)).cloned(),
71 );
72 (path, short, true)
73 }
74 None => (name.clone().into(), name.into(), false),
75 };
76 let (qualified_name, escaped_qualified_name) = (
77 qualified_name.display_verbatim(ctx.db()).to_string(),
78 qualified_name.display(ctx.db(), completion.edition).to_string(),
79 );
80 let snippet_cap = ctx.snippet_cap();
81
82 let mut rendered = match kind {
83 StructKind::Tuple if should_add_parens => {
84 render_tuple_lit(completion, snippet_cap, &fields, &escaped_qualified_name)
85 }
86 StructKind::Record if should_add_parens => {
87 render_record_lit(completion, snippet_cap, &fields, &escaped_qualified_name)
88 }
89 _ => RenderedLiteral {
90 literal: escaped_qualified_name.clone(),
91 detail: escaped_qualified_name,
92 },
93 };
94
95 if snippet_cap.is_some() {
96 rendered.literal.push_str("$0");
97 }
98
99 if !should_add_parens {
101 kind = StructKind::Unit;
102 }
103 let label = format_literal_label(&qualified_name, kind, snippet_cap);
104 let lookup = if qualified {
105 format_literal_lookup(
106 &short_qualified_name.display(ctx.db(), completion.edition).to_string(),
107 kind,
108 )
109 } else {
110 format_literal_lookup(&qualified_name, kind)
111 };
112
113 let mut item = CompletionItem::new(
114 CompletionItemKind::SymbolKind(thing.symbol_kind()),
115 ctx.source_range(),
116 label,
117 completion.edition,
118 );
119
120 item.lookup_by(lookup);
121 item.detail(rendered.detail);
122
123 match snippet_cap {
124 Some(snippet_cap) => item.insert_snippet(snippet_cap, rendered.literal).trigger_call_info(),
125 None => item.insert_text(rendered.literal),
126 };
127
128 item.set_documentation(thing.docs(db)).set_deprecated(thing.is_deprecated(&ctx));
129
130 let ty = thing.ty(db);
131 item.set_relevance(CompletionRelevance {
132 type_match: compute_type_match(ctx.completion, &ty),
133 function: Some(CompletionRelevanceFn {
135 has_params: !fields.is_empty(),
136 has_self_param: false,
137 return_type: CompletionRelevanceReturnType::DirectConstructor,
138 }),
139 ..ctx.completion_relevance()
140 });
141
142 super::path_ref_match(completion, path_ctx, &ty, &mut item);
143
144 if let Some(import_to_add) = ctx.import_to_add {
145 item.add_import(import_to_add);
146 }
147 Some(item)
148}
149
150#[derive(Clone, Copy)]
151enum Variant {
152 Struct(hir::Struct),
153 EnumVariant(hir::Variant),
154}
155
156impl Variant {
157 fn fields(self, ctx: &CompletionContext<'_>) -> Option<Vec<hir::Field>> {
158 let fields = match self {
159 Variant::Struct(it) => it.fields(ctx.db),
160 Variant::EnumVariant(it) => it.fields(ctx.db),
161 };
162 let (visible_fields, fields_omitted) = match self {
163 Variant::Struct(it) => visible_fields(ctx, &fields, it)?,
164 Variant::EnumVariant(it) => visible_fields(ctx, &fields, it)?,
165 };
166 if !fields_omitted { Some(visible_fields) } else { None }
167 }
168
169 fn kind(self, db: &dyn HirDatabase) -> StructKind {
170 match self {
171 Variant::Struct(it) => it.kind(db),
172 Variant::EnumVariant(it) => it.kind(db),
173 }
174 }
175
176 fn symbol_kind(self) -> SymbolKind {
177 match self {
178 Variant::Struct(_) => SymbolKind::Struct,
179 Variant::EnumVariant(_) => SymbolKind::Variant,
180 }
181 }
182
183 fn docs(self, db: &dyn HirDatabase) -> Option<Documentation<'_>> {
184 match self {
185 Variant::Struct(it) => it.docs(db),
186 Variant::EnumVariant(it) => it.docs(db),
187 }
188 }
189
190 fn is_deprecated(self, ctx: &RenderContext<'_>) -> bool {
191 match self {
192 Variant::Struct(it) => ctx.is_deprecated(it),
193 Variant::EnumVariant(it) => ctx.is_deprecated(it),
194 }
195 }
196
197 fn ty(self, db: &dyn HirDatabase) -> hir::Type<'_> {
198 match self {
199 Variant::Struct(it) => it.ty(db),
200 Variant::EnumVariant(it) => it.parent_enum(db).ty(db),
201 }
202 }
203}