Skip to content

Commit

Permalink
nixd/Server: semantic completion
Browse files Browse the repository at this point in the history
  • Loading branch information
inclyc committed Jul 30, 2023
1 parent 82ea94c commit 5d162b1
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 71 deletions.
2 changes: 1 addition & 1 deletion nixd/include/nixd/Server/Controller.h
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ class Controller : public lspserver::LSPServer {
lspserver::Callback<lspserver::Hover>);

void onCompletion(const lspserver::CompletionParams &,
lspserver::Callback<lspserver::CompletionList>);
lspserver::Callback<llvm::json::Value>);

void onFormat(const lspserver::DocumentFormattingParams &,
lspserver::Callback<std::vector<lspserver::TextEdit>>);
Expand Down
13 changes: 5 additions & 8 deletions nixd/lib/AST/ParseAST.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

#include "lspserver/Protocol.h"
#include "nixd/Expr/Expr.h"
#include "nixexpr.hh"

#include <nix/nixexpr.hh>
#include <nix/symbol-table.hh>

#include <optional>
Expand Down Expand Up @@ -327,13 +327,10 @@ ParseAST::LocationContext ParseAST::getContext(lspserver::Position Pos) const {
V.traverseExpr(root());
if (V.InAttrDef)
return LocationContext::AttrName;
if (auto *E = lookupContainMin(Pos)) {
auto *EAttrs = dynamic_cast<const nix::ExprAttrs *>(E);
try {
if (lspserver::Range(nRange(E)).contains(Pos) && !EAttrs)
return LocationContext::Value;
} catch (std::out_of_range &) {
}
if (const auto *E = lookupContainMin(Pos)) {
const auto *EAttrs = dynamic_cast<const nix::ExprAttrs *>(E);
if (!EAttrs)
return LocationContext::Value;
}
return LocationContext::Unknown;
}
Expand Down
137 changes: 76 additions & 61 deletions nixd/lib/Server/Controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -544,76 +544,91 @@ void Controller::onHover(const lspserver::TextDocumentPositionParams &Params,
boost::asio::post(Pool, std::move(Task));
}

void Controller::onCompletion(
const lspserver::CompletionParams &Params,
lspserver::Callback<lspserver::CompletionList> Reply) {
auto EnableOption = Config.options.enable;
using RTy = lspserver::CompletionList;
using namespace lspserver;
constexpr auto Method = "nixd/ipc/textDocument/completion";
auto Task = [=, Reply = std::move(Reply), this]() mutable {
auto Resp = askWC<RTy>(Method, Params, {EvalWorkers, EvalWorkerLock, 2e6});
std::optional<RTy> R;
if (!Resp.empty())
R = std::move(Resp.back());
else {
// Statically construct the completion list.
std::binary_semaphore Smp(0);
try {
auto Path = Params.textDocument.uri.file();
auto Action = [&Smp, &Resp, Pos = Params.position](
const ParseAST &AST, ASTManager::VersionTy Version) {
auto Items = AST.completion(Pos);
Resp.emplace_back(CompletionList{false, Items});
Smp.release();
};
if (auto Draft = DraftMgr.getDraft(Path)) {
auto Version =
EvalDraftStore::decodeVersion(Draft->Version).value_or(0);
ASTMgr.withAST(Path.str(), Version, std::move(Action));
Smp.acquire();
}
} catch (std::exception &E) {
lspserver::elog("completion/parseAST: {0}", stripANSI(E.what()));
} catch (...) {
void Controller::onCompletion(const lspserver::CompletionParams &Params,
lspserver::Callback<llvm::json::Value> Reply) {
// Statically construct the completion list.
std::binary_semaphore Smp(0);
auto Path = Params.textDocument.uri.file();
auto Action = [&Smp, &Params, &Reply,
this](const ParseAST &AST,
ASTManager::VersionTy Version) mutable {
using PL = ParseAST::LocationContext;
using List = lspserver::CompletionList;
ReplyRAII<llvm::json::Value> RR(std::move(Reply));

// TODO: split this in AST, use AST-based attrpath construction.
auto CompletionFromOptions = [&, this]() -> std::optional<List> {
// Requested completion on an attribute name, not values.
bool OptionsEnabled;
{
std::lock_guard _(ConfigLock);
OptionsEnabled = Config.options.enable;
}
}
if (OptionsEnabled) {
ipc::AttrPathParams APParams;

if (EnableOption) {
ipc::AttrPathParams APParams;
if (Params.context.triggerCharacter == ".") {
// Get nixpkgs options
auto Code = llvm::StringRef(
*DraftMgr.getDraft(Params.textDocument.uri.file())->Contents);
auto ExpectedPosition = positionToOffset(Code, Params.position);

if (Params.context.triggerCharacter == ".") {
// Get nixpkgs options
auto Code = llvm::StringRef(
*DraftMgr.getDraft(Params.textDocument.uri.file())->Contents);
auto ExpectedPosition = positionToOffset(Code, Params.position);
// get the attr path
auto TruncateBackCode = Code.substr(0, ExpectedPosition.get());

// get the attr path
auto TruncateBackCode = Code.substr(0, ExpectedPosition.get());
auto [_, AttrPath] = TruncateBackCode.rsplit(" ");
APParams.Path = AttrPath.str();
}

auto [_, AttrPath] = TruncateBackCode.rsplit(" ");
APParams.Path = AttrPath.str();
auto Resp = askWC<lspserver::CompletionList>(
"nixd/ipc/textDocument/completion/options", APParams,
{OptionWorkers, OptionWorkerLock, 1e5});
if (!Resp.empty())
return Resp.back();
}
return std::nullopt;
};

auto RespOption = askWC<lspserver::CompletionList>(
"nixd/ipc/textDocument/completion/options", APParams,
{OptionWorkers, OptionWorkerLock, 1e5});
if (!RespOption.empty()) {
if (R) {
// Merge response and option response.
auto O = RespOption.back().items;
R->items.insert(R->items.end(), O.begin(), O.end());
} else {
R = std::move(RespOption.back());
}
}
auto CompletionFromEval = [&, this]() -> std::optional<List> {
constexpr auto Method = "nixd/ipc/textDocument/completion";
auto Resp =
askWC<List>(Method, Params, {EvalWorkers, EvalWorkerLock, 2e6});
std::optional<List> R;
if (!Resp.empty())
return std::move(Resp.back());
return std::nullopt;
};

switch (AST.getContext(Params.position)) {
case (PL::AttrName): {
RR.Response = CompletionFromOptions();
break;
}
if (R)
Reply(std::move(*R));
else
Reply(RTy{});
case (PL::Value): {
RR.Response = CompletionFromEval();
break;
}
case (PL::Unknown):
List L;
if (auto LOptions = CompletionFromOptions())
L.items.insert(L.items.end(), (*LOptions).items.begin(),
(*LOptions).items.end());
if (auto LEval = CompletionFromEval())
L.items.insert(L.items.end(), (*LEval).items.begin(),
(*LEval).items.end());
L.isIncomplete = true;
RR.Response = std::move(L);
}
Smp.release();
};
boost::asio::post(Pool, std::move(Task));

if (auto Draft = DraftMgr.getDraft(Path)) {
auto Version = EvalDraftStore::decodeVersion(Draft->Version).value_or(0);
ASTMgr.withAST(Path.str(), Version, std::move(Action));
Smp.acquire();
} else {
Reply(lspserver::error("requested completion list on unknown draft path"));
}
}

void Controller::onRename(const lspserver::RenameParams &Params,
Expand Down
2 changes: 1 addition & 1 deletion nixd/tools/nixd/test/completion-attrset.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ Complete this file:
CHECK: "id": 1,
CHECK-NEXT: "jsonrpc": "2.0",
CHECK-NEXT: "result": {
CHECK-NEXT: "isIncomplete": false,
CHECK-NEXT: "isIncomplete": true,
CHECK-NEXT: "items": [
CHECK-NEXT: {
CHECK-NEXT: "kind": 5,
Expand Down

0 comments on commit 5d162b1

Please sign in to comment.