ide_completion/render/
literal.rs

1//! Renderer for `enum` variants.
2
3use 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    // only show name in label if not adding parens
100    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 is a misnomer here, this is more about constructor information
134        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}