From 1c5b2c7d03d684227279877a813fdf16790e4552 Mon Sep 17 00:00:00 2001 From: Morgan Thomas Date: Fri, 11 Mar 2022 17:17:01 -0800 Subject: [PATCH 1/6] =?UTF-8?q?-=20Break=20out=20functionality=20related?= =?UTF-8?q?=20to=20rendering=20struct=20completions=20into=20`crates/ide?= =?UTF-8?q?=5Fcompletion/src/render/compound.rs`=20-=20Add=20support=20for?= =?UTF-8?q?=20placeholder=20completions=20in=20tuple=20structs=20-=20Denot?= =?UTF-8?q?e=20tuple=20struct=20completions=20with=20`(=E2=80=A6)`=20inste?= =?UTF-8?q?ad=20of=20`=20{=E2=80=A6}`=20-=20Show=20struct=20completions=20?= =?UTF-8?q?as=20their=20type=20(`Struct=20{=20field:=20Type=20}`)=20in=20t?= =?UTF-8?q?he=20completion=20menu=20instead=20of=20raw=20snippet=20text=20?= =?UTF-8?q?(`Struct=20{=20field:=20${1:()}=20}$0`)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/ide_completion/src/render.rs | 1 + crates/ide_completion/src/render/compound.rs | 93 ++++++++++++++++++ .../src/render/struct_literal.rs | 94 +++++-------------- crates/ide_completion/src/tests/record.rs | 2 +- 4 files changed, 120 insertions(+), 70 deletions(-) create mode 100644 crates/ide_completion/src/render/compound.rs diff --git a/crates/ide_completion/src/render.rs b/crates/ide_completion/src/render.rs index e7a5426a26..8003d200f9 100644 --- a/crates/ide_completion/src/render.rs +++ b/crates/ide_completion/src/render.rs @@ -8,6 +8,7 @@ pub(crate) mod const_; 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 0000000000..586bb92a8e --- /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 3bc94fa782..124b465773 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 3bb332b437..87d0d853b6 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 From 2a22cf8efca9cdf70dc849f974f9ee858171853e Mon Sep 17 00:00:00 2001 From: Morgan Thomas Date: Fri, 11 Mar 2022 18:26:01 -0800 Subject: [PATCH 2/6] Complete enum variants identically to structures. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In particular: - unit variants now display in the menu as "Variant", complete to "Variant", and display a detail of "Variant" (was "()") - tuple variants now display in the menu as "Variant(…)", complete to "Variant(${1:()})$0" (was "Variant($0)"), and display a detail of "Variant(type)" (was "(type)") - record variants now display in the menu as "Variant {…}", complete to "Variant { x: ${1:()} }$0" (was "Variant"), and display a detail of "Variant { x: type }" (was "{x: type}") This behavior is identical to that of struct completions. In addition, tuple variants no longer set triggers_call_info, as to my understanding it's unnecessary now that we're emitting placeholders. Tests have been updated to match, and the render::enum_variant::tests::inserts_parens_for_tuple_enums test has been removed entirely as it's covered by other tests (render::enum_detail_includes_{record, tuple}_fields, render::enum_detail_just_name_for_unit, render::pattern::enum_qualified). --- .../src/completions/qualified_path.rs | 6 +- crates/ide_completion/src/render.rs | 35 ++++--- .../ide_completion/src/render/builder_ext.rs | 1 + .../ide_completion/src/render/enum_variant.rs | 95 ++++++++----------- crates/ide_completion/src/tests/expression.rs | 12 +-- crates/ide_completion/src/tests/pattern.rs | 8 +- crates/ide_completion/src/tests/use_tree.rs | 2 +- 7 files changed, 69 insertions(+), 90 deletions(-) diff --git a/crates/ide_completion/src/completions/qualified_path.rs b/crates/ide_completion/src/completions/qualified_path.rs index acd02616b1..86b1f534b2 100644 --- a/crates/ide_completion/src/completions/qualified_path.rs +++ b/crates/ide_completion/src/completions/qualified_path.rs @@ -580,8 +580,8 @@ impl Foo { } "#, expect![[r#" - ev Bar () - ev Baz () + ev Bar Bar + ev Baz Baz me foo(…) fn(self) "#]], ); @@ -626,7 +626,7 @@ fn main() { } "#, expect![[r#" - ev Bar () + ev Bar Bar "#]], ); } diff --git a/crates/ide_completion/src/render.rs b/crates/ide_completion/src/render.rs index 8003d200f9..e8ebb3e337 100644 --- a/crates/ide_completion/src/render.rs +++ b/crates/ide_completion/src/render.rs @@ -429,14 +429,14 @@ fn main() { Foo::Fo$0 } expect![[r#" [ CompletionItem { - label: "Foo", + label: "Foo {…}", source_range: 54..56, delete: 54..56, - insert: "Foo", + insert: "Foo { x: ${1:()}, y: ${2:()} }$0", kind: SymbolKind( Variant, ), - detail: "{x: i32, y: i32}", + detail: "Foo { x: i32, y: i32 }", }, ] "#]], @@ -444,7 +444,7 @@ fn main() { Foo::Fo$0 } } #[test] - fn enum_detail_doesnt_include_tuple_fields() { + fn enum_detail_includes_tuple_fields() { check( r#" enum Foo { Foo (i32, i32) } @@ -458,13 +458,11 @@ fn main() { Foo::Fo$0 } label: "Foo(…)", source_range: 46..48, delete: 46..48, - insert: "Foo($0)", + insert: "Foo(${1:()}, ${2:()})$0", kind: SymbolKind( Variant, ), - lookup: "Foo", - detail: "(i32, i32)", - trigger_call_info: true, + detail: "Foo(i32, i32)", }, ] "#]], @@ -511,7 +509,7 @@ fn main() { fo$0 } } #[test] - fn enum_detail_just_parentheses_for_unit() { + fn enum_detail_just_name_for_unit() { check( r#" enum Foo { Foo } @@ -525,11 +523,11 @@ fn main() { Foo::Fo$0 } label: "Foo", source_range: 35..37, delete: 35..37, - insert: "Foo", + insert: "Foo$0", kind: SymbolKind( Variant, ), - detail: "()", + detail: "Foo", }, ] "#]], @@ -573,15 +571,15 @@ fn main() { let _: m::Spam = S$0 } ), }, CompletionItem { - label: "Spam::Bar(…)", + label: "m::Spam::Bar(…)", source_range: 75..76, delete: 75..76, - insert: "Spam::Bar($0)", + insert: "m::Spam::Bar(${1:()})$0", kind: SymbolKind( Variant, ), lookup: "Spam::Bar", - detail: "(i32)", + detail: "m::Spam::Bar(i32)", relevance: CompletionRelevance { exact_name_match: false, type_match: Some( @@ -592,18 +590,17 @@ fn main() { let _: m::Spam = S$0 } is_private_editable: false, exact_postfix_snippet_match: false, }, - trigger_call_info: true, }, CompletionItem { label: "m::Spam::Foo", source_range: 75..76, delete: 75..76, - insert: "m::Spam::Foo", + insert: "m::Spam::Foo$0", kind: SymbolKind( Variant, ), lookup: "Spam::Foo", - detail: "()", + detail: "m::Spam::Foo", relevance: CompletionRelevance { exact_name_match: false, type_match: Some( @@ -788,11 +785,11 @@ use self::E::*; label: "V", source_range: 10..12, delete: 10..12, - insert: "V", + insert: "V$0", kind: SymbolKind( Variant, ), - detail: "()", + detail: "V", documentation: Documentation( "variant docs", ), diff --git a/crates/ide_completion/src/render/builder_ext.rs b/crates/ide_completion/src/render/builder_ext.rs index 653515fec2..70767a2a9c 100644 --- a/crates/ide_completion/src/render/builder_ext.rs +++ b/crates/ide_completion/src/render/builder_ext.rs @@ -8,6 +8,7 @@ use crate::{context::PathKind, item::Builder, patterns::ImmediateLocation, Compl #[derive(Debug)] pub(super) enum Params { Named(Option, Vec), + #[allow(dead_code)] Anonymous(usize), } diff --git a/crates/ide_completion/src/render/enum_variant.rs b/crates/ide_completion/src/render/enum_variant.rs index 914ace910d..6f358bfd4c 100644 --- a/crates/ide_completion/src/render/enum_variant.rs +++ b/crates/ide_completion/src/render/enum_variant.rs @@ -1,13 +1,15 @@ //! Renderer for `enum` variants. -use hir::{db::HirDatabase, HasAttrs, HirDisplay, StructKind}; +use hir::{HasAttrs, StructKind}; use ide_db::SymbolKind; -use itertools::Itertools; use syntax::SmolStr; use crate::{ item::{CompletionItem, ImportEdit}, - render::{builder_ext::Params, compute_ref_match, compute_type_match, RenderContext}, + render::{ + compound::{render_record, render_tuple, RenderedCompound}, + compute_ref_match, compute_type_match, RenderContext, + }, CompletionRelevance, }; @@ -46,20 +48,46 @@ fn render( let qualified_name = qualified_name.to_string(); let short_qualified_name: SmolStr = short_qualified_name.to_string().into(); - let mut item = CompletionItem::new(SymbolKind::Variant, ctx.source_range(), qualified_name); + let mut rendered = match variant_kind { + StructKind::Tuple => { + render_tuple(db, ctx.snippet_cap(), &variant.fields(db), Some(&qualified_name)) + } + StructKind::Record => { + render_record(db, ctx.snippet_cap(), &variant.fields(db), Some(&qualified_name)) + } + StructKind::Unit => { + RenderedCompound { literal: qualified_name.clone(), detail: qualified_name.clone() } + } + }; + + if ctx.snippet_cap().is_some() { + rendered.literal.push_str("$0"); + } + + let mut item = CompletionItem::new( + SymbolKind::Variant, + ctx.source_range(), + match variant_kind { + StructKind::Tuple => SmolStr::from_iter([&qualified_name, "(…)"]), + StructKind::Record => SmolStr::from_iter([&qualified_name, " {…}"]), + StructKind::Unit => qualified_name.into(), + }, + ); + item.set_documentation(variant.docs(db)) .set_deprecated(ctx.is_deprecated(variant)) - .detail(detail(db, variant, variant_kind)); + .detail(rendered.detail); + + match ctx.snippet_cap() { + Some(snippet_cap) => item.insert_snippet(snippet_cap, rendered.literal), + None => item.insert_text(rendered.literal), + }; if let Some(import_to_add) = import_to_add { item.add_import(import_to_add); } - if variant_kind == hir::StructKind::Tuple { - cov_mark::hit!(inserts_parens_for_tuple_enums); - let params = Params::Anonymous(variant.fields(db).len()); - item.add_call_parens(completion, short_qualified_name, params); - } else if qualified { + if qualified { item.lookup_by(short_qualified_name); } @@ -75,50 +103,3 @@ fn render( item.build() } - -fn detail(db: &dyn HirDatabase, variant: hir::Variant, variant_kind: StructKind) -> String { - let detail_types = variant.fields(db).into_iter().map(|field| (field.name(db), field.ty(db))); - - match variant_kind { - hir::StructKind::Tuple | hir::StructKind::Unit => { - format!("({})", detail_types.format_with(", ", |(_, t), f| f(&t.display(db)))) - } - hir::StructKind::Record => { - format!( - "{{{}}}", - detail_types.format_with(", ", |(n, t), f| { - f(&n)?; - f(&": ")?; - f(&t.display(db)) - }), - ) - } - } -} - -#[cfg(test)] -mod tests { - use crate::tests::check_edit; - - #[test] - fn inserts_parens_for_tuple_enums() { - cov_mark::check!(inserts_parens_for_tuple_enums); - check_edit( - "Some", - r#" -enum Option { Some(T), None } -use Option::*; -fn main() -> Option { - Som$0 -} -"#, - r#" -enum Option { Some(T), None } -use Option::*; -fn main() -> Option { - Some($0) -} -"#, - ); - } -} diff --git a/crates/ide_completion/src/tests/expression.rs b/crates/ide_completion/src/tests/expression.rs index a841605e49..bb8b34b79a 100644 --- a/crates/ide_completion/src/tests/expression.rs +++ b/crates/ide_completion/src/tests/expression.rs @@ -61,7 +61,7 @@ fn baz() { fn function() fn() sc STATIC un Union - ev TupleV(…) (u32) + ev TupleV(…) TupleV(u32) ct CONST "#]], ) @@ -171,7 +171,7 @@ impl Unit { fn function() fn() sc STATIC un Union - ev TupleV(…) (u32) + ev TupleV(…) TupleV(u32) ct CONST "#]], ); @@ -200,7 +200,7 @@ impl Unit { fn function() fn() sc STATIC un Union - ev TupleV(…) (u32) + ev TupleV(…) TupleV(u32) ct CONST "#]], ); @@ -543,9 +543,9 @@ fn func() { } "#, expect![[r#" - ev TupleV(…) (u32) - ev RecordV {field: u32} - ev UnitV () + ev TupleV(…) TupleV(u32) + ev RecordV {…} RecordV { field: u32 } + ev UnitV UnitV ct ASSOC_CONST const ASSOC_CONST: () fn assoc_fn() fn() ta AssocType type AssocType = () diff --git a/crates/ide_completion/src/tests/pattern.rs b/crates/ide_completion/src/tests/pattern.rs index 0ca20f93b5..7767f24632 100644 --- a/crates/ide_completion/src/tests/pattern.rs +++ b/crates/ide_completion/src/tests/pattern.rs @@ -218,7 +218,7 @@ fn foo() { expect![[r#" kw ref kw mut - ev E::X () + ev E::X E::X en E ma m!(…) macro_rules! m "#]], @@ -291,9 +291,9 @@ fn func() { } "#, expect![[r#" - ev TupleV(…) (u32) - ev RecordV {field: u32} - ev UnitV () + ev TupleV(…) TupleV(u32) + ev RecordV {…} RecordV { field: u32 } + ev UnitV UnitV "#]], ); } diff --git a/crates/ide_completion/src/tests/use_tree.rs b/crates/ide_completion/src/tests/use_tree.rs index 73cb83957f..ca06cc376f 100644 --- a/crates/ide_completion/src/tests/use_tree.rs +++ b/crates/ide_completion/src/tests/use_tree.rs @@ -167,7 +167,7 @@ impl Foo { } "#, expect![[r#" - ev Variant () + ev Variant Variant "#]], ); } From 6a1c1511595da866f6609e14c449136fa377fa33 Mon Sep 17 00:00:00 2001 From: Morgan Thomas Date: Fri, 11 Mar 2022 19:10:43 -0800 Subject: [PATCH 3/6] visibility tweak for CI --- crates/ide_completion/src/render/compound.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/ide_completion/src/render/compound.rs b/crates/ide_completion/src/render/compound.rs index 586bb92a8e..c1047e9b62 100644 --- a/crates/ide_completion/src/render/compound.rs +++ b/crates/ide_completion/src/render/compound.rs @@ -9,8 +9,8 @@ use itertools::Itertools; /// 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, + pub(crate) literal: String, + pub(crate) detail: String, } /// Render a record type (or sub-type) to a `RenderedCompound`. Use `None` for From b3640ce424f5440190277df702ebe885827b128c Mon Sep 17 00:00:00 2001 From: Morgan Thomas Date: Fri, 11 Mar 2022 19:23:04 -0800 Subject: [PATCH 4/6] make the doc comment on render::compound::visible_fields a little better --- crates/ide_completion/src/render/compound.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/ide_completion/src/render/compound.rs b/crates/ide_completion/src/render/compound.rs index c1047e9b62..19bc53203a 100644 --- a/crates/ide_completion/src/render/compound.rs +++ b/crates/ide_completion/src/render/compound.rs @@ -71,9 +71,9 @@ pub(crate) fn render_tuple( } } -/// Find all the visible fields in a `HasAttrs`. Returns the list of visible +/// 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 is not marked `#[non_exhaustive]`). +/// private fields and its item is not marked `#[non_exhaustive]`). pub(crate) fn visible_fields( ctx: &RenderContext<'_>, fields: &[hir::Field], From d430ddd809c7bb9721f4a5550cc1ff63eb700845 Mon Sep 17 00:00:00 2001 From: Morgan Thomas Date: Sat, 12 Mar 2022 04:40:05 -0800 Subject: [PATCH 5/6] Extract the code for formatting struct and enum-variant literal labels out into a common function --- crates/ide_completion/src/render/compound.rs | 12 +++++++++++- crates/ide_completion/src/render/enum_variant.rs | 8 ++------ crates/ide_completion/src/render/struct_literal.rs | 9 ++++----- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/crates/ide_completion/src/render/compound.rs b/crates/ide_completion/src/render/compound.rs index 19bc53203a..a1a199c6e2 100644 --- a/crates/ide_completion/src/render/compound.rs +++ b/crates/ide_completion/src/render/compound.rs @@ -1,9 +1,10 @@ //! Code common to structs, unions, and enum variants. use crate::render::RenderContext; -use hir::{db::HirDatabase, HasAttrs, HasVisibility, HirDisplay}; +use hir::{db::HirDatabase, HasAttrs, 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 @@ -91,3 +92,12 @@ pub(crate) fn visible_fields( n_fields - fields.len() > 0 || item.attrs(ctx.db()).by_key("non_exhaustive").exists(); 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) -> SmolStr { + match kind { + StructKind::Tuple => SmolStr::from_iter([name, "(…)"]), + StructKind::Record => SmolStr::from_iter([name, " {…}"]), + StructKind::Unit => name.into(), + } +} diff --git a/crates/ide_completion/src/render/enum_variant.rs b/crates/ide_completion/src/render/enum_variant.rs index 6f358bfd4c..5b485005d3 100644 --- a/crates/ide_completion/src/render/enum_variant.rs +++ b/crates/ide_completion/src/render/enum_variant.rs @@ -7,7 +7,7 @@ use syntax::SmolStr; use crate::{ item::{CompletionItem, ImportEdit}, render::{ - compound::{render_record, render_tuple, RenderedCompound}, + compound::{format_literal_label, render_record, render_tuple, RenderedCompound}, compute_ref_match, compute_type_match, RenderContext, }, CompletionRelevance, @@ -67,11 +67,7 @@ fn render( let mut item = CompletionItem::new( SymbolKind::Variant, ctx.source_range(), - match variant_kind { - StructKind::Tuple => SmolStr::from_iter([&qualified_name, "(…)"]), - StructKind::Record => SmolStr::from_iter([&qualified_name, " {…}"]), - StructKind::Unit => qualified_name.into(), - }, + format_literal_label(&qualified_name, variant_kind), ); item.set_documentation(variant.docs(db)) diff --git a/crates/ide_completion/src/render/struct_literal.rs b/crates/ide_completion/src/render/struct_literal.rs index 124b465773..a686be6691 100644 --- a/crates/ide_completion/src/render/struct_literal.rs +++ b/crates/ide_completion/src/render/struct_literal.rs @@ -4,7 +4,9 @@ use hir::{HasAttrs, Name, StructKind}; use syntax::SmolStr; use crate::{ - render::compound::{render_record, render_tuple, visible_fields, RenderedCompound}, + render::compound::{ + format_literal_label, render_record, render_tuple, visible_fields, RenderedCompound, + }, render::RenderContext, CompletionItem, CompletionItemKind, }; @@ -42,10 +44,7 @@ fn build_completion( let mut item = CompletionItem::new( CompletionItemKind::Snippet, ctx.source_range(), - match kind { - StructKind::Tuple => SmolStr::from_iter([&name, "(…)"]), - _ => SmolStr::from_iter([&name, " {…}"]), - }, + format_literal_label(&name, kind), ); item.set_documentation(ctx.docs(def)) From f27c0ef1cf41ad381ac4168bb291442b68a4932b Mon Sep 17 00:00:00 2001 From: Morgan Thomas Date: Sat, 12 Mar 2022 05:01:25 -0800 Subject: [PATCH 6/6] Reduce intermediate string allocations in render::compound::render_record and ::render_tuple --- crates/ide_completion/src/render/compound.rs | 56 +++++++++----------- 1 file changed, 24 insertions(+), 32 deletions(-) diff --git a/crates/ide_completion/src/render/compound.rs b/crates/ide_completion/src/render/compound.rs index a1a199c6e2..c7f3bd1f79 100644 --- a/crates/ide_completion/src/render/compound.rs +++ b/crates/ide_completion/src/render/compound.rs @@ -22,24 +22,21 @@ pub(crate) fn render_record( fields: &[hir::Field], name: Option<&str>, ) -> RenderedCompound { - let fields = fields.iter(); + 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))) + }); - 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(", ")), + literal: format!("{} {{ {} }}", name.unwrap_or(""), completions), + detail: format!("{} {{ {} }}", name.unwrap_or(""), types), } } @@ -51,24 +48,19 @@ pub(crate) fn render_tuple( fields: &[hir::Field], name: Option<&str>, ) -> RenderedCompound { - let fields = fields.iter(); + 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))); - 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(", ")), + literal: format!("{}({})", name.unwrap_or(""), completions), + detail: format!("{}({})", name.unwrap_or(""), types), } }