ide_completion/render/
variant.rs

1//! Code common to structs, unions, and enum variants.
2
3use crate::context::CompletionContext;
4use hir::{HasAttrs, HasCrate, HasVisibility, HirDisplay, StructKind};
5use ide_db::SnippetCap;
6use itertools::Itertools;
7use syntax::SmolStr;
8
9/// A rendered struct, union, or enum variant, split into fields for actual
10/// auto-completion (`literal`, using `field: ()`) and display in the
11/// completions menu (`detail`, using `field: type`).
12pub(crate) struct RenderedLiteral {
13    pub(crate) literal: String,
14    pub(crate) detail: String,
15}
16
17/// Render a record type (or sub-type) to a `RenderedCompound`. Use `None` for
18/// the `name` argument for an anonymous type.
19pub(crate) fn render_record_lit(
20    ctx: &CompletionContext<'_>,
21    snippet_cap: Option<SnippetCap>,
22    fields: &[hir::Field],
23    path: &str,
24) -> RenderedLiteral {
25    if snippet_cap.is_none() {
26        return RenderedLiteral { literal: path.to_owned(), detail: path.to_owned() };
27    }
28    let completions = fields.iter().enumerate().format_with(", ", |(idx, field), f| {
29        let mut fmt_field = |fill, tab| {
30            let field_name = field.name(ctx.db);
31
32            if let Some(local) = ctx.locals.get(&field_name)
33                && local
34                    .ty(ctx.db)
35                    .could_unify_with_deeply(ctx.db, &field.ty(ctx.db).to_type(ctx.db))
36            {
37                f(&format_args!("{}{tab}", field_name.display(ctx.db, ctx.edition)))
38            } else {
39                f(&format_args!("{}: {fill}", field_name.display(ctx.db, ctx.edition)))
40            }
41        };
42        if snippet_cap.is_some() {
43            fmt_field(format_args!("${{{}:()}}", idx + 1), format_args!("${}", idx + 1))
44        } else {
45            fmt_field(format_args!("()"), format_args!(""))
46        }
47    });
48
49    let types = fields.iter().format_with(", ", |field, f| {
50        f(&format_args!(
51            "{}: {}",
52            field.name(ctx.db).display(ctx.db, ctx.edition),
53            field.ty(ctx.db).display(ctx.db, ctx.display_target)
54        ))
55    });
56
57    RenderedLiteral {
58        literal: format!("{path} {{ {completions} }}"),
59        detail: format!("{path} {{ {types} }}"),
60    }
61}
62
63/// Render a tuple type (or sub-type) to a `RenderedCompound`. Use `None` for
64/// the `name` argument for an anonymous type.
65pub(crate) fn render_tuple_lit(
66    ctx: &CompletionContext<'_>,
67    snippet_cap: Option<SnippetCap>,
68    fields: &[hir::Field],
69    path: &str,
70) -> RenderedLiteral {
71    if snippet_cap.is_none() {
72        return RenderedLiteral { literal: path.to_owned(), detail: path.to_owned() };
73    }
74    let completions = fields.iter().enumerate().format_with(", ", |(idx, _), f| {
75        if snippet_cap.is_some() {
76            f(&format_args!("${{{}:()}}", idx + 1))
77        } else {
78            f(&format_args!("()"))
79        }
80    });
81
82    let types = fields
83        .iter()
84        .format_with(", ", |field, f| f(&field.ty(ctx.db).display(ctx.db, ctx.display_target)));
85
86    RenderedLiteral {
87        literal: format!("{path}({completions})"),
88        detail: format!("{path}({types})"),
89    }
90}
91
92/// Find all the visible fields in a given list. Returns the list of visible
93/// fields, plus a boolean for whether the list is comprehensive (contains no
94/// private fields and its item is not marked `#[non_exhaustive]`).
95pub(crate) fn visible_fields(
96    ctx: &CompletionContext<'_>,
97    fields: &[hir::Field],
98    item: impl HasAttrs + HasCrate + Copy,
99) -> Option<(Vec<hir::Field>, bool)> {
100    let module = ctx.module;
101    let n_fields = fields.len();
102    let fields = fields
103        .iter()
104        .filter(|field| field.is_visible_from(ctx.db, module))
105        .copied()
106        .collect::<Vec<_>>();
107    let has_invisible_field = n_fields - fields.len() > 0;
108    let is_foreign_non_exhaustive =
109        item.attrs(ctx.db).is_non_exhaustive() && item.krate(ctx.db) != module.krate(ctx.db);
110    let fields_omitted = has_invisible_field || is_foreign_non_exhaustive;
111    Some((fields, fields_omitted))
112}
113
114/// Format a struct, etc. literal option for display in the completions menu.
115pub(crate) fn format_literal_label(
116    name: &str,
117    kind: StructKind,
118    snippet_cap: Option<SnippetCap>,
119) -> SmolStr {
120    if snippet_cap.is_none() {
121        return name.into();
122    }
123    match kind {
124        StructKind::Tuple => SmolStr::from_iter([name, "(…)"]),
125        StructKind::Record => SmolStr::from_iter([name, " {…}"]),
126        StructKind::Unit => name.into(),
127    }
128}
129
130/// Format a struct, etc. literal option for lookup used in completions filtering.
131pub(crate) fn format_literal_lookup(name: &str, kind: StructKind) -> SmolStr {
132    match kind {
133        StructKind::Tuple => SmolStr::from_iter([name, "()"]),
134        StructKind::Record => SmolStr::from_iter([name, "{}"]),
135        StructKind::Unit => name.into(),
136    }
137}