rename completion -> ide_completion

We don't have completion-related PRs in flight, so lets do it
This commit is contained in:
Aleksey Kladov 2021-02-17 17:53:31 +03:00
parent 6334ce866a
commit 3db64a400c
37 changed files with 33 additions and 32 deletions

View file

@ -0,0 +1,94 @@
//! Extensions for `Builder` structure required for item rendering.
use itertools::Itertools;
use test_utils::mark;
use crate::{item::Builder, CompletionContext};
#[derive(Debug)]
pub(super) enum Params {
Named(Vec<String>),
Anonymous(usize),
}
impl Params {
pub(super) fn len(&self) -> usize {
match self {
Params::Named(xs) => xs.len(),
Params::Anonymous(len) => *len,
}
}
pub(super) fn is_empty(&self) -> bool {
self.len() == 0
}
}
impl Builder {
fn should_add_parens(&self, ctx: &CompletionContext) -> bool {
if !ctx.config.add_call_parenthesis {
return false;
}
if ctx.use_item_syntax.is_some() {
mark::hit!(no_parens_in_use_item);
return false;
}
if ctx.is_pattern_call {
return false;
}
if ctx.is_call {
return false;
}
// Don't add parentheses if the expected type is some function reference.
if let Some(ty) = &ctx.expected_type {
if ty.is_fn() {
mark::hit!(no_call_parens_if_fn_ptr_needed);
return false;
}
}
// Nothing prevents us from adding parentheses
true
}
pub(super) fn add_call_parens(
mut self,
ctx: &CompletionContext,
name: String,
params: Params,
) -> Builder {
if !self.should_add_parens(ctx) {
return self;
}
let cap = match ctx.config.snippet_cap {
Some(it) => it,
None => return self,
};
// If not an import, add parenthesis automatically.
mark::hit!(inserts_parens_for_function_calls);
let (snippet, label) = if params.is_empty() {
(format!("{}()$0", name), format!("{}()", name))
} else {
self = self.trigger_call_info();
let snippet = match (ctx.config.add_call_argument_snippets, params) {
(true, Params::Named(params)) => {
let function_params_snippet =
params.iter().enumerate().format_with(", ", |(index, param_name), f| {
f(&format_args!("${{{}:{}}}", index + 1, param_name))
});
format!("{}({})$0", name, function_params_snippet)
}
_ => {
mark::hit!(suppress_arg_snippets);
format!("{}($0)", name)
}
};
(snippet, format!("{}(…)", name))
};
self.lookup_by(name).label(label).insert_snippet(cap, snippet)
}
}

View file

@ -0,0 +1,59 @@
//! Renderer for `const` fields.
use hir::HasSource;
use ide_db::SymbolKind;
use syntax::{
ast::{Const, NameOwner},
display::const_label,
};
use crate::{
item::{CompletionItem, CompletionKind},
render::RenderContext,
};
pub(crate) fn render_const<'a>(
ctx: RenderContext<'a>,
const_: hir::Const,
) -> Option<CompletionItem> {
ConstRender::new(ctx, const_)?.render()
}
#[derive(Debug)]
struct ConstRender<'a> {
ctx: RenderContext<'a>,
const_: hir::Const,
ast_node: Const,
}
impl<'a> ConstRender<'a> {
fn new(ctx: RenderContext<'a>, const_: hir::Const) -> Option<ConstRender<'a>> {
let ast_node = const_.source(ctx.db())?.value;
Some(ConstRender { ctx, const_, ast_node })
}
fn render(self) -> Option<CompletionItem> {
let name = self.name()?;
let detail = self.detail();
let item = CompletionItem::new(CompletionKind::Reference, self.ctx.source_range(), name)
.kind(SymbolKind::Const)
.set_documentation(self.ctx.docs(self.const_))
.set_deprecated(
self.ctx.is_deprecated(self.const_)
|| self.ctx.is_deprecated_assoc_item(self.const_),
)
.detail(detail)
.build();
Some(item)
}
fn name(&self) -> Option<String> {
self.ast_node.name().map(|name| name.text().to_string())
}
fn detail(&self) -> String {
const_label(&self.ast_node)
}
}

View file

@ -0,0 +1,131 @@
//! Renderer for `enum` variants.
use hir::{HasAttrs, HirDisplay, ModPath, StructKind};
use ide_db::SymbolKind;
use itertools::Itertools;
use test_utils::mark;
use crate::{
item::{CompletionItem, CompletionKind, ImportEdit},
render::{builder_ext::Params, RenderContext},
};
pub(crate) fn render_variant<'a>(
ctx: RenderContext<'a>,
import_to_add: Option<ImportEdit>,
local_name: Option<String>,
variant: hir::Variant,
path: Option<ModPath>,
) -> CompletionItem {
let _p = profile::span("render_enum_variant");
EnumRender::new(ctx, local_name, variant, path).render(import_to_add)
}
#[derive(Debug)]
struct EnumRender<'a> {
ctx: RenderContext<'a>,
name: String,
variant: hir::Variant,
path: Option<ModPath>,
qualified_name: String,
short_qualified_name: String,
variant_kind: StructKind,
}
impl<'a> EnumRender<'a> {
fn new(
ctx: RenderContext<'a>,
local_name: Option<String>,
variant: hir::Variant,
path: Option<ModPath>,
) -> EnumRender<'a> {
let name = local_name.unwrap_or_else(|| variant.name(ctx.db()).to_string());
let variant_kind = variant.kind(ctx.db());
let (qualified_name, short_qualified_name) = match &path {
Some(path) => {
let full = path.to_string();
let segments = path.segments();
let short = segments[segments.len().saturating_sub(2)..].iter().join("::");
(full, short)
}
None => (name.to_string(), name.to_string()),
};
EnumRender { ctx, name, variant, path, qualified_name, short_qualified_name, variant_kind }
}
fn render(self, import_to_add: Option<ImportEdit>) -> CompletionItem {
let mut builder = CompletionItem::new(
CompletionKind::Reference,
self.ctx.source_range(),
self.qualified_name.clone(),
)
.kind(SymbolKind::Variant)
.set_documentation(self.variant.docs(self.ctx.db()))
.set_deprecated(self.ctx.is_deprecated(self.variant))
.add_import(import_to_add)
.detail(self.detail());
if self.variant_kind == StructKind::Tuple {
mark::hit!(inserts_parens_for_tuple_enums);
let params = Params::Anonymous(self.variant.fields(self.ctx.db()).len());
builder =
builder.add_call_parens(self.ctx.completion, self.short_qualified_name, params);
} else if self.path.is_some() {
builder = builder.lookup_by(self.short_qualified_name);
}
builder.build()
}
fn detail(&self) -> String {
let detail_types = self
.variant
.fields(self.ctx.db())
.into_iter()
.map(|field| (field.name(self.ctx.db()), field.signature_ty(self.ctx.db())));
match self.variant_kind {
StructKind::Tuple | StructKind::Unit => format!(
"({})",
detail_types.map(|(_, t)| t.display(self.ctx.db()).to_string()).format(", ")
),
StructKind::Record => format!(
"{{ {} }}",
detail_types
.map(|(n, t)| format!("{}: {}", n, t.display(self.ctx.db()).to_string()))
.format(", ")
),
}
}
}
#[cfg(test)]
mod tests {
use test_utils::mark;
use crate::test_utils::check_edit;
#[test]
fn inserts_parens_for_tuple_enums() {
mark::check!(inserts_parens_for_tuple_enums);
check_edit(
"Some",
r#"
enum Option<T> { Some(T), None }
use Option::*;
fn main() -> Option<i32> {
Som$0
}
"#,
r#"
enum Option<T> { Some(T), None }
use Option::*;
fn main() -> Option<i32> {
Some($0)
}
"#,
);
}
}

View file

@ -0,0 +1,345 @@
//! Renderer for function calls.
use hir::{HasSource, HirDisplay, Type};
use ide_db::SymbolKind;
use syntax::ast::Fn;
use test_utils::mark;
use crate::{
item::{CompletionItem, CompletionItemKind, CompletionKind, ImportEdit},
render::{builder_ext::Params, RenderContext},
};
pub(crate) fn render_fn<'a>(
ctx: RenderContext<'a>,
import_to_add: Option<ImportEdit>,
local_name: Option<String>,
fn_: hir::Function,
) -> Option<CompletionItem> {
let _p = profile::span("render_fn");
Some(FunctionRender::new(ctx, local_name, fn_)?.render(import_to_add))
}
#[derive(Debug)]
struct FunctionRender<'a> {
ctx: RenderContext<'a>,
name: String,
func: hir::Function,
ast_node: Fn,
}
impl<'a> FunctionRender<'a> {
fn new(
ctx: RenderContext<'a>,
local_name: Option<String>,
fn_: hir::Function,
) -> Option<FunctionRender<'a>> {
let name = local_name.unwrap_or_else(|| fn_.name(ctx.db()).to_string());
let ast_node = fn_.source(ctx.db())?.value;
Some(FunctionRender { ctx, name, func: fn_, ast_node })
}
fn render(self, import_to_add: Option<ImportEdit>) -> CompletionItem {
let params = self.params();
CompletionItem::new(CompletionKind::Reference, self.ctx.source_range(), self.name.clone())
.kind(self.kind())
.set_documentation(self.ctx.docs(self.func))
.set_deprecated(
self.ctx.is_deprecated(self.func) || self.ctx.is_deprecated_assoc_item(self.func),
)
.detail(self.detail())
.add_call_parens(self.ctx.completion, self.name, params)
.add_import(import_to_add)
.build()
}
fn detail(&self) -> String {
let ty = self.func.ret_type(self.ctx.db());
format!("-> {}", ty.display(self.ctx.db()))
}
fn add_arg(&self, arg: &str, ty: &Type) -> String {
if let Some(derefed_ty) = ty.remove_ref() {
for (name, local) in self.ctx.completion.locals.iter() {
if name == arg && local.ty(self.ctx.db()) == derefed_ty {
let mutability = if ty.is_mutable_reference() { "&mut " } else { "&" };
return format!("{}{}", mutability, arg);
}
}
}
arg.to_string()
}
fn params(&self) -> Params {
let ast_params = match self.ast_node.param_list() {
Some(it) => it,
None => return Params::Named(Vec::new()),
};
let mut params_pats = Vec::new();
let params_ty = if self.ctx.completion.dot_receiver.is_some() {
self.func.method_params(self.ctx.db()).unwrap_or_default()
} else {
if let Some(s) = ast_params.self_param() {
mark::hit!(parens_for_method_call_as_assoc_fn);
params_pats.push(Some(s.to_string()));
}
self.func.assoc_fn_params(self.ctx.db())
};
params_pats
.extend(ast_params.params().into_iter().map(|it| it.pat().map(|it| it.to_string())));
let params = params_pats
.into_iter()
.zip(params_ty)
.flat_map(|(pat, param_ty)| {
let pat = pat?;
let name = pat;
let arg = name.trim_start_matches("mut ").trim_start_matches('_');
Some(self.add_arg(arg, param_ty.ty()))
})
.collect();
Params::Named(params)
}
fn kind(&self) -> CompletionItemKind {
if self.func.self_param(self.ctx.db()).is_some() {
CompletionItemKind::Method
} else {
SymbolKind::Function.into()
}
}
}
#[cfg(test)]
mod tests {
use test_utils::mark;
use crate::{
test_utils::{check_edit, check_edit_with_config, TEST_CONFIG},
CompletionConfig,
};
#[test]
fn inserts_parens_for_function_calls() {
mark::check!(inserts_parens_for_function_calls);
check_edit(
"no_args",
r#"
fn no_args() {}
fn main() { no_$0 }
"#,
r#"
fn no_args() {}
fn main() { no_args()$0 }
"#,
);
check_edit(
"with_args",
r#"
fn with_args(x: i32, y: String) {}
fn main() { with_$0 }
"#,
r#"
fn with_args(x: i32, y: String) {}
fn main() { with_args(${1:x}, ${2:y})$0 }
"#,
);
check_edit(
"foo",
r#"
struct S;
impl S {
fn foo(&self) {}
}
fn bar(s: &S) { s.f$0 }
"#,
r#"
struct S;
impl S {
fn foo(&self) {}
}
fn bar(s: &S) { s.foo()$0 }
"#,
);
check_edit(
"foo",
r#"
struct S {}
impl S {
fn foo(&self, x: i32) {}
}
fn bar(s: &S) {
s.f$0
}
"#,
r#"
struct S {}
impl S {
fn foo(&self, x: i32) {}
}
fn bar(s: &S) {
s.foo(${1:x})$0
}
"#,
);
}
#[test]
fn parens_for_method_call_as_assoc_fn() {
mark::check!(parens_for_method_call_as_assoc_fn);
check_edit(
"foo",
r#"
struct S;
impl S {
fn foo(&self) {}
}
fn main() { S::f$0 }
"#,
r#"
struct S;
impl S {
fn foo(&self) {}
}
fn main() { S::foo(${1:&self})$0 }
"#,
);
}
#[test]
fn suppress_arg_snippets() {
mark::check!(suppress_arg_snippets);
check_edit_with_config(
CompletionConfig { add_call_argument_snippets: false, ..TEST_CONFIG },
"with_args",
r#"
fn with_args(x: i32, y: String) {}
fn main() { with_$0 }
"#,
r#"
fn with_args(x: i32, y: String) {}
fn main() { with_args($0) }
"#,
);
}
#[test]
fn strips_underscores_from_args() {
check_edit(
"foo",
r#"
fn foo(_foo: i32, ___bar: bool, ho_ge_: String) {}
fn main() { f$0 }
"#,
r#"
fn foo(_foo: i32, ___bar: bool, ho_ge_: String) {}
fn main() { foo(${1:foo}, ${2:bar}, ${3:ho_ge_})$0 }
"#,
);
}
#[test]
fn insert_ref_when_matching_local_in_scope() {
check_edit(
"ref_arg",
r#"
struct Foo {}
fn ref_arg(x: &Foo) {}
fn main() {
let x = Foo {};
ref_ar$0
}
"#,
r#"
struct Foo {}
fn ref_arg(x: &Foo) {}
fn main() {
let x = Foo {};
ref_arg(${1:&x})$0
}
"#,
);
}
#[test]
fn insert_mut_ref_when_matching_local_in_scope() {
check_edit(
"ref_arg",
r#"
struct Foo {}
fn ref_arg(x: &mut Foo) {}
fn main() {
let x = Foo {};
ref_ar$0
}
"#,
r#"
struct Foo {}
fn ref_arg(x: &mut Foo) {}
fn main() {
let x = Foo {};
ref_arg(${1:&mut x})$0
}
"#,
);
}
#[test]
fn insert_ref_when_matching_local_in_scope_for_method() {
check_edit(
"apply_foo",
r#"
struct Foo {}
struct Bar {}
impl Bar {
fn apply_foo(&self, x: &Foo) {}
}
fn main() {
let x = Foo {};
let y = Bar {};
y.$0
}
"#,
r#"
struct Foo {}
struct Bar {}
impl Bar {
fn apply_foo(&self, x: &Foo) {}
}
fn main() {
let x = Foo {};
let y = Bar {};
y.apply_foo(${1:&x})$0
}
"#,
);
}
#[test]
fn trim_mut_keyword_in_func_completion() {
check_edit(
"take_mutably",
r#"
fn take_mutably(mut x: &i32) {}
fn main() {
take_m$0
}
"#,
r#"
fn take_mutably(mut x: &i32) {}
fn main() {
take_mutably(${1:x})$0
}
"#,
);
}
}

View file

@ -0,0 +1,214 @@
//! Renderer for macro invocations.
use hir::{Documentation, HasSource};
use ide_db::SymbolKind;
use syntax::display::macro_label;
use test_utils::mark;
use crate::{
item::{CompletionItem, CompletionKind, ImportEdit},
render::RenderContext,
};
pub(crate) fn render_macro<'a>(
ctx: RenderContext<'a>,
import_to_add: Option<ImportEdit>,
name: String,
macro_: hir::MacroDef,
) -> Option<CompletionItem> {
let _p = profile::span("render_macro");
MacroRender::new(ctx, name, macro_).render(import_to_add)
}
#[derive(Debug)]
struct MacroRender<'a> {
ctx: RenderContext<'a>,
name: String,
macro_: hir::MacroDef,
docs: Option<Documentation>,
bra: &'static str,
ket: &'static str,
}
impl<'a> MacroRender<'a> {
fn new(ctx: RenderContext<'a>, name: String, macro_: hir::MacroDef) -> MacroRender<'a> {
let docs = ctx.docs(macro_);
let docs_str = docs.as_ref().map_or("", |s| s.as_str());
let (bra, ket) = guess_macro_braces(&name, docs_str);
MacroRender { ctx, name, macro_, docs, bra, ket }
}
fn render(&self, import_to_add: Option<ImportEdit>) -> Option<CompletionItem> {
let mut builder =
CompletionItem::new(CompletionKind::Reference, self.ctx.source_range(), &self.label())
.kind(SymbolKind::Macro)
.set_documentation(self.docs.clone())
.set_deprecated(self.ctx.is_deprecated(self.macro_))
.add_import(import_to_add)
.set_detail(self.detail());
let needs_bang = self.needs_bang();
builder = match self.ctx.snippet_cap() {
Some(cap) if needs_bang => {
let snippet = self.snippet();
let lookup = self.lookup();
builder.insert_snippet(cap, snippet).lookup_by(lookup)
}
None if needs_bang => builder.insert_text(self.banged_name()),
_ => {
mark::hit!(dont_insert_macro_call_parens_unncessary);
builder.insert_text(&self.name)
}
};
Some(builder.build())
}
fn needs_bang(&self) -> bool {
self.ctx.completion.use_item_syntax.is_none() && !self.ctx.completion.is_macro_call
}
fn label(&self) -> String {
if self.needs_bang() && self.ctx.snippet_cap().is_some() {
format!("{}!{}{}", self.name, self.bra, self.ket)
} else {
self.banged_name()
}
}
fn snippet(&self) -> String {
format!("{}!{}$0{}", self.name, self.bra, self.ket)
}
fn lookup(&self) -> String {
self.banged_name()
}
fn banged_name(&self) -> String {
format!("{}!", self.name)
}
fn detail(&self) -> Option<String> {
let ast_node = self.macro_.source(self.ctx.db())?.value;
Some(macro_label(&ast_node))
}
}
fn guess_macro_braces(macro_name: &str, docs: &str) -> (&'static str, &'static str) {
let mut votes = [0, 0, 0];
for (idx, s) in docs.match_indices(&macro_name) {
let (before, after) = (&docs[..idx], &docs[idx + s.len()..]);
// Ensure to match the full word
if after.starts_with('!')
&& !before.ends_with(|c: char| c == '_' || c.is_ascii_alphanumeric())
{
// It may have spaces before the braces like `foo! {}`
match after[1..].chars().find(|&c| !c.is_whitespace()) {
Some('{') => votes[0] += 1,
Some('[') => votes[1] += 1,
Some('(') => votes[2] += 1,
_ => {}
}
}
}
// Insert a space before `{}`.
// We prefer the last one when some votes equal.
let (_vote, (bra, ket)) = votes
.iter()
.zip(&[(" {", "}"), ("[", "]"), ("(", ")")])
.max_by_key(|&(&vote, _)| vote)
.unwrap();
(*bra, *ket)
}
#[cfg(test)]
mod tests {
use test_utils::mark;
use crate::test_utils::check_edit;
#[test]
fn dont_insert_macro_call_parens_unncessary() {
mark::check!(dont_insert_macro_call_parens_unncessary);
check_edit(
"frobnicate!",
r#"
//- /main.rs crate:main deps:foo
use foo::$0;
//- /foo/lib.rs crate:foo
#[macro_export]
macro_rules! frobnicate { () => () }
"#,
r#"
use foo::frobnicate;
"#,
);
check_edit(
"frobnicate!",
r#"
macro_rules! frobnicate { () => () }
fn main() { frob$0!(); }
"#,
r#"
macro_rules! frobnicate { () => () }
fn main() { frobnicate!(); }
"#,
);
}
#[test]
fn guesses_macro_braces() {
check_edit(
"vec!",
r#"
/// Creates a [`Vec`] containing the arguments.
///
/// ```
/// let v = vec![1, 2, 3];
/// assert_eq!(v[0], 1);
/// assert_eq!(v[1], 2);
/// assert_eq!(v[2], 3);
/// ```
macro_rules! vec { () => {} }
fn fn main() { v$0 }
"#,
r#"
/// Creates a [`Vec`] containing the arguments.
///
/// ```
/// let v = vec![1, 2, 3];
/// assert_eq!(v[0], 1);
/// assert_eq!(v[1], 2);
/// assert_eq!(v[2], 3);
/// ```
macro_rules! vec { () => {} }
fn fn main() { vec![$0] }
"#,
);
check_edit(
"foo!",
r#"
/// Foo
///
/// Don't call `fooo!()` `fooo!()`, or `_foo![]` `_foo![]`,
/// call as `let _=foo! { hello world };`
macro_rules! foo { () => {} }
fn main() { $0 }
"#,
r#"
/// Foo
///
/// Don't call `fooo!()` `fooo!()`, or `_foo![]` `_foo![]`,
/// call as `let _=foo! { hello world };`
macro_rules! foo { () => {} }
fn main() { foo! {$0} }
"#,
)
}
}

View file

@ -0,0 +1,150 @@
//! Renderer for patterns.
use hir::{db::HirDatabase, HasAttrs, HasVisibility, Name, StructKind};
use ide_db::helpers::SnippetCap;
use itertools::Itertools;
use crate::{item::CompletionKind, render::RenderContext, CompletionItem, CompletionItemKind};
fn visible_fields(
ctx: &RenderContext<'_>,
fields: &[hir::Field],
item: impl HasAttrs,
) -> Option<(Vec<hir::Field>, bool)> {
let module = ctx.completion.scope.module()?;
let n_fields = fields.len();
let fields = fields
.into_iter()
.filter(|field| field.is_visible_from(ctx.db(), module))
.copied()
.collect::<Vec<_>>();
let fields_omitted =
n_fields - fields.len() > 0 || item.attrs(ctx.db()).by_key("non_exhaustive").exists();
Some((fields, fields_omitted))
}
pub(crate) fn render_struct_pat(
ctx: RenderContext<'_>,
strukt: hir::Struct,
local_name: Option<Name>,
) -> Option<CompletionItem> {
let _p = profile::span("render_struct_pat");
let fields = strukt.fields(ctx.db());
let (visible_fields, fields_omitted) = visible_fields(&ctx, &fields, strukt)?;
if visible_fields.is_empty() {
// Matching a struct without matching its fields is pointless, unlike matching a Variant without its fields
return None;
}
let name = local_name.unwrap_or_else(|| strukt.name(ctx.db())).to_string();
let pat = render_pat(&ctx, &name, strukt.kind(ctx.db()), &visible_fields, fields_omitted)?;
Some(build_completion(ctx, name, pat, strukt))
}
pub(crate) fn render_variant_pat(
ctx: RenderContext<'_>,
variant: hir::Variant,
local_name: Option<Name>,
path: Option<hir::ModPath>,
) -> Option<CompletionItem> {
let _p = profile::span("render_variant_pat");
let fields = variant.fields(ctx.db());
let (visible_fields, fields_omitted) = visible_fields(&ctx, &fields, variant)?;
let name = match &path {
Some(path) => path.to_string(),
None => local_name.unwrap_or_else(|| variant.name(ctx.db())).to_string(),
};
let pat = render_pat(&ctx, &name, variant.kind(ctx.db()), &visible_fields, fields_omitted)?;
Some(build_completion(ctx, name, pat, variant))
}
fn build_completion(
ctx: RenderContext<'_>,
name: String,
pat: String,
item: impl HasAttrs + Copy,
) -> CompletionItem {
let completion = CompletionItem::new(CompletionKind::Snippet, ctx.source_range(), name)
.kind(CompletionItemKind::Binding)
.set_documentation(ctx.docs(item))
.set_deprecated(ctx.is_deprecated(item))
.detail(&pat);
let completion = if let Some(snippet_cap) = ctx.snippet_cap() {
completion.insert_snippet(snippet_cap, pat)
} else {
completion.insert_text(pat)
};
completion.build()
}
fn render_pat(
ctx: &RenderContext<'_>,
name: &str,
kind: StructKind,
fields: &[hir::Field],
fields_omitted: bool,
) -> Option<String> {
let mut pat = match kind {
StructKind::Tuple if ctx.snippet_cap().is_some() => {
render_tuple_as_pat(&fields, &name, fields_omitted)
}
StructKind::Record => {
render_record_as_pat(ctx.db(), ctx.snippet_cap(), &fields, &name, fields_omitted)
}
_ => return None,
};
if ctx.completion.is_param {
pat.push(':');
pat.push(' ');
pat.push_str(&name);
}
if ctx.snippet_cap().is_some() {
pat.push_str("$0");
}
Some(pat)
}
fn render_record_as_pat(
db: &dyn HirDatabase,
snippet_cap: Option<SnippetCap>,
fields: &[hir::Field],
name: &str,
fields_omitted: bool,
) -> String {
let fields = fields.iter();
if snippet_cap.is_some() {
format!(
"{name} {{ {}{} }}",
fields
.enumerate()
.map(|(idx, field)| format!("{}${}", field.name(db), idx + 1))
.format(", "),
if fields_omitted { ", .." } else { "" },
name = name
)
} else {
format!(
"{name} {{ {}{} }}",
fields.map(|field| field.name(db)).format(", "),
if fields_omitted { ", .." } else { "" },
name = name
)
}
}
fn render_tuple_as_pat(fields: &[hir::Field], name: &str, fields_omitted: bool) -> String {
format!(
"{name}({}{})",
fields.iter().enumerate().map(|(idx, _)| format!("${}", idx + 1)).format(", "),
if fields_omitted { ", .." } else { "" },
name = name
)
}

View file

@ -0,0 +1,59 @@
//! Renderer for type aliases.
use hir::HasSource;
use ide_db::SymbolKind;
use syntax::{
ast::{NameOwner, TypeAlias},
display::type_label,
};
use crate::{
item::{CompletionItem, CompletionKind},
render::RenderContext,
};
pub(crate) fn render_type_alias<'a>(
ctx: RenderContext<'a>,
type_alias: hir::TypeAlias,
) -> Option<CompletionItem> {
TypeAliasRender::new(ctx, type_alias)?.render()
}
#[derive(Debug)]
struct TypeAliasRender<'a> {
ctx: RenderContext<'a>,
type_alias: hir::TypeAlias,
ast_node: TypeAlias,
}
impl<'a> TypeAliasRender<'a> {
fn new(ctx: RenderContext<'a>, type_alias: hir::TypeAlias) -> Option<TypeAliasRender<'a>> {
let ast_node = type_alias.source(ctx.db())?.value;
Some(TypeAliasRender { ctx, type_alias, ast_node })
}
fn render(self) -> Option<CompletionItem> {
let name = self.name()?;
let detail = self.detail();
let item = CompletionItem::new(CompletionKind::Reference, self.ctx.source_range(), name)
.kind(SymbolKind::TypeAlias)
.set_documentation(self.ctx.docs(self.type_alias))
.set_deprecated(
self.ctx.is_deprecated(self.type_alias)
|| self.ctx.is_deprecated_assoc_item(self.type_alias),
)
.detail(detail)
.build();
Some(item)
}
fn name(&self) -> Option<String> {
self.ast_node.name().map(|name| name.text().to_string())
}
fn detail(&self) -> String {
type_label(&self.ast_node)
}
}