//! Code common to structs, unions, and enum variants. use crate::context::CompletionContext; use hir::{db::HirDatabase, HasAttrs, HasCrate, HasVisibility, HirDisplay, StructKind}; use ide_db::SnippetCap; use itertools::Itertools; use syntax::SmolStr; /// A rendered struct, union, or enum variant, split into fields for actual /// auto-completion (`literal`, using `field: ()`) and display in the /// completions menu (`detail`, using `field: type`). pub(crate) struct RenderedLiteral { pub(crate) literal: String, pub(crate) detail: String, } /// Render a record type (or sub-type) to a `RenderedCompound`. Use `None` for /// the `name` argument for an anonymous type. pub(crate) fn render_record_lit( db: &dyn HirDatabase, snippet_cap: Option, fields: &[hir::Field], path: &str, ) -> RenderedLiteral { if snippet_cap.is_none() { return RenderedLiteral { literal: path.to_string(), detail: path.to_string() }; } let completions = fields.iter().enumerate().format_with(", ", |(idx, field), f| { if snippet_cap.is_some() { f(&format_args!("{}: ${{{}:()}}", field.name(db), idx + 1)) } else { f(&format_args!("{}: ()", field.name(db))) } }); let types = fields.iter().format_with(", ", |field, f| { f(&format_args!("{}: {}", field.name(db), field.ty(db).display(db))) }); RenderedLiteral { literal: format!("{path} {{ {completions} }}"), detail: format!("{path} {{ {types} }}"), } } /// Render a tuple type (or sub-type) to a `RenderedCompound`. Use `None` for /// the `name` argument for an anonymous type. pub(crate) fn render_tuple_lit( db: &dyn HirDatabase, snippet_cap: Option, fields: &[hir::Field], path: &str, ) -> RenderedLiteral { if snippet_cap.is_none() { return RenderedLiteral { literal: path.to_string(), detail: path.to_string() }; } let completions = fields.iter().enumerate().format_with(", ", |(idx, _), f| { if snippet_cap.is_some() { f(&format_args!("${{{}:()}}", idx + 1)) } else { f(&format_args!("()")) } }); let types = fields.iter().format_with(", ", |field, f| f(&field.ty(db).display(db))); RenderedLiteral { literal: format!("{path}({completions})"), detail: format!("{path}({types})"), } } /// Find all the visible fields in a given list. Returns the list of visible /// fields, plus a boolean for whether the list is comprehensive (contains no /// private fields and its item is not marked `#[non_exhaustive]`). pub(crate) fn visible_fields( ctx: &CompletionContext<'_>, fields: &[hir::Field], item: impl HasAttrs + HasCrate + Copy, ) -> Option<(Vec, bool)> { let module = ctx.module; let n_fields = fields.len(); let fields = fields .iter() .filter(|field| field.is_visible_from(ctx.db, module)) .copied() .collect::>(); let has_invisible_field = n_fields - fields.len() > 0; let is_foreign_non_exhaustive = item.attrs(ctx.db).by_key("non_exhaustive").exists() && item.krate(ctx.db) != module.krate(); let fields_omitted = has_invisible_field || is_foreign_non_exhaustive; Some((fields, fields_omitted)) } /// Format a struct, etc. literal option for display in the completions menu. pub(crate) fn format_literal_label( name: &str, kind: StructKind, snippet_cap: Option, ) -> SmolStr { if snippet_cap.is_none() { return name.into(); } match kind { StructKind::Tuple => SmolStr::from_iter([name, "(…)"]), StructKind::Record => SmolStr::from_iter([name, " {…}"]), StructKind::Unit => name.into(), } } /// Format a struct, etc. literal option for lookup used in completions filtering. pub(crate) fn format_literal_lookup(name: &str, kind: StructKind) -> SmolStr { match kind { StructKind::Tuple => SmolStr::from_iter([name, "()"]), StructKind::Record => SmolStr::from_iter([name, "{}"]), StructKind::Unit => name.into(), } }