1
1
mirror of https://github.com/oxalica/nil.git synced 2024-11-23 03:57:06 +03:00

Fix completion inside non-last Attr

This commit is contained in:
oxalica 2022-11-07 12:29:30 +08:00
parent d714bedf1e
commit 2997e14a66

View File

@ -1,11 +1,11 @@
use crate::def::{AstPtr, NameKind};
use crate::def::{AstPtr, BindingValue, Expr, NameKind};
use crate::{FileId, FilePos, TyDatabase};
use builtin::{BuiltinKind, ALL_BUILTINS};
use either::Either::{Left, Right};
use rowan::ast::AstNode;
use smol_str::SmolStr;
use syntax::semantic::AttrKind;
use syntax::{ast, best_token_at_offset, match_ast, SyntaxKind, SyntaxNode, TextRange, T};
use syntax::{ast, best_token_at_offset, match_ast, SyntaxKind, TextRange, T};
#[rustfmt::skip]
const EXPR_POS_KEYWORDS: &[&str] = &[
@ -20,7 +20,6 @@ const EXPR_POS_KEYWORDS: &[&str] = &[
// "then",
"with",
];
const ATTR_POS_KEYWORDS: &[&str] = &["inherit"];
/// A single completion variant in the editor pop-up.
#[derive(Debug, Clone, PartialEq, Eq)]
@ -98,26 +97,7 @@ pub(crate) fn completions(
match node {
Left(ref_node) => complete_expr(db, file_id, source_range, ref_node),
Right(name_node) => {
let path_node = ast::Attrpath::cast(name_node.syntax().parent()?)?;
let parent_node = path_node.syntax().parent()?;
let container_node = match_ast! {
match parent_node {
ast::AttrpathValue(_) => parent_node.parent()?,
ast::HasAttr(n) => n.set()?.syntax().clone(),
ast::Select(n) => n.set()?.syntax().clone(),
_ => return None,
}
};
complete_attrpath(
db,
file_id,
source_range,
container_node,
path_node,
name_node,
)
}
Right(name_node) => complete_attrpath(db, file_id, source_range, name_node),
}
}
@ -209,27 +189,84 @@ fn complete_attrpath(
db: &dyn TyDatabase,
file_id: FileId,
source_range: TextRange,
container_node: SyntaxNode,
path_node: ast::Attrpath,
name_node: ast::Name,
) -> Option<Vec<CompletionItem>> {
let mut items = Vec::new();
let path_node = ast::Attrpath::cast(name_node.syntax().parent()?)?;
let (set_node, container_node) = match_ast! {
match (path_node.syntax().parent()?){
ast::AttrpathValue(n) => {
let n = n.syntax().parent()?;
(n.clone(), n)
},
ast::HasAttr(n) => (n.set()?.syntax().clone(), n.syntax().clone()),
ast::Select(n) => (n.set()?.syntax().clone(), n.syntax().clone()),
_ => return None,
}
};
let is_let = ast::LetIn::can_cast(container_node.kind());
let is_attrset = ast::AttrSet::can_cast(container_node.kind());
let attr_cnt = path_node.attrs().count();
let current_input = name_node
.token()
.map_or(SmolStr::default(), |tok| tok.text().into());
if attr_cnt <= 1 {
items.extend(
ATTR_POS_KEYWORDS
.iter()
.copied()
.chain(is_let.then_some("in"))
.map(|kw| keyword_to_completion(kw, source_range)),
)
let module = db.module(file_id);
let source_map = db.source_map(file_id);
let mut items = Vec::new();
// Only complete keywords when this Attrpath has only one Attr.
if attr_cnt == 1 && (is_let || is_attrset) {
items.push(keyword_to_completion("inherit", source_range));
}
if attr_cnt == 1 && is_let {
items.push(keyword_to_completion("in", source_range));
}
// We are inside the first Attr of a Let.
// Completes all static names in the same `Let` for splited definition.
// ```nix
// let
// foo.bar = 42;
// f| # <- We may want to also define `foo.baz`.
// in /**/
// ```
// Note that the first Attr of an Attrset can be handled by types later.
if is_let
&& path_node
.attrs()
.next()
.map_or(false, |attr| attr.syntax() == name_node.syntax())
{
if let Some(expr) = source_map.expr_for_node(AstPtr::new(&container_node)) {
if let Expr::LetIn(b, _) = &module[expr] {
items.extend(
b.statics
.iter()
.filter(|(_, v)| matches!(v, BindingValue::Expr(_)))
.map(|&(name, _)| module[name].text.clone())
// We should not report current incomplete definition.
// This is covered by `no_incomplete_field`.
.filter(|name| *name != current_input)
.map(|name| CompletionItem {
label: name.clone(),
source_range,
replace: name,
kind: CompletionItemKind::LetBinding,
brief: None,
doc: None,
}),
);
}
}
return Some(items);
}
// If we get here, we are either inside a selection path `a.b|`,
// or non-first parts of a definition `{ a.b| }`.
// Use type information
(|| -> Option<()> {
let source_map = db.source_map(file_id);
let infer = db.infer(file_id);
let mut attrs = path_node.attrs();
@ -237,15 +274,15 @@ fn complete_attrpath(
let name = source_map.name_for_node(AstPtr::new(attrs.next()?.syntax()))?;
infer.ty_for_name(name)
} else {
let container_expr = source_map.expr_for_node(AstPtr::new(&container_node))?;
infer.ty_for_expr(container_expr)
let set_expr = source_map.expr_for_node(AstPtr::new(&set_node))?;
infer.ty_for_expr(set_expr)
};
// Resolve prefix paths, except for the last one.
// foo.a.b.c
// Resolve prefix paths, except for the current NAME.
// foo.a.b.c|.d
// ^-----^
let set = attrs
.take(attr_cnt.saturating_sub(if is_let { 2 } else { 1 }))
.take_while(|attr| attr.syntax() != name_node.syntax())
.try_fold(set_ty, |set_ty, attr| match AttrKind::of(attr) {
AttrKind::Static(Some(field)) => set_ty.kind(&infer).as_attrset()?.get(&field),
_ => None,
@ -253,9 +290,6 @@ fn complete_attrpath(
.kind(&infer)
.as_attrset()?;
let current_input = name_node
.token()
.map_or(SmolStr::default(), |tok| tok.text().into());
items.extend(
set.iter()
// We should not report current incomplete definition.
@ -400,6 +434,11 @@ mod tests {
"foo",
expect!["(Field) { foo.bar = 1; }.foo"],
);
check(
"{ foo.bar = 1; }.f$0.bar",
"foo",
expect!["(Field) { foo.bar = 1; }.foo.bar"],
);
check(
"{ foo.bar = 1; }.foo.b$0",
"bar",
@ -418,6 +457,25 @@ mod tests {
);
}
#[test]
fn has_known_field() {
check(
"{ foo.bar = 1; } ? f$0",
"foo",
expect!["(Field) { foo.bar = 1; } ? foo"],
);
check(
"{ foo.bar = 1; } ? f$0.bar",
"foo",
expect!["(Field) { foo.bar = 1; } ? foo.bar"],
);
check(
"{ foo.bar = 1; } ? foo.b$0",
"bar",
expect!["(Field) { foo.bar = 1; } ? foo.bar"],
);
}
#[test]
fn define_known_field_let() {
check(
@ -425,6 +483,11 @@ mod tests {
"foo",
expect!["(Field) let a.foo = 1; in a.foo.bar"],
);
check(
"let a.f$0.bar = 1; in a.foo.bar",
"foo",
expect!["(Field) let a.foo.bar = 1; in a.foo.bar"],
);
check(
"let a.foo.b$0 = 1; in a.foo.bar",
"bar",
@ -439,6 +502,11 @@ mod tests {
"foo",
expect!["(Field) let f = { foo }: foo.bar; in f { foo }"],
);
check(
"let f = { foo }: foo.bar; in f { f$0.bar }",
"foo",
expect!["(Field) let f = { foo }: foo.bar; in f { foo.bar }"],
);
check(
"let f = { foo }: foo.bar; in f { foo.b$0 }",
"bar",
@ -446,8 +514,19 @@ mod tests {
);
}
#[test]
fn define_let_sibling() {
check(
"let foo.bar = 1; f$0 in 1",
"foo",
expect!["(LetBinding) let foo.bar = 1; foo in 1"],
);
}
#[test]
fn no_incomplete_field() {
check_no("a: a.f$0", "f");
check_no("{ f$0 = 1; }", "f");
check_no("let f$0 = 1; in 1", "f");
}
}