From: Morgan Thomas Date: Sat, 12 Mar 2022 01:17:01 +0000 (-0800) Subject: - Break out functionality related to rendering struct completions into `crates/ide_co... X-Git-Url: https://git.lizzy.rs/?a=commitdiff_plain;h=1c5b2c7d03d684227279877a813fdf16790e4552;p=rust.git - Break out functionality related to rendering struct completions into `crates/ide_completion/src/render/compound.rs` - Add support for placeholder completions in tuple structs - Denote tuple struct completions with `(…)` instead of ` {…}` - Show struct completions as their type (`Struct { field: Type }`) in the completion menu instead of raw snippet text (`Struct { field: ${1:()} }$0`) --- diff --git a/crates/ide_completion/src/render.rs b/crates/ide_completion/src/render.rs index e7a5426a26b..8003d200f96 100644 --- a/crates/ide_completion/src/render.rs +++ b/crates/ide_completion/src/render.rs @@ -8,6 +8,7 @@ pub(crate) mod pattern; pub(crate) mod type_alias; pub(crate) mod struct_literal; +pub(crate) mod compound; mod builder_ext; diff --git a/crates/ide_completion/src/render/compound.rs b/crates/ide_completion/src/render/compound.rs new file mode 100644 index 00000000000..586bb92a8ec --- /dev/null +++ b/crates/ide_completion/src/render/compound.rs @@ -0,0 +1,93 @@ +//! Code common to structs, unions, and enum variants. + +use crate::render::RenderContext; +use hir::{db::HirDatabase, HasAttrs, HasVisibility, HirDisplay}; +use ide_db::SnippetCap; +use itertools::Itertools; + +/// 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 RenderedCompound { + pub literal: String, + pub 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( + db: &dyn HirDatabase, + snippet_cap: Option, + fields: &[hir::Field], + name: Option<&str>, +) -> RenderedCompound { + let fields = fields.iter(); + + let (completions, types): (Vec<_>, Vec<_>) = fields + .enumerate() + .map(|(idx, field)| { + ( + if snippet_cap.is_some() { + format!("{}: ${{{}:()}}", field.name(db), idx + 1) + } else { + format!("{}: ()", field.name(db)) + }, + format!("{}: {}", field.name(db), field.ty(db).display(db)), + ) + }) + .unzip(); + RenderedCompound { + literal: format!("{} {{ {} }}", name.unwrap_or(""), completions.iter().format(", ")), + detail: format!("{} {{ {} }}", name.unwrap_or(""), types.iter().format(", ")), + } +} + +/// 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( + db: &dyn HirDatabase, + snippet_cap: Option, + fields: &[hir::Field], + name: Option<&str>, +) -> RenderedCompound { + let fields = fields.iter(); + + let (completions, types): (Vec<_>, Vec<_>) = fields + .enumerate() + .map(|(idx, field)| { + ( + if snippet_cap.is_some() { + format!("${{{}:()}}", (idx + 1).to_string()) + } else { + "()".to_string() + }, + field.ty(db).display(db).to_string(), + ) + }) + .unzip(); + RenderedCompound { + literal: format!("{}({})", name.unwrap_or(""), completions.iter().format(", ")), + detail: format!("{}({})", name.unwrap_or(""), types.iter().format(", ")), + } +} + +/// Find all the visible fields in a `HasAttrs`. Returns the list of visible +/// fields, plus a boolean for whether the list is comprehensive (contains no +/// private fields and is not marked `#[non_exhaustive]`). +pub(crate) fn visible_fields( + ctx: &RenderContext<'_>, + fields: &[hir::Field], + item: impl HasAttrs, +) -> Option<(Vec, bool)> { + let module = ctx.completion.module?; + let n_fields = fields.len(); + let fields = fields + .iter() + .filter(|field| field.is_visible_from(ctx.db(), module)) + .copied() + .collect::>(); + + let fields_omitted = + n_fields - fields.len() > 0 || item.attrs(ctx.db()).by_key("non_exhaustive").exists(); + Some((fields, fields_omitted)) +} diff --git a/crates/ide_completion/src/render/struct_literal.rs b/crates/ide_completion/src/render/struct_literal.rs index 3bc94fa782a..124b465773a 100644 --- a/crates/ide_completion/src/render/struct_literal.rs +++ b/crates/ide_completion/src/render/struct_literal.rs @@ -1,11 +1,13 @@ //! Renderer for `struct` literal. -use hir::{db::HirDatabase, HasAttrs, HasVisibility, Name, StructKind}; -use ide_db::SnippetCap; -use itertools::Itertools; +use hir::{HasAttrs, Name, StructKind}; use syntax::SmolStr; -use crate::{render::RenderContext, CompletionItem, CompletionItemKind}; +use crate::{ + render::compound::{render_record, render_tuple, visible_fields, RenderedCompound}, + render::RenderContext, + CompletionItem, CompletionItemKind, +}; pub(crate) fn render_struct_literal( ctx: RenderContext<'_>, @@ -25,29 +27,34 @@ pub(crate) fn render_struct_literal( let name = local_name.unwrap_or_else(|| strukt.name(ctx.db())).to_smol_str(); - let literal = render_literal(&ctx, path, &name, strukt.kind(ctx.db()), &visible_fields)?; + let rendered = render_literal(&ctx, path, &name, strukt.kind(ctx.db()), &visible_fields)?; - Some(build_completion(ctx, name, literal, strukt)) + Some(build_completion(&ctx, name, rendered, strukt.kind(ctx.db()), strukt)) } fn build_completion( - ctx: RenderContext<'_>, + ctx: &RenderContext<'_>, name: SmolStr, - literal: String, + rendered: RenderedCompound, + kind: StructKind, def: impl HasAttrs + Copy, ) -> CompletionItem { let mut item = CompletionItem::new( CompletionItemKind::Snippet, ctx.source_range(), - SmolStr::from_iter([&name, " {…}"]), + match kind { + StructKind::Tuple => SmolStr::from_iter([&name, "(…)"]), + _ => SmolStr::from_iter([&name, " {…}"]), + }, ); + item.set_documentation(ctx.docs(def)) .set_deprecated(ctx.is_deprecated(def)) - .detail(&literal) + .detail(&rendered.detail) .set_relevance(ctx.completion_relevance()); match ctx.snippet_cap() { - Some(snippet_cap) => item.insert_snippet(snippet_cap, literal), - None => item.insert_text(literal), + Some(snippet_cap) => item.insert_snippet(snippet_cap, rendered.literal), + None => item.insert_text(rendered.literal), }; item.build() } @@ -58,7 +65,7 @@ fn render_literal( name: &str, kind: StructKind, fields: &[hir::Field], -) -> Option { +) -> Option { let path_string; let qualified_name = if let Some(path) = path { @@ -68,69 +75,18 @@ fn render_literal( name }; - let mut literal = match kind { + let mut rendered = match kind { StructKind::Tuple if ctx.snippet_cap().is_some() => { - render_tuple_as_literal(fields, qualified_name) + render_tuple(ctx.db(), ctx.snippet_cap(), fields, Some(qualified_name)) } StructKind::Record => { - render_record_as_literal(ctx.db(), ctx.snippet_cap(), fields, qualified_name) + render_record(ctx.db(), ctx.snippet_cap(), fields, Some(qualified_name)) } _ => return None, }; if ctx.snippet_cap().is_some() { - literal.push_str("$0"); + rendered.literal.push_str("$0"); } - Some(literal) -} - -fn render_record_as_literal( - db: &dyn HirDatabase, - snippet_cap: Option, - fields: &[hir::Field], - name: &str, -) -> String { - let fields = fields.iter(); - if snippet_cap.is_some() { - format!( - "{name} {{ {} }}", - fields - .enumerate() - .map(|(idx, field)| format!("{}: ${{{}:()}}", field.name(db), idx + 1)) - .format(", "), - name = name - ) - } else { - format!( - "{name} {{ {} }}", - fields.map(|field| format!("{}: ()", field.name(db))).format(", "), - name = name - ) - } -} - -fn render_tuple_as_literal(fields: &[hir::Field], name: &str) -> String { - format!( - "{name}({})", - fields.iter().enumerate().map(|(idx, _)| format!("${}", idx + 1)).format(", "), - name = name - ) -} - -fn visible_fields( - ctx: &RenderContext<'_>, - fields: &[hir::Field], - item: impl HasAttrs, -) -> Option<(Vec, bool)> { - let module = ctx.completion.module?; - let n_fields = fields.len(); - let fields = fields - .iter() - .filter(|field| field.is_visible_from(ctx.db(), module)) - .copied() - .collect::>(); - - let fields_omitted = - n_fields - fields.len() > 0 || item.attrs(ctx.db()).by_key("non_exhaustive").exists(); - Some((fields, fields_omitted)) + Some(rendered) } diff --git a/crates/ide_completion/src/tests/record.rs b/crates/ide_completion/src/tests/record.rs index 3bb332b4372..87d0d853b6f 100644 --- a/crates/ide_completion/src/tests/record.rs +++ b/crates/ide_completion/src/tests/record.rs @@ -166,7 +166,7 @@ fn main() { kw true kw false kw return - sn Foo {…} Foo { foo1: ${1:()}, foo2: ${2:()} }$0 + sn Foo {…} Foo { foo1: u32, foo2: u32 } fd ..Default::default() fd foo1 u32 fd foo2 u32