Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

nixd/Sema: add CompletionBuilder #239

Merged
merged 1 commit into from
Aug 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
)
89 changes: 9 additions & 80 deletions nixd/lib/Server/EvalWorker.cpp
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
#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"
#include "nixd/Support/String.h"

#include "lspserver/LSPServer.h"
Expand Down Expand Up @@ -163,93 +163,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