mirror of
https://github.com/oxalica/nil.git
synced 2025-12-23 09:19:49 +00:00
Enhance completion
This commit is contained in:
parent
9ef631bb0c
commit
11a690bb73
6 changed files with 150 additions and 18 deletions
7
Cargo.lock
generated
7
Cargo.lock
generated
|
|
@ -78,6 +78,12 @@ version = "1.0.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c97b9233581d84b8e1e689cdd3a47b6f69770084fc246e86a7f78b0d9c1d4a5"
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be"
|
||||
|
||||
[[package]]
|
||||
name = "env_logger"
|
||||
version = "0.9.0"
|
||||
|
|
@ -286,6 +292,7 @@ dependencies = [
|
|||
name = "nil"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"either",
|
||||
"expect-test",
|
||||
"indexmap",
|
||||
"la-arena",
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ edition = "2021"
|
|||
license = "MIT OR Apache-2.0"
|
||||
|
||||
[dependencies]
|
||||
either = "1.7.0"
|
||||
indexmap = "1.9.1"
|
||||
la-arena = "0.2.1"
|
||||
ordered-float = "3.0.0"
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@ Super fast incremental analysis! Scans `all-packages.nix` in less than 0.1s and
|
|||
- [x] With expression references.
|
||||
- [x] Completion. `textDocument/completion`
|
||||
- [x] Builtin names.
|
||||
- [x] Local bindings.
|
||||
- [x] Local bindings and rec-attrset fields.
|
||||
- [x] Keywords.
|
||||
- [ ] Attrset fields.
|
||||
- [x] Diagnostics. `textDocument/publishDiagnostics`
|
||||
- Syntax errors.
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ pub(crate) fn completion(
|
|||
.into_iter()
|
||||
.map(|item| {
|
||||
let kind = match item.kind {
|
||||
CompletionItemKind::Keyword => lsp::CompletionItemKind::KEYWORD,
|
||||
CompletionItemKind::Param => lsp::CompletionItemKind::VARIABLE,
|
||||
CompletionItemKind::LetBinding => lsp::CompletionItemKind::VARIABLE,
|
||||
CompletionItemKind::Field => lsp::CompletionItemKind::FIELD,
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ impl<'a> Traversal<'a> {
|
|||
},
|
||||
// Don't walk LetIn bindings unless referenced.
|
||||
Expr::LetIn(_, body) => self.queue.push(*body),
|
||||
e => e.walk_child_exprs(&self.module, |e| self.queue.push(e)),
|
||||
e => e.walk_child_exprs(self.module, |e| self.queue.push(e)),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,26 @@
|
|||
use crate::builtin::BuiltinKind;
|
||||
use crate::def::{AstPtr, DefDatabase, NameDefKind};
|
||||
use crate::{builtin, FileId};
|
||||
use either::Either::{Left, Right};
|
||||
use rowan::ast::AstNode;
|
||||
use smol_str::SmolStr;
|
||||
use syntax::{ast, match_ast, SyntaxKind, TextRange, TextSize, T};
|
||||
|
||||
#[rustfmt::skip]
|
||||
const EXPR_POS_KEYWORDS: &[&str] = &[
|
||||
"assert",
|
||||
// "else",
|
||||
"if",
|
||||
// "in",
|
||||
// "inherit",
|
||||
"let",
|
||||
"or",
|
||||
"rec",
|
||||
// "then",
|
||||
"with",
|
||||
];
|
||||
const ATTR_POS_KEYWORDS: &[&str] = &["inherit"];
|
||||
|
||||
/// A single completion variant in the editor pop-up.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct CompletionItem {
|
||||
|
|
@ -21,6 +37,7 @@ pub struct CompletionItem {
|
|||
/// The type of the completion item.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum CompletionItemKind {
|
||||
Keyword,
|
||||
Param,
|
||||
LetBinding,
|
||||
Field,
|
||||
|
|
@ -63,22 +80,73 @@ pub(crate) fn completions(
|
|||
_ => return None,
|
||||
};
|
||||
|
||||
let ref_node = tok.parent_ancestors().find_map(|node| {
|
||||
let node = tok.parent_ancestors().find_map(|node| {
|
||||
match_ast! {
|
||||
match node {
|
||||
ast::Ref(n) => Some(n),
|
||||
ast::Ref(n) => Some(Left(n)),
|
||||
ast::Name(n) => Some(Right(n)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
})?;
|
||||
|
||||
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 _entry_node = ast::AttrpathValue::cast(path_node.syntax().parent()?)?;
|
||||
complete_attrpath_def(db, file_id, source_range, path_node, name_node)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn complete_expr(
|
||||
db: &dyn DefDatabase,
|
||||
file_id: FileId,
|
||||
source_range: TextRange,
|
||||
ref_node: ast::Ref,
|
||||
) -> Option<Vec<CompletionItem>> {
|
||||
let module = db.module(file_id);
|
||||
let source_map = db.source_map(file_id);
|
||||
let expr_id = source_map.node_expr(AstPtr::new(ref_node.syntax()))?;
|
||||
let scopes = db.scopes(file_id);
|
||||
let scope_id = scopes.scope_by_expr(expr_id)?;
|
||||
|
||||
// TODO: Better sorting.
|
||||
let mut items = scopes
|
||||
let prefix = SmolStr::from(ref_node.token()?.text());
|
||||
let mut items = Vec::new();
|
||||
let mut feed = |compe: CompletionItem| {
|
||||
if can_complete(&prefix, &compe.replace) {
|
||||
items.push(compe);
|
||||
}
|
||||
};
|
||||
|
||||
// Keywords.
|
||||
EXPR_POS_KEYWORDS
|
||||
.iter()
|
||||
.map(|kw| keyword_to_completion(kw, source_range))
|
||||
.for_each(&mut feed);
|
||||
|
||||
// Contectual keywords.
|
||||
if ref_node
|
||||
.syntax()
|
||||
.ancestors()
|
||||
.find_map(ast::IfThenElse::cast)
|
||||
.is_some()
|
||||
{
|
||||
feed(keyword_to_completion("then", source_range));
|
||||
feed(keyword_to_completion("else", source_range));
|
||||
}
|
||||
if ref_node
|
||||
.syntax()
|
||||
.ancestors()
|
||||
.find_map(ast::LetIn::cast)
|
||||
.is_some()
|
||||
{
|
||||
feed(keyword_to_completion("in", source_range));
|
||||
}
|
||||
|
||||
// Names in current scopes.
|
||||
scopes
|
||||
.ancestors(scope_id)
|
||||
.filter_map(|scope| scope.as_name_defs())
|
||||
.flatten()
|
||||
|
|
@ -88,20 +156,74 @@ pub(crate) fn completions(
|
|||
replace: name.clone(),
|
||||
kind: module[def].kind.into(),
|
||||
})
|
||||
.chain(
|
||||
builtin::BUILTINS
|
||||
.values()
|
||||
.filter(|b| !b.is_hidden)
|
||||
.map(|b| CompletionItem {
|
||||
label: b.name.into(),
|
||||
source_range,
|
||||
replace: b.name.into(),
|
||||
kind: b.kind.into(),
|
||||
}),
|
||||
)
|
||||
.collect::<Vec<_>>();
|
||||
.for_each(&mut feed);
|
||||
|
||||
// Global builtins.
|
||||
builtin::BUILTINS
|
||||
.values()
|
||||
.filter(|b| !b.is_hidden)
|
||||
.map(|b| CompletionItem {
|
||||
label: b.name.into(),
|
||||
source_range,
|
||||
replace: b.name.into(),
|
||||
kind: b.kind.into(),
|
||||
})
|
||||
.for_each(&mut feed);
|
||||
|
||||
// TODO: Better sorting.
|
||||
items.sort_by(|lhs, rhs| lhs.label.cmp(&rhs.label));
|
||||
items.dedup_by(|lhs, rhs| lhs.label == rhs.label);
|
||||
|
||||
Some(items)
|
||||
}
|
||||
|
||||
fn complete_attrpath_def(
|
||||
_db: &dyn DefDatabase,
|
||||
_file_id: FileId,
|
||||
source_range: TextRange,
|
||||
path_node: ast::Attrpath,
|
||||
_name_node: ast::Name,
|
||||
) -> Option<Vec<CompletionItem>> {
|
||||
if path_node.attrs().count() >= 2 {
|
||||
return None;
|
||||
}
|
||||
let in_let = path_node
|
||||
.syntax()
|
||||
.ancestors()
|
||||
.find_map(ast::LetIn::cast)
|
||||
.is_some();
|
||||
Some(
|
||||
ATTR_POS_KEYWORDS
|
||||
.iter()
|
||||
.copied()
|
||||
.chain(in_let.then_some("in"))
|
||||
.map(|kw| keyword_to_completion(kw, source_range))
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
fn keyword_to_completion(kw: &str, source_range: TextRange) -> CompletionItem {
|
||||
CompletionItem {
|
||||
label: kw.into(),
|
||||
source_range,
|
||||
replace: kw.into(),
|
||||
kind: CompletionItemKind::Keyword,
|
||||
}
|
||||
}
|
||||
|
||||
// Subsequence matching.
|
||||
fn can_complete(prefix: &str, replace: &str) -> bool {
|
||||
let mut rest = prefix.as_bytes();
|
||||
if rest.is_empty() {
|
||||
return true;
|
||||
}
|
||||
for b in replace.bytes() {
|
||||
if rest.first().unwrap() == &b {
|
||||
rest = &rest[1..];
|
||||
if rest.is_empty() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue