Add completions for patterns
This commit is contained in:
parent
f3125555a8
commit
b184bfad7a
@ -19,9 +19,14 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
item::Builder,
|
item::Builder,
|
||||||
render::{
|
render::{
|
||||||
const_::render_const, enum_variant::render_variant, function::render_fn,
|
const_::render_const,
|
||||||
macro_::render_macro, render_field, render_resolution, render_tuple_field,
|
enum_variant::render_variant,
|
||||||
type_alias::render_type_alias, RenderContext,
|
function::render_fn,
|
||||||
|
macro_::render_macro,
|
||||||
|
pattern::{render_struct_pat, render_variant_pat},
|
||||||
|
render_field, render_resolution, render_tuple_field,
|
||||||
|
type_alias::render_type_alias,
|
||||||
|
RenderContext,
|
||||||
},
|
},
|
||||||
CompletionContext, CompletionItem,
|
CompletionContext, CompletionItem,
|
||||||
};
|
};
|
||||||
@ -105,6 +110,28 @@ pub(crate) fn add_function(
|
|||||||
self.add(item)
|
self.add(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn add_variant_pat(
|
||||||
|
&mut self,
|
||||||
|
ctx: &CompletionContext,
|
||||||
|
variant: hir::Variant,
|
||||||
|
local_name: Option<hir::Name>,
|
||||||
|
) {
|
||||||
|
if let Some(item) = render_variant_pat(RenderContext::new(ctx), variant, local_name) {
|
||||||
|
self.add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn add_struct_pat(
|
||||||
|
&mut self,
|
||||||
|
ctx: &CompletionContext,
|
||||||
|
strukt: hir::Struct,
|
||||||
|
local_name: Option<hir::Name>,
|
||||||
|
) {
|
||||||
|
if let Some(item) = render_struct_pat(RenderContext::new(ctx), strukt, local_name) {
|
||||||
|
self.add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn add_const(&mut self, ctx: &CompletionContext, constant: hir::Const) {
|
pub(crate) fn add_const(&mut self, ctx: &CompletionContext, constant: hir::Const) {
|
||||||
if let Some(item) = render_const(RenderContext::new(ctx), constant) {
|
if let Some(item) = render_const(RenderContext::new(ctx), constant) {
|
||||||
self.add(item);
|
self.add(item);
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
//! Completes constats and paths in patterns.
|
//! Completes constats and paths in patterns.
|
||||||
|
|
||||||
|
use hir::StructKind;
|
||||||
|
|
||||||
use crate::{CompletionContext, Completions};
|
use crate::{CompletionContext, Completions};
|
||||||
|
|
||||||
/// Completes constats and paths in patterns.
|
/// Completes constants and paths in patterns.
|
||||||
pub(crate) 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 || ctx.is_irrefutable_let_pat_binding) {
|
if !(ctx.is_pat_binding_or_const || ctx.is_irrefutable_pat_binding) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ctx.record_pat_syntax.is_some() {
|
if ctx.record_pat_syntax.is_some() {
|
||||||
@ -15,20 +17,25 @@ pub(crate) fn complete_pattern(acc: &mut Completions, ctx: &CompletionContext) {
|
|||||||
// suggest variants + auto-imports
|
// suggest variants + auto-imports
|
||||||
ctx.scope.process_all_names(&mut |name, res| {
|
ctx.scope.process_all_names(&mut |name, res| {
|
||||||
let add_resolution = match &res {
|
let add_resolution = match &res {
|
||||||
hir::ScopeDef::ModuleDef(def) => {
|
hir::ScopeDef::ModuleDef(def) => match def {
|
||||||
if ctx.is_irrefutable_let_pat_binding {
|
hir::ModuleDef::Adt(hir::Adt::Struct(strukt)) => {
|
||||||
matches!(def, hir::ModuleDef::Adt(hir::Adt::Struct(_)))
|
acc.add_struct_pat(ctx, strukt.clone(), Some(name.clone()));
|
||||||
} else {
|
true
|
||||||
matches!(
|
|
||||||
def,
|
|
||||||
hir::ModuleDef::Adt(hir::Adt::Enum(..))
|
|
||||||
| hir::ModuleDef::Adt(hir::Adt::Struct(..))
|
|
||||||
| hir::ModuleDef::Variant(..)
|
|
||||||
| hir::ModuleDef::Const(..)
|
|
||||||
| hir::ModuleDef::Module(..)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
hir::ModuleDef::Variant(variant)
|
||||||
|
if !ctx.is_irrefutable_pat_binding
|
||||||
|
// render_resolution already does some pattern completion tricks for tuple variants
|
||||||
|
&& variant.kind(ctx.db) == StructKind::Record =>
|
||||||
|
{
|
||||||
|
acc.add_variant_pat(ctx, variant.clone(), Some(name.clone()));
|
||||||
|
true
|
||||||
|
}
|
||||||
|
hir::ModuleDef::Adt(hir::Adt::Enum(..))
|
||||||
|
| hir::ModuleDef::Variant(..)
|
||||||
|
| hir::ModuleDef::Const(..)
|
||||||
|
| hir::ModuleDef::Module(..) => !ctx.is_irrefutable_pat_binding,
|
||||||
|
_ => false,
|
||||||
|
},
|
||||||
hir::ScopeDef::MacroDef(_) => true,
|
hir::ScopeDef::MacroDef(_) => true,
|
||||||
_ => false,
|
_ => false,
|
||||||
};
|
};
|
||||||
@ -49,6 +56,11 @@ fn check(ra_fixture: &str, expect: Expect) {
|
|||||||
expect.assert_eq(&actual)
|
expect.assert_eq(&actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn check_snippet(ra_fixture: &str, expect: Expect) {
|
||||||
|
let actual = completion_list(ra_fixture, CompletionKind::Snippet);
|
||||||
|
expect.assert_eq(&actual)
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn completes_enum_variants_and_modules() {
|
fn completes_enum_variants_and_modules() {
|
||||||
check(
|
check(
|
||||||
@ -114,4 +126,116 @@ fn foo() {
|
|||||||
"#]],
|
"#]],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn completes_in_param() {
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
enum E { X }
|
||||||
|
|
||||||
|
static FOO: E = E::X;
|
||||||
|
struct Bar { f: u32 }
|
||||||
|
|
||||||
|
fn foo(<|>) {
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
st Bar
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn completes_pat_in_let() {
|
||||||
|
check_snippet(
|
||||||
|
r#"
|
||||||
|
struct Bar { f: u32 }
|
||||||
|
|
||||||
|
fn foo() {
|
||||||
|
let <|>
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
bn Bar Bar { f }$0
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn completes_param_pattern() {
|
||||||
|
check_snippet(
|
||||||
|
r#"
|
||||||
|
struct Foo { bar: String, baz: String }
|
||||||
|
struct Bar(String, String);
|
||||||
|
struct Baz;
|
||||||
|
fn outer(<|>) {}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
bn Foo Foo { bar, baz }: Foo$0
|
||||||
|
bn Bar Bar($1, $2): Bar$0
|
||||||
|
"#]],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn completes_let_pattern() {
|
||||||
|
check_snippet(
|
||||||
|
r#"
|
||||||
|
struct Foo { bar: String, baz: String }
|
||||||
|
struct Bar(String, String);
|
||||||
|
struct Baz;
|
||||||
|
fn outer() {
|
||||||
|
let <|>
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
bn Foo Foo { bar, baz }$0
|
||||||
|
bn Bar Bar($1, $2)$0
|
||||||
|
"#]],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn completes_refutable_pattern() {
|
||||||
|
check_snippet(
|
||||||
|
r#"
|
||||||
|
struct Foo { bar: i32, baz: i32 }
|
||||||
|
struct Bar(String, String);
|
||||||
|
struct Baz;
|
||||||
|
fn outer() {
|
||||||
|
match () {
|
||||||
|
<|>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
bn Foo Foo { bar, baz }$0
|
||||||
|
bn Bar Bar($1, $2)$0
|
||||||
|
"#]],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn omits_private_fields_pat() {
|
||||||
|
check_snippet(
|
||||||
|
r#"
|
||||||
|
mod foo {
|
||||||
|
pub struct Foo { pub bar: i32, baz: i32 }
|
||||||
|
pub struct Bar(pub String, String);
|
||||||
|
pub struct Invisible(String, String);
|
||||||
|
}
|
||||||
|
use foo::*;
|
||||||
|
|
||||||
|
fn outer() {
|
||||||
|
match () {
|
||||||
|
<|>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
bn Foo Foo { bar, .. }$0
|
||||||
|
bn Bar Bar($1, ..)$0
|
||||||
|
"#]],
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,7 @@ pub(crate) struct CompletionContext<'a> {
|
|||||||
/// If a name-binding or reference to a const in a pattern.
|
/// If a name-binding or reference to a const in a pattern.
|
||||||
/// Irrefutable patterns (like let) are excluded.
|
/// Irrefutable patterns (like let) are excluded.
|
||||||
pub(super) is_pat_binding_or_const: bool,
|
pub(super) is_pat_binding_or_const: bool,
|
||||||
pub(super) is_irrefutable_let_pat_binding: bool,
|
pub(super) is_irrefutable_pat_binding: bool,
|
||||||
/// A single-indent path, like `foo`. `::foo` should not be considered a trivial path.
|
/// A single-indent path, like `foo`. `::foo` should not be considered a trivial path.
|
||||||
pub(super) is_trivial_path: bool,
|
pub(super) is_trivial_path: bool,
|
||||||
/// If not a trivial path, the prefix (qualifier).
|
/// If not a trivial path, the prefix (qualifier).
|
||||||
@ -147,7 +147,7 @@ pub(super) fn new(
|
|||||||
active_parameter: ActiveParameter::at(db, position),
|
active_parameter: ActiveParameter::at(db, position),
|
||||||
is_param: false,
|
is_param: false,
|
||||||
is_pat_binding_or_const: false,
|
is_pat_binding_or_const: false,
|
||||||
is_irrefutable_let_pat_binding: false,
|
is_irrefutable_pat_binding: false,
|
||||||
is_trivial_path: false,
|
is_trivial_path: false,
|
||||||
path_qual: None,
|
path_qual: None,
|
||||||
after_if: false,
|
after_if: false,
|
||||||
@ -327,14 +327,19 @@ fn fill(
|
|||||||
if bind_pat.syntax().parent().and_then(ast::RecordPatFieldList::cast).is_some() {
|
if bind_pat.syntax().parent().and_then(ast::RecordPatFieldList::cast).is_some() {
|
||||||
self.is_pat_binding_or_const = false;
|
self.is_pat_binding_or_const = false;
|
||||||
}
|
}
|
||||||
if let Some(let_stmt) = bind_pat.syntax().ancestors().find_map(ast::LetStmt::cast) {
|
if let Some(Some(pat)) = bind_pat.syntax().ancestors().find_map(|node| {
|
||||||
if let Some(pat) = let_stmt.pat() {
|
match_ast! {
|
||||||
if pat.syntax().text_range().contains_range(bind_pat.syntax().text_range())
|
match node {
|
||||||
{
|
ast::LetStmt(it) => Some(it.pat()),
|
||||||
self.is_pat_binding_or_const = false;
|
ast::Param(it) => Some(it.pat()),
|
||||||
self.is_irrefutable_let_pat_binding = true;
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}) {
|
||||||
|
if pat.syntax().text_range().contains_range(bind_pat.syntax().text_range()) {
|
||||||
|
self.is_pat_binding_or_const = false;
|
||||||
|
self.is_irrefutable_pat_binding = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if is_node::<ast::Param>(name.syntax()) {
|
if is_node::<ast::Param>(name.syntax()) {
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
pub(crate) mod function;
|
pub(crate) mod function;
|
||||||
pub(crate) mod enum_variant;
|
pub(crate) mod enum_variant;
|
||||||
pub(crate) mod const_;
|
pub(crate) mod const_;
|
||||||
|
pub(crate) mod pattern;
|
||||||
pub(crate) mod type_alias;
|
pub(crate) mod type_alias;
|
||||||
|
|
||||||
mod builder_ext;
|
mod builder_ext;
|
||||||
|
128
crates/completion/src/render/pattern.rs
Normal file
128
crates/completion/src/render/pattern.rs
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
//! Renderer for patterns.
|
||||||
|
|
||||||
|
use hir::{db::HirDatabase, HasVisibility, Name, StructKind};
|
||||||
|
use itertools::Itertools;
|
||||||
|
|
||||||
|
use crate::{item::CompletionKind, render::RenderContext, CompletionItem, CompletionItemKind};
|
||||||
|
|
||||||
|
pub(crate) fn render_struct_pat<'a>(
|
||||||
|
ctx: RenderContext<'a>,
|
||||||
|
strukt: hir::Struct,
|
||||||
|
local_name: Option<Name>,
|
||||||
|
) -> Option<CompletionItem> {
|
||||||
|
let _p = profile::span("render_struct_pat");
|
||||||
|
|
||||||
|
let module = ctx.completion.scope.module()?;
|
||||||
|
let fields = strukt.fields(ctx.db());
|
||||||
|
let n_fields = fields.len();
|
||||||
|
let fields = fields
|
||||||
|
.into_iter()
|
||||||
|
.filter(|field| field.is_visible_from(ctx.db(), module))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
if fields.is_empty() {
|
||||||
|
// Matching a struct without matching its fields is pointless, unlike matching a Variant without its fields
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let fields_omitted = n_fields - fields.len() > 0;
|
||||||
|
|
||||||
|
let name = local_name.unwrap_or_else(|| strukt.name(ctx.db())).to_string();
|
||||||
|
let mut pat = match strukt.kind(ctx.db()) {
|
||||||
|
StructKind::Tuple if ctx.snippet_cap().is_some() => {
|
||||||
|
render_tuple_as_pat(&fields, &name, fields_omitted)
|
||||||
|
}
|
||||||
|
StructKind::Record => render_record_as_pat(ctx.db(), &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");
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut completion = CompletionItem::new(CompletionKind::Snippet, ctx.source_range(), name)
|
||||||
|
.kind(CompletionItemKind::Binding)
|
||||||
|
.set_documentation(ctx.docs(strukt))
|
||||||
|
.set_deprecated(ctx.is_deprecated(strukt))
|
||||||
|
.detail(&pat);
|
||||||
|
if let Some(snippet_cap) = ctx.snippet_cap() {
|
||||||
|
completion = completion.insert_snippet(snippet_cap, pat);
|
||||||
|
} else {
|
||||||
|
completion = completion.insert_text(pat);
|
||||||
|
}
|
||||||
|
Some(completion.build())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn render_variant_pat<'a>(
|
||||||
|
ctx: RenderContext<'a>,
|
||||||
|
variant: hir::Variant,
|
||||||
|
local_name: Option<Name>,
|
||||||
|
) -> Option<CompletionItem> {
|
||||||
|
let _p = profile::span("render_variant_pat");
|
||||||
|
|
||||||
|
let module = ctx.completion.scope.module()?;
|
||||||
|
let fields = variant.fields(ctx.db());
|
||||||
|
let n_fields = fields.len();
|
||||||
|
let fields = fields
|
||||||
|
.into_iter()
|
||||||
|
.filter(|field| field.is_visible_from(ctx.db(), module))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let fields_omitted = n_fields - fields.len() > 0;
|
||||||
|
|
||||||
|
let name = local_name.unwrap_or_else(|| variant.name(ctx.db())).to_string();
|
||||||
|
let mut pat = match variant.kind(ctx.db()) {
|
||||||
|
StructKind::Tuple if ctx.snippet_cap().is_some() => {
|
||||||
|
render_tuple_as_pat(&fields, &name, fields_omitted)
|
||||||
|
}
|
||||||
|
StructKind::Record => render_record_as_pat(ctx.db(), &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");
|
||||||
|
}
|
||||||
|
let mut completion = CompletionItem::new(CompletionKind::Snippet, ctx.source_range(), name)
|
||||||
|
.kind(CompletionItemKind::Binding)
|
||||||
|
.set_documentation(ctx.docs(variant))
|
||||||
|
.set_deprecated(ctx.is_deprecated(variant))
|
||||||
|
.detail(&pat);
|
||||||
|
if let Some(snippet_cap) = ctx.snippet_cap() {
|
||||||
|
completion = completion.insert_snippet(snippet_cap, pat);
|
||||||
|
} else {
|
||||||
|
completion = completion.insert_text(pat);
|
||||||
|
}
|
||||||
|
Some(completion.build())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_record_as_pat(
|
||||||
|
db: &dyn HirDatabase,
|
||||||
|
fields: &[hir::Field],
|
||||||
|
name: &str,
|
||||||
|
fields_omitted: bool,
|
||||||
|
) -> String {
|
||||||
|
format!(
|
||||||
|
"{name} {{ {}{} }}",
|
||||||
|
fields.into_iter().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.into_iter().enumerate().map(|(idx, _)| format!("${}", idx + 1)).format(", "),
|
||||||
|
if fields_omitted { ", .." } else { "" },
|
||||||
|
name = name
|
||||||
|
)
|
||||||
|
}
|
@ -511,6 +511,10 @@ pub fn repr(self, db: &dyn HirDatabase) -> Option<ReprKind> {
|
|||||||
db.struct_data(self.id).repr.clone()
|
db.struct_data(self.id).repr.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn kind(self, db: &dyn HirDatabase) -> StructKind {
|
||||||
|
self.variant_data(db).kind()
|
||||||
|
}
|
||||||
|
|
||||||
fn variant_data(self, db: &dyn HirDatabase) -> Arc<VariantData> {
|
fn variant_data(self, db: &dyn HirDatabase) -> Arc<VariantData> {
|
||||||
db.struct_data(self.id).variant_data.clone()
|
db.struct_data(self.id).variant_data.clone()
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user