]> git.lizzy.rs Git - rust.git/blob - crates/ide_completion/src/render/struct_literal.rs
Merge #11393
[rust.git] / crates / ide_completion / src / render / struct_literal.rs
1 //! Renderer for `struct` literal.
2
3 use hir::{db::HirDatabase, HasAttrs, HasVisibility, Name, StructKind};
4 use ide_db::helpers::SnippetCap;
5 use itertools::Itertools;
6 use syntax::SmolStr;
7
8 use crate::{render::RenderContext, CompletionItem, CompletionItemKind};
9
10 pub(crate) fn render_struct_literal(
11     ctx: RenderContext<'_>,
12     strukt: hir::Struct,
13     path: Option<hir::ModPath>,
14     local_name: Option<Name>,
15 ) -> Option<CompletionItem> {
16     let _p = profile::span("render_struct_literal");
17
18     let fields = strukt.fields(ctx.db());
19     let (visible_fields, fields_omitted) = visible_fields(&ctx, &fields, strukt)?;
20
21     if fields_omitted {
22         // If some fields are private you can't make `struct` literal.
23         return None;
24     }
25
26     let name = local_name.unwrap_or_else(|| strukt.name(ctx.db())).to_smol_str();
27
28     let literal = render_literal(&ctx, path, &name, strukt.kind(ctx.db()), &visible_fields)?;
29
30     Some(build_completion(ctx, name, literal, strukt))
31 }
32
33 fn build_completion(
34     ctx: RenderContext<'_>,
35     name: SmolStr,
36     literal: String,
37     def: impl HasAttrs + Copy,
38 ) -> CompletionItem {
39     let mut item = CompletionItem::new(
40         CompletionItemKind::Snippet,
41         ctx.source_range(),
42         SmolStr::from_iter([&name, " {…}"]),
43     );
44     item.set_documentation(ctx.docs(def)).set_deprecated(ctx.is_deprecated(def)).detail(&literal);
45     match ctx.snippet_cap() {
46         Some(snippet_cap) => item.insert_snippet(snippet_cap, literal),
47         None => item.insert_text(literal),
48     };
49     item.build()
50 }
51
52 fn render_literal(
53     ctx: &RenderContext<'_>,
54     path: Option<hir::ModPath>,
55     name: &str,
56     kind: StructKind,
57     fields: &[hir::Field],
58 ) -> Option<String> {
59     let path_string;
60
61     let qualified_name = if let Some(path) = path {
62         path_string = path.to_string();
63         &path_string
64     } else {
65         name
66     };
67
68     let mut literal = match kind {
69         StructKind::Tuple if ctx.snippet_cap().is_some() => {
70             render_tuple_as_literal(fields, qualified_name)
71         }
72         StructKind::Record => {
73             render_record_as_literal(ctx.db(), ctx.snippet_cap(), fields, qualified_name)
74         }
75         _ => return None,
76     };
77
78     if ctx.snippet_cap().is_some() {
79         literal.push_str("$0");
80     }
81     Some(literal)
82 }
83
84 fn render_record_as_literal(
85     db: &dyn HirDatabase,
86     snippet_cap: Option<SnippetCap>,
87     fields: &[hir::Field],
88     name: &str,
89 ) -> String {
90     let fields = fields.iter();
91     if snippet_cap.is_some() {
92         format!(
93             "{name} {{ {} }}",
94             fields
95                 .enumerate()
96                 .map(|(idx, field)| format!("{}: ${{{}:()}}", field.name(db), idx + 1))
97                 .format(", "),
98             name = name
99         )
100     } else {
101         format!(
102             "{name} {{ {} }}",
103             fields.map(|field| format!("{}: ()", field.name(db))).format(", "),
104             name = name
105         )
106     }
107 }
108
109 fn render_tuple_as_literal(fields: &[hir::Field], name: &str) -> String {
110     format!(
111         "{name}({})",
112         fields.iter().enumerate().map(|(idx, _)| format!("${}", idx + 1)).format(", "),
113         name = name
114     )
115 }
116
117 fn visible_fields(
118     ctx: &RenderContext<'_>,
119     fields: &[hir::Field],
120     item: impl HasAttrs,
121 ) -> Option<(Vec<hir::Field>, bool)> {
122     let module = ctx.completion.module?;
123     let n_fields = fields.len();
124     let fields = fields
125         .iter()
126         .filter(|field| field.is_visible_from(ctx.db(), module))
127         .copied()
128         .collect::<Vec<_>>();
129
130     let fields_omitted =
131         n_fields - fields.len() > 0 || item.attrs(ctx.db()).by_key("non_exhaustive").exists();
132     Some((fields, fields_omitted))
133 }