mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-27 05:23:24 +00:00
Merge #6351
6351: Organized completions r=popzxc a=popzxc This PR continues the work on refactoring of the `completions` crate. In this episode: - Actual completions methods are encapsulated into `completions` module, so they aren't mixed with the rest of the code. - Name duplication was removed (`complete_attribute` => `completions::attribute`, `completion_context` => `context`). - `Completions` structure was moved from `item` module to the `completions`. - `presentation` module was removed, as it was basically a module with `impl` for `Completions`. - Code approaches were a bit unified here and there. Co-authored-by: Igor Aleksanov <popzxc@yandex.ru>
This commit is contained in:
commit
d10e2a04c8
22 changed files with 165 additions and 185 deletions
|
@ -1,5 +1,18 @@
|
||||||
//! This modules takes care of rendering various definitions as completion items.
|
//! This module defines an accumulator for completions which are going to be presented to user.
|
||||||
//! It also handles scoring (sorting) completions.
|
|
||||||
|
pub(crate) mod attribute;
|
||||||
|
pub(crate) mod dot;
|
||||||
|
pub(crate) mod record;
|
||||||
|
pub(crate) mod pattern;
|
||||||
|
pub(crate) mod fn_param;
|
||||||
|
pub(crate) mod keyword;
|
||||||
|
pub(crate) mod snippet;
|
||||||
|
pub(crate) mod qualified_path;
|
||||||
|
pub(crate) mod unqualified_path;
|
||||||
|
pub(crate) mod postfix;
|
||||||
|
pub(crate) mod macro_in_item_position;
|
||||||
|
pub(crate) mod trait_impl;
|
||||||
|
pub(crate) mod mod_;
|
||||||
|
|
||||||
use hir::{HasAttrs, HasSource, HirDisplay, ModPath, Mutability, ScopeDef, StructKind, Type};
|
use hir::{HasAttrs, HasSource, HirDisplay, ModPath, Mutability, ScopeDef, StructKind, Type};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
@ -7,20 +20,47 @@ use syntax::{ast::NameOwner, display::*};
|
||||||
use test_utils::mark;
|
use test_utils::mark;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
// display::{const_label, function_declaration, macro_label, type_label},
|
item::Builder, CompletionContext, CompletionItem, CompletionItemKind, CompletionKind,
|
||||||
CompletionScore,
|
CompletionScore, RootDatabase,
|
||||||
RootDatabase,
|
|
||||||
{
|
|
||||||
completion_item::Builder, CompletionContext, CompletionItem, CompletionItemKind,
|
|
||||||
CompletionKind, Completions,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Represents an in-progress set of completions being built.
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct Completions {
|
||||||
|
buf: Vec<CompletionItem>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<Vec<CompletionItem>> for Completions {
|
||||||
|
fn into(self) -> Vec<CompletionItem> {
|
||||||
|
self.buf
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Builder {
|
||||||
|
/// Convenience method, which allows to add a freshly created completion into accumulator
|
||||||
|
/// without binding it to the variable.
|
||||||
|
pub(crate) fn add_to(self, acc: &mut Completions) {
|
||||||
|
acc.add(self.build())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Completions {
|
impl Completions {
|
||||||
|
pub(crate) fn add(&mut self, item: CompletionItem) {
|
||||||
|
self.buf.push(item.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn add_all<I>(&mut self, items: I)
|
||||||
|
where
|
||||||
|
I: IntoIterator,
|
||||||
|
I::Item: Into<CompletionItem>,
|
||||||
|
{
|
||||||
|
items.into_iter().for_each(|item| self.add(item.into()))
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn add_field(&mut self, ctx: &CompletionContext, field: hir::Field, ty: &Type) {
|
pub(crate) fn add_field(&mut self, ctx: &CompletionContext, field: hir::Field, ty: &Type) {
|
||||||
let is_deprecated = is_deprecated(field, ctx.db);
|
let is_deprecated = is_deprecated(field, ctx.db);
|
||||||
let name = field.name(ctx.db);
|
let name = field.name(ctx.db);
|
||||||
let mut completion_item =
|
let mut item =
|
||||||
CompletionItem::new(CompletionKind::Reference, ctx.source_range(), name.to_string())
|
CompletionItem::new(CompletionKind::Reference, ctx.source_range(), name.to_string())
|
||||||
.kind(CompletionItemKind::Field)
|
.kind(CompletionItemKind::Field)
|
||||||
.detail(ty.display(ctx.db).to_string())
|
.detail(ty.display(ctx.db).to_string())
|
||||||
|
@ -28,10 +68,10 @@ impl Completions {
|
||||||
.set_deprecated(is_deprecated);
|
.set_deprecated(is_deprecated);
|
||||||
|
|
||||||
if let Some(score) = compute_score(ctx, &ty, &name.to_string()) {
|
if let Some(score) = compute_score(ctx, &ty, &name.to_string()) {
|
||||||
completion_item = completion_item.set_score(score);
|
item = item.set_score(score);
|
||||||
}
|
}
|
||||||
|
|
||||||
completion_item.add_to(self);
|
item.add_to(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn add_tuple_field(&mut self, ctx: &CompletionContext, field: usize, ty: &Type) {
|
pub(crate) fn add_tuple_field(&mut self, ctx: &CompletionContext, field: usize, ty: &Type) {
|
||||||
|
@ -57,7 +97,8 @@ impl Completions {
|
||||||
let kind = match resolution {
|
let kind = match resolution {
|
||||||
ScopeDef::ModuleDef(Module(..)) => CompletionItemKind::Module,
|
ScopeDef::ModuleDef(Module(..)) => CompletionItemKind::Module,
|
||||||
ScopeDef::ModuleDef(Function(func)) => {
|
ScopeDef::ModuleDef(Function(func)) => {
|
||||||
return self.add_function(ctx, *func, Some(local_name));
|
self.add_function(ctx, *func, Some(local_name));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
ScopeDef::ModuleDef(Adt(hir::Adt::Struct(_))) => CompletionItemKind::Struct,
|
ScopeDef::ModuleDef(Adt(hir::Adt::Struct(_))) => CompletionItemKind::Struct,
|
||||||
// FIXME: add CompletionItemKind::Union
|
// FIXME: add CompletionItemKind::Union
|
||||||
|
@ -65,7 +106,8 @@ impl Completions {
|
||||||
ScopeDef::ModuleDef(Adt(hir::Adt::Enum(_))) => CompletionItemKind::Enum,
|
ScopeDef::ModuleDef(Adt(hir::Adt::Enum(_))) => CompletionItemKind::Enum,
|
||||||
|
|
||||||
ScopeDef::ModuleDef(EnumVariant(var)) => {
|
ScopeDef::ModuleDef(EnumVariant(var)) => {
|
||||||
return self.add_enum_variant(ctx, *var, Some(local_name));
|
self.add_enum_variant(ctx, *var, Some(local_name));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
ScopeDef::ModuleDef(Const(..)) => CompletionItemKind::Const,
|
ScopeDef::ModuleDef(Const(..)) => CompletionItemKind::Const,
|
||||||
ScopeDef::ModuleDef(Static(..)) => CompletionItemKind::Static,
|
ScopeDef::ModuleDef(Static(..)) => CompletionItemKind::Static,
|
||||||
|
@ -77,13 +119,14 @@ impl Completions {
|
||||||
// (does this need its own kind?)
|
// (does this need its own kind?)
|
||||||
ScopeDef::AdtSelfType(..) | ScopeDef::ImplSelfType(..) => CompletionItemKind::TypeParam,
|
ScopeDef::AdtSelfType(..) | ScopeDef::ImplSelfType(..) => CompletionItemKind::TypeParam,
|
||||||
ScopeDef::MacroDef(mac) => {
|
ScopeDef::MacroDef(mac) => {
|
||||||
return self.add_macro(ctx, Some(local_name), *mac);
|
self.add_macro(ctx, Some(local_name), *mac);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
ScopeDef::Unknown => {
|
ScopeDef::Unknown => {
|
||||||
return self.add(
|
CompletionItem::new(CompletionKind::Reference, ctx.source_range(), local_name)
|
||||||
CompletionItem::new(CompletionKind::Reference, ctx.source_range(), local_name)
|
.kind(CompletionItemKind::UnresolvedReference)
|
||||||
.kind(CompletionItemKind::UnresolvedReference),
|
.add_to(self);
|
||||||
);
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -98,12 +141,11 @@ impl Completions {
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut completion_item =
|
let mut item = CompletionItem::new(completion_kind, ctx.source_range(), local_name.clone());
|
||||||
CompletionItem::new(completion_kind, ctx.source_range(), local_name.clone());
|
|
||||||
if let ScopeDef::Local(local) = resolution {
|
if let ScopeDef::Local(local) = resolution {
|
||||||
let ty = local.ty(ctx.db);
|
let ty = local.ty(ctx.db);
|
||||||
if !ty.is_unknown() {
|
if !ty.is_unknown() {
|
||||||
completion_item = completion_item.detail(ty.display(ctx.db).to_string());
|
item = item.detail(ty.display(ctx.db).to_string());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -114,7 +156,7 @@ impl Completions {
|
||||||
if let Some(score) =
|
if let Some(score) =
|
||||||
compute_score_from_active(&active_type, &active_name, &ty, &local_name)
|
compute_score_from_active(&active_type, &active_name, &ty, &local_name)
|
||||||
{
|
{
|
||||||
completion_item = completion_item.set_score(score);
|
item = item.set_score(score);
|
||||||
}
|
}
|
||||||
ref_match = refed_type_matches(&active_type, &active_name, &ty, &local_name);
|
ref_match = refed_type_matches(&active_type, &active_name, &ty, &local_name);
|
||||||
}
|
}
|
||||||
|
@ -130,7 +172,7 @@ impl Completions {
|
||||||
};
|
};
|
||||||
if has_non_default_type_params {
|
if has_non_default_type_params {
|
||||||
mark::hit!(inserts_angle_brackets_for_generics);
|
mark::hit!(inserts_angle_brackets_for_generics);
|
||||||
completion_item = completion_item
|
item = item
|
||||||
.lookup_by(local_name.clone())
|
.lookup_by(local_name.clone())
|
||||||
.label(format!("{}<…>", local_name))
|
.label(format!("{}<…>", local_name))
|
||||||
.insert_snippet(cap, format!("{}<$0>", local_name));
|
.insert_snippet(cap, format!("{}<$0>", local_name));
|
||||||
|
@ -138,7 +180,7 @@ impl Completions {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
completion_item.kind(kind).set_documentation(docs).set_ref_match(ref_match).add_to(self)
|
item.kind(kind).set_documentation(docs).set_ref_match(ref_match).add_to(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn add_macro(
|
pub(crate) fn add_macro(
|
||||||
|
@ -190,7 +232,7 @@ impl Completions {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
self.add(builder);
|
self.add(builder.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn add_function(
|
pub(crate) fn add_function(
|
||||||
|
@ -242,7 +284,7 @@ impl Completions {
|
||||||
|
|
||||||
builder = builder.add_call_parens(ctx, name, Params::Named(params));
|
builder = builder.add_call_parens(ctx, name, Params::Named(params));
|
||||||
|
|
||||||
self.add(builder)
|
self.add(builder.build())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn add_const(&mut self, ctx: &CompletionContext, constant: hir::Const) {
|
pub(crate) fn add_const(&mut self, ctx: &CompletionContext, constant: hir::Const) {
|
||||||
|
@ -506,7 +548,7 @@ mod tests {
|
||||||
use test_utils::mark;
|
use test_utils::mark;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
test_utils::{check_edit, check_edit_with_config, do_completion, get_all_completion_items},
|
test_utils::{check_edit, check_edit_with_config, do_completion, get_all_items},
|
||||||
CompletionConfig, CompletionKind, CompletionScore,
|
CompletionConfig, CompletionKind, CompletionScore,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -524,7 +566,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut completions = get_all_completion_items(CompletionConfig::default(), ra_fixture);
|
let mut completions = get_all_items(CompletionConfig::default(), ra_fixture);
|
||||||
completions.sort_by_key(|it| (Reverse(it.score()), it.label().to_string()));
|
completions.sort_by_key(|it| (Reverse(it.score()), it.label().to_string()));
|
||||||
let actual = completions
|
let actual = completions
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
@ -661,7 +703,7 @@ fn main() { let _: m::Spam = S<|> }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn sets_deprecated_flag_in_completion_items() {
|
fn sets_deprecated_flag_in_items() {
|
||||||
check(
|
check(
|
||||||
r#"
|
r#"
|
||||||
#[deprecated]
|
#[deprecated]
|
|
@ -7,12 +7,13 @@ use rustc_hash::FxHashSet;
|
||||||
use syntax::{ast, AstNode, SyntaxKind};
|
use syntax::{ast, AstNode, SyntaxKind};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
completion_context::CompletionContext,
|
context::CompletionContext,
|
||||||
completion_item::{CompletionItem, CompletionItemKind, CompletionKind, Completions},
|
|
||||||
generated_lint_completions::{CLIPPY_LINTS, FEATURES},
|
generated_lint_completions::{CLIPPY_LINTS, FEATURES},
|
||||||
|
item::{CompletionItem, CompletionItemKind, CompletionKind},
|
||||||
|
Completions,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub(super) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
|
pub(crate) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
|
||||||
if ctx.mod_declaration_under_caret.is_some() {
|
if ctx.mod_declaration_under_caret.is_some() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
@ -60,7 +61,7 @@ fn complete_attribute_start(acc: &mut Completions, ctx: &CompletionContext, attr
|
||||||
}
|
}
|
||||||
|
|
||||||
if attribute.kind() == ast::AttrKind::Inner || !attr_completion.prefer_inner {
|
if attribute.kind() == ast::AttrKind::Inner || !attr_completion.prefer_inner {
|
||||||
acc.add(item);
|
acc.add(item.build());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -152,21 +153,15 @@ fn complete_derive(acc: &mut Completions, ctx: &CompletionContext, derive_input:
|
||||||
label.push_str(", ");
|
label.push_str(", ");
|
||||||
label.push_str(dependency);
|
label.push_str(dependency);
|
||||||
}
|
}
|
||||||
acc.add(
|
CompletionItem::new(CompletionKind::Attribute, ctx.source_range(), label)
|
||||||
CompletionItem::new(CompletionKind::Attribute, ctx.source_range(), label)
|
.kind(CompletionItemKind::Attribute)
|
||||||
.kind(CompletionItemKind::Attribute),
|
.add_to(acc)
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for custom_derive_name in get_derive_names_in_scope(ctx).difference(&existing_derives) {
|
for custom_derive_name in get_derive_names_in_scope(ctx).difference(&existing_derives) {
|
||||||
acc.add(
|
CompletionItem::new(CompletionKind::Attribute, ctx.source_range(), custom_derive_name)
|
||||||
CompletionItem::new(
|
.kind(CompletionItemKind::Attribute)
|
||||||
CompletionKind::Attribute,
|
.add_to(acc)
|
||||||
ctx.source_range(),
|
|
||||||
custom_derive_name,
|
|
||||||
)
|
|
||||||
.kind(CompletionItemKind::Attribute),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -182,15 +177,14 @@ fn complete_lint(
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|completion| !existing_lints.contains(completion.label))
|
.filter(|completion| !existing_lints.contains(completion.label))
|
||||||
{
|
{
|
||||||
acc.add(
|
CompletionItem::new(
|
||||||
CompletionItem::new(
|
CompletionKind::Attribute,
|
||||||
CompletionKind::Attribute,
|
ctx.source_range(),
|
||||||
ctx.source_range(),
|
lint_completion.label,
|
||||||
lint_completion.label,
|
)
|
||||||
)
|
.kind(CompletionItemKind::Attribute)
|
||||||
.kind(CompletionItemKind::Attribute)
|
.detail(lint_completion.description)
|
||||||
.detail(lint_completion.description),
|
.add_to(acc)
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -262,9 +256,9 @@ const DEFAULT_DERIVE_COMPLETIONS: &[DeriveCompletion] = &[
|
||||||
DeriveCompletion { label: "Ord", dependencies: &["PartialOrd", "Eq", "PartialEq"] },
|
DeriveCompletion { label: "Ord", dependencies: &["PartialOrd", "Eq", "PartialEq"] },
|
||||||
];
|
];
|
||||||
|
|
||||||
pub(super) struct LintCompletion {
|
pub(crate) struct LintCompletion {
|
||||||
pub(super) label: &'static str,
|
pub(crate) label: &'static str,
|
||||||
pub(super) description: &'static str,
|
pub(crate) description: &'static str,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
|
@ -4,10 +4,10 @@ use hir::{HasVisibility, Type};
|
||||||
use rustc_hash::FxHashSet;
|
use rustc_hash::FxHashSet;
|
||||||
use test_utils::mark;
|
use test_utils::mark;
|
||||||
|
|
||||||
use crate::{completion_context::CompletionContext, completion_item::Completions};
|
use crate::{context::CompletionContext, Completions};
|
||||||
|
|
||||||
/// Complete dot accesses, i.e. fields or methods.
|
/// Complete dot accesses, i.e. fields or methods.
|
||||||
pub(super) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext) {
|
pub(crate) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext) {
|
||||||
let dot_receiver = match &ctx.dot_receiver {
|
let dot_receiver = match &ctx.dot_receiver {
|
||||||
Some(expr) => expr,
|
Some(expr) => expr,
|
||||||
_ => return,
|
_ => return,
|
||||||
|
@ -141,7 +141,7 @@ mod inner {
|
||||||
private_field: u32,
|
private_field: u32,
|
||||||
pub pub_field: u32,
|
pub pub_field: u32,
|
||||||
pub(crate) crate_field: u32,
|
pub(crate) crate_field: u32,
|
||||||
pub(super) super_field: u32,
|
pub(crate) super_field: u32,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn foo(a: inner::A) { a.<|> }
|
fn foo(a: inner::A) { a.<|> }
|
||||||
|
@ -159,13 +159,13 @@ struct A {}
|
||||||
mod m {
|
mod m {
|
||||||
impl super::A {
|
impl super::A {
|
||||||
fn private_method(&self) {}
|
fn private_method(&self) {}
|
||||||
pub(super) fn the_method(&self) {}
|
pub(crate) fn the_method(&self) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn foo(a: A) { a.<|> }
|
fn foo(a: A) { a.<|> }
|
||||||
"#,
|
"#,
|
||||||
expect![[r#"
|
expect![[r#"
|
||||||
me the_method() pub(super) fn the_method(&self)
|
me the_method() pub(crate) fn the_method(&self)
|
||||||
"#]],
|
"#]],
|
||||||
);
|
);
|
||||||
}
|
}
|
|
@ -12,7 +12,7 @@ use crate::{CompletionContext, CompletionItem, CompletionKind, Completions};
|
||||||
/// functions in a file have a `spam: &mut Spam` parameter, a completion with
|
/// functions in a file have a `spam: &mut Spam` parameter, a completion with
|
||||||
/// `spam: &mut Spam` insert text/label and `spam` lookup string will be
|
/// `spam: &mut Spam` insert text/label and `spam` lookup string will be
|
||||||
/// suggested.
|
/// suggested.
|
||||||
pub(super) fn complete_fn_param(acc: &mut Completions, ctx: &CompletionContext) {
|
pub(crate) fn complete_fn_param(acc: &mut Completions, ctx: &CompletionContext) {
|
||||||
if !ctx.is_param {
|
if !ctx.is_param {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
|
@ -5,7 +5,7 @@ use test_utils::mark;
|
||||||
|
|
||||||
use crate::{CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, Completions};
|
use crate::{CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, Completions};
|
||||||
|
|
||||||
pub(super) fn complete_use_tree_keyword(acc: &mut Completions, ctx: &CompletionContext) {
|
pub(crate) fn complete_use_tree_keyword(acc: &mut Completions, ctx: &CompletionContext) {
|
||||||
// complete keyword "crate" in use stmt
|
// complete keyword "crate" in use stmt
|
||||||
let source_range = ctx.source_range();
|
let source_range = ctx.source_range();
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@ pub(super) fn complete_use_tree_keyword(acc: &mut Completions, ctx: &CompletionC
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionContext) {
|
pub(crate) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionContext) {
|
||||||
if ctx.token.kind() == SyntaxKind::COMMENT {
|
if ctx.token.kind() == SyntaxKind::COMMENT {
|
||||||
mark::hit!(no_keyword_completion_in_comments);
|
mark::hit!(no_keyword_completion_in_comments);
|
||||||
return;
|
return;
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
use crate::{CompletionContext, Completions};
|
use crate::{CompletionContext, Completions};
|
||||||
|
|
||||||
pub(super) fn complete_macro_in_item_position(acc: &mut Completions, ctx: &CompletionContext) {
|
pub(crate) fn complete_macro_in_item_position(acc: &mut Completions, ctx: &CompletionContext) {
|
||||||
// Show only macros in top level.
|
// Show only macros in top level.
|
||||||
if ctx.is_new_item {
|
if ctx.is_new_item {
|
||||||
ctx.scope.process_all_names(&mut |name, res| {
|
ctx.scope.process_all_names(&mut |name, res| {
|
|
@ -7,13 +7,10 @@ use rustc_hash::FxHashSet;
|
||||||
|
|
||||||
use crate::{CompletionItem, CompletionItemKind};
|
use crate::{CompletionItem, CompletionItemKind};
|
||||||
|
|
||||||
use super::{
|
use crate::{context::CompletionContext, item::CompletionKind, Completions};
|
||||||
completion_context::CompletionContext, completion_item::CompletionKind,
|
|
||||||
completion_item::Completions,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Complete mod declaration, i.e. `mod <|> ;`
|
/// Complete mod declaration, i.e. `mod <|> ;`
|
||||||
pub(super) fn complete_mod(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
|
pub(crate) fn complete_mod(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
|
||||||
let mod_under_caret = match &ctx.mod_declaration_under_caret {
|
let mod_under_caret = match &ctx.mod_declaration_under_caret {
|
||||||
Some(mod_under_caret) if mod_under_caret.item_list().is_some() => return None,
|
Some(mod_under_caret) if mod_under_caret.item_list().is_some() => return None,
|
||||||
Some(mod_under_caret) => mod_under_caret,
|
Some(mod_under_caret) => mod_under_caret,
|
||||||
|
@ -78,10 +75,9 @@ pub(super) fn complete_mod(acc: &mut Completions, ctx: &CompletionContext) -> Op
|
||||||
if mod_under_caret.semicolon_token().is_none() {
|
if mod_under_caret.semicolon_token().is_none() {
|
||||||
label.push(';')
|
label.push(';')
|
||||||
}
|
}
|
||||||
acc.add(
|
CompletionItem::new(CompletionKind::Magic, ctx.source_range(), &label)
|
||||||
CompletionItem::new(CompletionKind::Magic, ctx.source_range(), &label)
|
.kind(CompletionItemKind::Module)
|
||||||
.kind(CompletionItemKind::Module),
|
.add_to(acc)
|
||||||
)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Some(())
|
Some(())
|
|
@ -3,7 +3,7 @@
|
||||||
use crate::{CompletionContext, Completions};
|
use crate::{CompletionContext, Completions};
|
||||||
|
|
||||||
/// Completes constats and paths in patterns.
|
/// Completes constats and paths in patterns.
|
||||||
pub(super) fn complete_pattern(acc: &mut Completions, ctx: &CompletionContext) {
|
pub(crate) fn complete_pattern(acc: &mut Completions, ctx: &CompletionContext) {
|
||||||
if !ctx.is_pat_binding_or_const {
|
if !ctx.is_pat_binding_or_const {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
|
@ -11,13 +11,13 @@ use text_edit::TextEdit;
|
||||||
|
|
||||||
use self::format_like::add_format_like_completions;
|
use self::format_like::add_format_like_completions;
|
||||||
use crate::{
|
use crate::{
|
||||||
completion_config::SnippetCap,
|
config::SnippetCap,
|
||||||
completion_context::CompletionContext,
|
context::CompletionContext,
|
||||||
completion_item::{Builder, CompletionKind, Completions},
|
item::{Builder, CompletionKind},
|
||||||
CompletionItem, CompletionItemKind,
|
CompletionItem, CompletionItemKind, Completions,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) {
|
pub(crate) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) {
|
||||||
if !ctx.config.enable_postfix_completions {
|
if !ctx.config.enable_postfix_completions {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
|
@ -15,8 +15,8 @@
|
||||||
// + `loge` -> `log::error!(...)`
|
// + `loge` -> `log::error!(...)`
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
complete_postfix::postfix_snippet, completion_config::SnippetCap,
|
completions::postfix::postfix_snippet, config::SnippetCap, context::CompletionContext,
|
||||||
completion_context::CompletionContext, completion_item::Completions,
|
Completions,
|
||||||
};
|
};
|
||||||
use syntax::ast::{self, AstToken};
|
use syntax::ast::{self, AstToken};
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ static KINDS: &[(&str, &str)] = &[
|
||||||
("loge", "log::error!"),
|
("loge", "log::error!"),
|
||||||
];
|
];
|
||||||
|
|
||||||
pub(super) fn add_format_like_completions(
|
pub(crate) fn add_format_like_completions(
|
||||||
acc: &mut Completions,
|
acc: &mut Completions,
|
||||||
ctx: &CompletionContext,
|
ctx: &CompletionContext,
|
||||||
dot_receiver: &ast::Expr,
|
dot_receiver: &ast::Expr,
|
||||||
|
@ -70,7 +70,7 @@ fn string_literal_contents(item: &ast::String) -> Option<String> {
|
||||||
/// Parser for a format-like string. It is more allowing in terms of string contents,
|
/// Parser for a format-like string. It is more allowing in terms of string contents,
|
||||||
/// as we expect variable placeholders to be filled with expressions.
|
/// as we expect variable placeholders to be filled with expressions.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct FormatStrParser {
|
pub(crate) struct FormatStrParser {
|
||||||
input: String,
|
input: String,
|
||||||
output: String,
|
output: String,
|
||||||
extracted_expressions: Vec<String>,
|
extracted_expressions: Vec<String>,
|
|
@ -7,7 +7,7 @@ use test_utils::mark;
|
||||||
|
|
||||||
use crate::{CompletionContext, Completions};
|
use crate::{CompletionContext, Completions};
|
||||||
|
|
||||||
pub(super) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionContext) {
|
pub(crate) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionContext) {
|
||||||
let path = match &ctx.path_qual {
|
let path = match &ctx.path_qual {
|
||||||
Some(path) => path.clone(),
|
Some(path) => path.clone(),
|
||||||
None => return,
|
None => return,
|
||||||
|
@ -369,11 +369,11 @@ struct S;
|
||||||
|
|
||||||
mod m {
|
mod m {
|
||||||
impl super::S {
|
impl super::S {
|
||||||
pub(super) fn public_method() { }
|
pub(crate) fn public_method() { }
|
||||||
fn private_method() { }
|
fn private_method() { }
|
||||||
pub(super) type PublicType = u32;
|
pub(crate) type PublicType = u32;
|
||||||
type PrivateType = u32;
|
type PrivateType = u32;
|
||||||
pub(super) const PUBLIC_CONST: u32 = 1;
|
pub(crate) const PUBLIC_CONST: u32 = 1;
|
||||||
const PRIVATE_CONST: u32 = 1;
|
const PRIVATE_CONST: u32 = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -381,9 +381,9 @@ mod m {
|
||||||
fn foo() { let _ = S::<|> }
|
fn foo() { let _ = S::<|> }
|
||||||
"#,
|
"#,
|
||||||
expect![[r#"
|
expect![[r#"
|
||||||
ct PUBLIC_CONST pub(super) const PUBLIC_CONST: u32 = 1;
|
ct PUBLIC_CONST pub(crate) const PUBLIC_CONST: u32 = 1;
|
||||||
ta PublicType pub(super) type PublicType = u32;
|
ta PublicType pub(crate) type PublicType = u32;
|
||||||
fn public_method() pub(super) fn public_method()
|
fn public_method() pub(crate) fn public_method()
|
||||||
"#]],
|
"#]],
|
||||||
);
|
);
|
||||||
}
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
//! Complete fields in record literals and patterns.
|
//! Complete fields in record literals and patterns.
|
||||||
use crate::{CompletionContext, Completions};
|
use crate::{CompletionContext, Completions};
|
||||||
|
|
||||||
pub(super) fn complete_record(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
|
pub(crate) fn complete_record(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
|
||||||
let missing_fields = match (ctx.record_pat_syntax.as_ref(), ctx.record_lit_syntax.as_ref()) {
|
let missing_fields = match (ctx.record_pat_syntax.as_ref(), ctx.record_lit_syntax.as_ref()) {
|
||||||
(None, None) => return None,
|
(None, None) => return None,
|
||||||
(Some(_), Some(_)) => unreachable!("A record cannot be both a literal and a pattern"),
|
(Some(_), Some(_)) => unreachable!("A record cannot be both a literal and a pattern"),
|
|
@ -1,8 +1,8 @@
|
||||||
//! This file provides snippet completions, like `pd` => `eprintln!(...)`.
|
//! This file provides snippet completions, like `pd` => `eprintln!(...)`.
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
completion_config::SnippetCap, completion_item::Builder, CompletionContext, CompletionItem,
|
config::SnippetCap, item::Builder, CompletionContext, CompletionItem, CompletionItemKind,
|
||||||
CompletionItemKind, CompletionKind, Completions,
|
CompletionKind, Completions,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn snippet(ctx: &CompletionContext, cap: SnippetCap, label: &str, snippet: &str) -> Builder {
|
fn snippet(ctx: &CompletionContext, cap: SnippetCap, label: &str, snippet: &str) -> Builder {
|
||||||
|
@ -11,7 +11,7 @@ fn snippet(ctx: &CompletionContext, cap: SnippetCap, label: &str, snippet: &str)
|
||||||
.kind(CompletionItemKind::Snippet)
|
.kind(CompletionItemKind::Snippet)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn complete_expr_snippet(acc: &mut Completions, ctx: &CompletionContext) {
|
pub(crate) fn complete_expr_snippet(acc: &mut Completions, ctx: &CompletionContext) {
|
||||||
if !(ctx.is_trivial_path && ctx.function_syntax.is_some()) {
|
if !(ctx.is_trivial_path && ctx.function_syntax.is_some()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@ pub(super) fn complete_expr_snippet(acc: &mut Completions, ctx: &CompletionConte
|
||||||
snippet(ctx, cap, "ppd", "eprintln!(\"$0 = {:#?}\", $0);").add_to(acc);
|
snippet(ctx, cap, "ppd", "eprintln!(\"$0 = {:#?}\", $0);").add_to(acc);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn complete_item_snippet(acc: &mut Completions, ctx: &CompletionContext) {
|
pub(crate) fn complete_item_snippet(acc: &mut Completions, ctx: &CompletionContext) {
|
||||||
if !ctx.is_new_item {
|
if !ctx.is_new_item {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
|
@ -6,7 +6,7 @@ use test_utils::mark;
|
||||||
|
|
||||||
use crate::{CompletionContext, Completions};
|
use crate::{CompletionContext, Completions};
|
||||||
|
|
||||||
pub(super) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionContext) {
|
pub(crate) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionContext) {
|
||||||
if !(ctx.is_trivial_path || ctx.is_pat_binding_or_const) {
|
if !(ctx.is_trivial_path || ctx.is_pat_binding_or_const) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
File diff suppressed because one or more lines are too long
|
@ -6,7 +6,7 @@ use hir::{Documentation, Mutability};
|
||||||
use syntax::TextRange;
|
use syntax::TextRange;
|
||||||
use text_edit::TextEdit;
|
use text_edit::TextEdit;
|
||||||
|
|
||||||
use crate::completion_config::SnippetCap;
|
use crate::config::SnippetCap;
|
||||||
|
|
||||||
/// `CompletionItem` describes a single completion variant in the editor pop-up.
|
/// `CompletionItem` describes a single completion variant in the editor pop-up.
|
||||||
/// It is basically a POD with various properties. To construct a
|
/// It is basically a POD with various properties. To construct a
|
||||||
|
@ -272,10 +272,6 @@ pub(crate) struct Builder {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Builder {
|
impl Builder {
|
||||||
pub(crate) fn add_to(self, acc: &mut Completions) {
|
|
||||||
acc.add(self.build())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn build(self) -> CompletionItem {
|
pub(crate) fn build(self) -> CompletionItem {
|
||||||
let label = self.label;
|
let label = self.label;
|
||||||
let text_edit = match self.text_edit {
|
let text_edit = match self.text_edit {
|
||||||
|
@ -376,28 +372,3 @@ impl<'a> Into<CompletionItem> for Builder {
|
||||||
self.build()
|
self.build()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents an in-progress set of completions being built.
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
pub struct Completions {
|
|
||||||
buf: Vec<CompletionItem>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Completions {
|
|
||||||
pub fn add(&mut self, item: impl Into<CompletionItem>) {
|
|
||||||
self.buf.push(item.into())
|
|
||||||
}
|
|
||||||
pub fn add_all<I>(&mut self, items: I)
|
|
||||||
where
|
|
||||||
I: IntoIterator,
|
|
||||||
I::Item: Into<CompletionItem>,
|
|
||||||
{
|
|
||||||
items.into_iter().for_each(|item| self.add(item.into()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Into<Vec<CompletionItem>> for Completions {
|
|
||||||
fn into(self) -> Vec<CompletionItem> {
|
|
||||||
self.buf
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,39 +1,23 @@
|
||||||
//! `completions` crate provides utilities for generating completions of user input.
|
//! `completions` crate provides utilities for generating completions of user input.
|
||||||
|
|
||||||
mod completion_config;
|
mod config;
|
||||||
mod completion_item;
|
mod item;
|
||||||
mod completion_context;
|
mod context;
|
||||||
mod presentation;
|
|
||||||
mod patterns;
|
mod patterns;
|
||||||
mod generated_lint_completions;
|
mod generated_lint_completions;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test_utils;
|
mod test_utils;
|
||||||
|
|
||||||
mod complete_attribute;
|
mod completions;
|
||||||
mod complete_dot;
|
|
||||||
mod complete_record;
|
|
||||||
mod complete_pattern;
|
|
||||||
mod complete_fn_param;
|
|
||||||
mod complete_keyword;
|
|
||||||
mod complete_snippet;
|
|
||||||
mod complete_qualified_path;
|
|
||||||
mod complete_unqualified_path;
|
|
||||||
mod complete_postfix;
|
|
||||||
mod complete_macro_in_item_position;
|
|
||||||
mod complete_trait_impl;
|
|
||||||
mod complete_mod;
|
|
||||||
|
|
||||||
use ide_db::base_db::FilePosition;
|
use ide_db::base_db::FilePosition;
|
||||||
use ide_db::RootDatabase;
|
use ide_db::RootDatabase;
|
||||||
|
|
||||||
use crate::{
|
use crate::{completions::Completions, context::CompletionContext, item::CompletionKind};
|
||||||
completion_context::CompletionContext,
|
|
||||||
completion_item::{CompletionKind, Completions},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub use crate::{
|
pub use crate::{
|
||||||
completion_config::CompletionConfig,
|
config::CompletionConfig,
|
||||||
completion_item::{CompletionItem, CompletionItemKind, CompletionScore, InsertTextFormat},
|
item::{CompletionItem, CompletionItemKind, CompletionScore, InsertTextFormat},
|
||||||
};
|
};
|
||||||
|
|
||||||
//FIXME: split the following feature into fine-grained features.
|
//FIXME: split the following feature into fine-grained features.
|
||||||
|
@ -118,28 +102,28 @@ pub fn completions(
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut acc = Completions::default();
|
let mut acc = Completions::default();
|
||||||
complete_attribute::complete_attribute(&mut acc, &ctx);
|
completions::attribute::complete_attribute(&mut acc, &ctx);
|
||||||
complete_fn_param::complete_fn_param(&mut acc, &ctx);
|
completions::fn_param::complete_fn_param(&mut acc, &ctx);
|
||||||
complete_keyword::complete_expr_keyword(&mut acc, &ctx);
|
completions::keyword::complete_expr_keyword(&mut acc, &ctx);
|
||||||
complete_keyword::complete_use_tree_keyword(&mut acc, &ctx);
|
completions::keyword::complete_use_tree_keyword(&mut acc, &ctx);
|
||||||
complete_snippet::complete_expr_snippet(&mut acc, &ctx);
|
completions::snippet::complete_expr_snippet(&mut acc, &ctx);
|
||||||
complete_snippet::complete_item_snippet(&mut acc, &ctx);
|
completions::snippet::complete_item_snippet(&mut acc, &ctx);
|
||||||
complete_qualified_path::complete_qualified_path(&mut acc, &ctx);
|
completions::qualified_path::complete_qualified_path(&mut acc, &ctx);
|
||||||
complete_unqualified_path::complete_unqualified_path(&mut acc, &ctx);
|
completions::unqualified_path::complete_unqualified_path(&mut acc, &ctx);
|
||||||
complete_dot::complete_dot(&mut acc, &ctx);
|
completions::dot::complete_dot(&mut acc, &ctx);
|
||||||
complete_record::complete_record(&mut acc, &ctx);
|
completions::record::complete_record(&mut acc, &ctx);
|
||||||
complete_pattern::complete_pattern(&mut acc, &ctx);
|
completions::pattern::complete_pattern(&mut acc, &ctx);
|
||||||
complete_postfix::complete_postfix(&mut acc, &ctx);
|
completions::postfix::complete_postfix(&mut acc, &ctx);
|
||||||
complete_macro_in_item_position::complete_macro_in_item_position(&mut acc, &ctx);
|
completions::macro_in_item_position::complete_macro_in_item_position(&mut acc, &ctx);
|
||||||
complete_trait_impl::complete_trait_impl(&mut acc, &ctx);
|
completions::trait_impl::complete_trait_impl(&mut acc, &ctx);
|
||||||
complete_mod::complete_mod(&mut acc, &ctx);
|
completions::mod_::complete_mod(&mut acc, &ctx);
|
||||||
|
|
||||||
Some(acc)
|
Some(acc)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::completion_config::CompletionConfig;
|
use crate::config::CompletionConfig;
|
||||||
use crate::test_utils;
|
use crate::test_utils;
|
||||||
|
|
||||||
struct DetailAndDocumentation<'a> {
|
struct DetailAndDocumentation<'a> {
|
||||||
|
|
|
@ -8,7 +8,7 @@ use stdx::{format_to, trim_indent};
|
||||||
use syntax::{AstNode, NodeOrToken, SyntaxElement};
|
use syntax::{AstNode, NodeOrToken, SyntaxElement};
|
||||||
use test_utils::{assert_eq_text, RangeOrOffset};
|
use test_utils::{assert_eq_text, RangeOrOffset};
|
||||||
|
|
||||||
use crate::{completion_item::CompletionKind, CompletionConfig, CompletionItem};
|
use crate::{item::CompletionKind, CompletionConfig, CompletionItem};
|
||||||
|
|
||||||
/// Creates analysis from a multi-file fixture, returns positions marked with <|>.
|
/// Creates analysis from a multi-file fixture, returns positions marked with <|>.
|
||||||
pub(crate) fn position(ra_fixture: &str) -> (RootDatabase, FilePosition) {
|
pub(crate) fn position(ra_fixture: &str) -> (RootDatabase, FilePosition) {
|
||||||
|
@ -32,10 +32,8 @@ pub(crate) fn do_completion_with_config(
|
||||||
code: &str,
|
code: &str,
|
||||||
kind: CompletionKind,
|
kind: CompletionKind,
|
||||||
) -> Vec<CompletionItem> {
|
) -> Vec<CompletionItem> {
|
||||||
let mut kind_completions: Vec<CompletionItem> = get_all_completion_items(config, code)
|
let mut kind_completions: Vec<CompletionItem> =
|
||||||
.into_iter()
|
get_all_items(config, code).into_iter().filter(|c| c.completion_kind == kind).collect();
|
||||||
.filter(|c| c.completion_kind == kind)
|
|
||||||
.collect();
|
|
||||||
kind_completions.sort_by(|l, r| l.label().cmp(r.label()));
|
kind_completions.sort_by(|l, r| l.label().cmp(r.label()));
|
||||||
kind_completions
|
kind_completions
|
||||||
}
|
}
|
||||||
|
@ -49,10 +47,8 @@ pub(crate) fn completion_list_with_config(
|
||||||
code: &str,
|
code: &str,
|
||||||
kind: CompletionKind,
|
kind: CompletionKind,
|
||||||
) -> String {
|
) -> String {
|
||||||
let mut kind_completions: Vec<CompletionItem> = get_all_completion_items(config, code)
|
let mut kind_completions: Vec<CompletionItem> =
|
||||||
.into_iter()
|
get_all_items(config, code).into_iter().filter(|c| c.completion_kind == kind).collect();
|
||||||
.filter(|c| c.completion_kind == kind)
|
|
||||||
.collect();
|
|
||||||
kind_completions.sort_by_key(|c| c.label().to_owned());
|
kind_completions.sort_by_key(|c| c.label().to_owned());
|
||||||
let label_width = kind_completions
|
let label_width = kind_completions
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -121,10 +117,7 @@ pub(crate) fn check_pattern_is_not_applicable(code: &str, check: fn(SyntaxElemen
|
||||||
assert!(!check(NodeOrToken::Token(token)));
|
assert!(!check(NodeOrToken::Token(token)));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get_all_completion_items(
|
pub(crate) fn get_all_items(config: CompletionConfig, code: &str) -> Vec<CompletionItem> {
|
||||||
config: CompletionConfig,
|
|
||||||
code: &str,
|
|
||||||
) -> Vec<CompletionItem> {
|
|
||||||
let (db, position) = position(code);
|
let (db, position) = position(code);
|
||||||
crate::completions(&db, &config, position).unwrap().into()
|
crate::completions(&db, &config, position).unwrap().into()
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ pub fn generate_lint_completions(mode: Mode) -> Result<()> {
|
||||||
|
|
||||||
let ts_clippy = generate_descriptor_clippy(&Path::new("./target/clippy_lints.json"))?;
|
let ts_clippy = generate_descriptor_clippy(&Path::new("./target/clippy_lints.json"))?;
|
||||||
let ts = quote! {
|
let ts = quote! {
|
||||||
use crate::complete_attribute::LintCompletion;
|
use crate::completions::attribute::LintCompletion;
|
||||||
#ts_features
|
#ts_features
|
||||||
#ts_clippy
|
#ts_clippy
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue