Skip to content

Commit

Permalink
nixd/Sema: add CompletionBuilder
Browse files Browse the repository at this point in the history
  • Loading branch information
inclyc committed Aug 10, 2023
1 parent c4f71ee commit 4dad1b0
Show file tree
Hide file tree
Showing 11 changed files with 211 additions and 172 deletions.
8 changes: 0 additions & 8 deletions nixd/include/nixd/AST/ParseAST.h
Original file line number Diff line number Diff line change
Expand Up @@ -212,14 +212,6 @@ class ParseAST {

[[nodiscard]] Links documentLink(const std::string &File) const;

// Completion

[[nodiscard]] lspserver::CompletionItem
toCompletionItem(const nix::Symbol &V) const;

[[nodiscard]] std::vector<lspserver::CompletionItem>
completion(const lspserver::Position &Pos) const;

[[nodiscard]] LocationContext getContext(lspserver::Position Pos) const;
};
} // namespace nixd
49 changes: 49 additions & 0 deletions nixd/include/nixd/Sema/CompletionBuilder.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#pragma once

#include "nixd/AST/EvalAST.h"
#include "nixd/AST/ParseAST.h"

#include "lspserver/Protocol.h"

#include <vector>

namespace nixd {

using CompletionResult = lspserver::CompletionList;

class CompletionBuilder {
CompletionResult Result;
size_t Limit = 1000;

public:
/// { a = 1; b = 2; }.|
/// ^ "a", "b"
void addAttrFields(const EvalAST &AST, const lspserver::Position &Pos,
nix::EvalState &State);

/// Symbols from let-bindings, rec-attrs.
void addSymbols(const ParseAST &AST, const nix::Expr *Node);

///
/// (
/// { foo ? 1 # <-- Lambda formal
/// , bar ? 2
/// }: foo + bar) { | }
/// ^ "foo", "bar"
void addLambdaFormals(const EvalAST &AST, nix::EvalState &State,
const nix::Expr *Node);

/// with pkgs; [ ]
void addEnv(nix::EvalState &State, nix::Env &NixEnv);

void addEnv(const EvalAST &AST, nix::EvalState &State, const nix::Expr *Node);

/// builtins.*
void addStaticEnv(const nix::SymbolTable &STable, const nix::StaticEnv &SEnv);

CompletionResult &getResult() { return Result; }

void setLimit(size_t Limit) { this->Limit = Limit; }
};

} // namespace nixd
18 changes: 0 additions & 18 deletions nixd/include/nixd/Support/CompletionHelper.h

This file was deleted.

23 changes: 0 additions & 23 deletions nixd/lib/AST/ParseAST.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -279,29 +279,6 @@ ParseAST::Links ParseAST::documentLink(const std::string &File) const {
return V.Result;
}

lspserver::CompletionItem
ParseAST::toCompletionItem(const nix::Symbol &V) const {
lspserver::CompletionItem R;
R.kind = lspserver::CompletionItemKind::Interface;
R.label = symbols()[V];
return R;
}

std::vector<lspserver::CompletionItem>
ParseAST::completion(const lspserver::Position &Pos) const {
std::vector<lspserver::CompletionItem> Items;
if (const auto *Node = lookupContainMin(Pos)) {
std::vector<nix::Symbol> Symbols;
collectSymbols(Node, Symbols);

// Insert symbols to our completion list.
std::transform(
Symbols.begin(), Symbols.end(), std::back_inserter(Items),
[this](const nix::Symbol &V) { return toCompletionItem(V); });
};
return Items;
}

namespace {
struct AttrDefVisitor : RecursiveASTVisitor<AttrDefVisitor> {
const ParseAST &AST;
Expand Down
134 changes: 134 additions & 0 deletions nixd/lib/Sema/CompletionBuilder.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
#include "nixd/Sema/CompletionBuilder.h"
#include "nixd/AST/ParseAST.h"
#include "nixd/Nix/Eval.h"

#include "lspserver/Protocol.h"

#include <stdexcept>

namespace nixd {

using lspserver::CompletionItem;
using lspserver::CompletionItemKind;

void CompletionBuilder::addAttrFields(const EvalAST &AST,
const lspserver::Position &Pos,
nix::EvalState &State) {
try {
if (const auto *Node = AST.lookupEnd(Pos)) {
auto Value = AST.getValueEval(Node, State);
if (Value.type() == nix::ValueType::nAttrs) {
// Traverse attribute bindings
for (auto Binding : *Value.attrs) {
CompletionItem R;
R.label = State.symbols[Binding.name];
R.kind = CompletionItemKind::Field;
Result.items.emplace_back(std::move(R));
if (Result.items.size() > Limit) {
Result.isIncomplete = true;
break;
}
}
}
}
} catch (std::out_of_range &) {
}
}

void CompletionBuilder::addSymbols(const ParseAST &AST, const nix::Expr *Node) {
std::vector<nix::Symbol> Symbols;
AST.collectSymbols(Node, Symbols);
// Insert symbols to our completion list.
std::transform(Symbols.begin(), Symbols.end(),
std::back_inserter(Result.items), [&](const nix::Symbol &V) {
lspserver::CompletionItem R;
R.kind = CompletionItemKind::Interface;
R.label = AST.symbols()[V];
return R;
});
}

void CompletionBuilder::addLambdaFormals(const EvalAST &AST,
nix::EvalState &State,
const nix::Expr *Node) {

// Nix only accepts attr set formals
if (!dynamic_cast<const nix::ExprAttrs *>(Node))
return;

try {
const auto *Parent = AST.parent(Node);
if (const auto *SomeExprCall =
dynamic_cast<const nix::ExprCall *>(Parent)) {
auto Value = AST.getValueEval(SomeExprCall->fun, State);
if (!Value.isLambda())
return;
auto *Fun = Value.lambda.fun;
if (!Fun->hasFormals())
return;
for (auto Formal : Fun->formals->formals) {
CompletionItem R;
R.label = State.symbols[Formal.name];
R.kind = CompletionItemKind::Constructor;
Result.items.emplace_back(std::move(R));
}
}
} catch (std::out_of_range &) {
}
}

void CompletionBuilder::addEnv(nix::EvalState &State, nix::Env &NixEnv) {
if (NixEnv.type == nix::Env::HasWithExpr) {
nix::Value *V = State.allocValue();
State.evalAttrs(*NixEnv.up, (nix::Expr *)NixEnv.values[0], *V, nix::noPos,
"<borked>");
NixEnv.values[0] = V;
NixEnv.type = nix::Env::HasWithAttrs;
}
if (NixEnv.type == nix::Env::HasWithAttrs) {
for (const auto &SomeAttr : *NixEnv.values[0]->attrs) {
std::string Name = State.symbols[SomeAttr.name];
lspserver::CompletionItem R;
R.label = Name;
R.kind = lspserver::CompletionItemKind::Variable;
Result.items.emplace_back(std::move(R));

if (Result.items.size() > Limit) {
Result.isIncomplete = true;
break;
}
}
}
if (NixEnv.up)
addEnv(State, *NixEnv.up);
}

void CompletionBuilder::addEnv(const EvalAST &AST, nix::EvalState &State,
const nix::Expr *Node) {
try {
// Eval this node, for reaching deeper envs (e.g. with expressions).
AST.getValueEval(Node, State);
if (auto *ExprEnv = AST.searchUpEnv(Node))
addEnv(State, *ExprEnv);
} catch (std::out_of_range &) {
}
}

void CompletionBuilder::addStaticEnv(const nix::SymbolTable &STable,
const nix::StaticEnv &SEnv) {
for (auto [Symbol, Displ] : SEnv.vars) {
std::string Name = STable[Symbol];
if (Name.starts_with("__"))
continue;

CompletionItem R;
R.label = Name;
R.kind = CompletionItemKind::Constant;
Result.items.emplace_back(std::move(R));
}

if (SEnv.up)
addStaticEnv(STable, SEnv);
}

} // namespace nixd
17 changes: 17 additions & 0 deletions nixd/lib/Sema/meson.build
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
libnixdSemaDeps = [ nixd_lsp_server
, nix_all
, nixdAST
, llvm
]

libnixdSema = library('nixdSema'
, 'CompletionBuilder.cpp'
, include_directories: nixd_inc
, dependencies: libnixdSemaDeps
, install: true
)

nixdSema = declare_dependency( include_directories: nixd_inc
, link_with: libnixdSema
, dependencies: libnixdSemaDeps
)
88 changes: 9 additions & 79 deletions nixd/lib/Server/EvalWorker.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "nixd/Server/EvalWorker.h"
#include "nixd/Nix/Init.h"
#include "nixd/Nix/Value.h"
#include "nixd/Sema/CompletionBuilder.h"
#include "nixd/Server/EvalDraftStore.h"
#include "nixd/Server/IPC.h"
#include "nixd/Support/CompletionHelper.h"
Expand Down Expand Up @@ -163,93 +164,22 @@ void EvalWorker::onCompletion(const lspserver::CompletionParams &Params,
auto Action = [Params, this](const nix::ref<EvalAST> &AST,
ReplyRAII<CompletionList> &&RR) {
auto State = IER->Session->getState();
CompletionBuilder Builder;
// Lookup an AST node, and get it's 'Env' after evaluation
CompletionHelper::Items Items;
lspserver::log("current trigger character is {0}",
Params.context.triggerCharacter);

if (Params.context.triggerCharacter == ".") {
const auto *Node = AST->lookupEnd(Params.position);
if (!Node)
return;
try {
auto Value = AST->getValueEval(Node, *IER->Session->getState());
if (Value.type() == nix::ValueType::nAttrs) {
// Traverse attribute bindings
for (auto Binding : *Value.attrs) {
Items.emplace_back(
CompletionItem{.label = State->symbols[Binding.name],
.kind = CompletionItemKind::Field});
if (Items.size() > 5000)
break;
}
}
} catch (std::out_of_range &) {
RR.Response = error("no associated value on requested attrset.");
return;
}
Builder.addAttrFields(*AST, Params.position, *State);
} else {

const auto *Node = AST->lookupContainMin(Params.position);

// TODO: share the same code with static worker
std::vector<nix::Symbol> Symbols;
AST->collectSymbols(Node, Symbols);
// Insert symbols to our completion list.
std::transform(Symbols.begin(), Symbols.end(), std::back_inserter(Items),
[&](const nix::Symbol &V) -> decltype(Items)::value_type {
decltype(Items)::value_type R;
R.kind = CompletionItemKind::Interface;
R.label = State->symbols[V];
return R;
});

auto FromLambdaFormals = [&]() {
// Firstly, we check that if we are in "ExprAttrs", consider this
// is a
// special case for completion lambda formals
if (!dynamic_cast<const nix::ExprAttrs *>(Node))
return;
// Then, check that if the parent of evaluates to a "<LAMBDA>"
try {
const auto *Parent = AST->parent(Node);
if (const auto *SomeExprCall =
dynamic_cast<const nix::ExprCall *>(Parent)) {
auto Value = AST->getValue(SomeExprCall->fun);
if (!Value.isLambda())
return;
// Then, filling the completion list using that lambda
// formals.
auto *Fun = Value.lambda.fun;
if (!Fun->hasFormals())
return;
for (auto Formal : Fun->formals->formals) {
CompletionItem Item;
Item.label = State->symbols[Formal.name];
Item.kind = CompletionItemKind::Constructor;
Items.emplace_back(std::move(Item));
}
}

} catch (...) {
}
};
FromLambdaFormals();
try {
if (!Node)
return;
auto *ExprEnv = AST->searchUpEnv(Node);
CompletionHelper::fromEnv(*State, *ExprEnv, Items);
} catch (std::out_of_range &) {
}
CompletionHelper::fromStaticEnv(State->symbols, *State->staticBaseEnv,
Items);
Builder.addSymbols(*AST, Node);
Builder.addLambdaFormals(*AST, *State, Node);
Builder.addEnv(*AST, *State, Node);
Builder.addStaticEnv(State->symbols, *State->staticBaseEnv);
}
// Make the response.
CompletionList List;
List.isIncomplete = false;
List.items = Items;
RR.Response = List;
if (!Builder.getResult().items.empty())
RR.Response = Builder.getResult();
};

withAST<CompletionList>(Params.textDocument.uri.file().str(),
Expand Down
1 change: 1 addition & 0 deletions nixd/lib/Server/meson.build
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
libnixdServerDeps = [ nixd_lsp_server
, nix_all
, nixdSema
, nixdAST
, nixdNix
, nixdSupport
Expand Down
Loading

0 comments on commit 4dad1b0

Please sign in to comment.