Skip to content

Commit

Permalink
nixd: complete nixpkgs (#411)
Browse files Browse the repository at this point in the history
  • Loading branch information
inclyc authored Apr 14, 2024
1 parent 7837528 commit f510da2
Show file tree
Hide file tree
Showing 24 changed files with 623 additions and 62 deletions.
2 changes: 1 addition & 1 deletion default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ stdenv.mkDerivation {
# Disable nixd regression tests, because it uses some features provided by
# nix, and does not correctly work in the sandbox
meson test --print-errorlogs unit/libnixf/Basic unit/libnixf/Parse unit/libnixt regression/nixd
meson test --print-errorlogs unit/libnixf/Basic unit/libnixf/Parse unit/libnixt
runHook postCheck
'';

Expand Down
2 changes: 2 additions & 0 deletions lspserver/include/lspserver/Protocol.h
Original file line number Diff line number Diff line change
Expand Up @@ -1291,8 +1291,10 @@ struct CompletionItem {
//
// data?: any - A data entry field that is preserved on a completion item
// between a completion and a completion resolve request.
std::string data;
};
llvm::json::Value toJSON(const CompletionItem &);
bool fromJSON(const llvm::json::Value &, CompletionItem &, llvm::json::Path);
llvm::raw_ostream &operator<<(llvm::raw_ostream &, const CompletionItem &);

bool operator<(const CompletionItem &, const CompletionItem &);
Expand Down
12 changes: 12 additions & 0 deletions lspserver/src/Protocol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -987,9 +987,21 @@ llvm::json::Value toJSON(const CompletionItem &CI) {
if (CI.deprecated)
Result["deprecated"] = CI.deprecated;
Result["score"] = CI.score;
Result["data"] = CI.data;
return Result;
}

bool fromJSON(const llvm::json::Value &Params, CompletionItem &R,
llvm::json::Path P) {
llvm::json::ObjectMapper O(Params, P);
int Kind;
O.mapOptional("kind", Kind);
R.kind = static_cast<CompletionItemKind>(Kind);
return O //
&& O.mapOptional("label", R.label) //
&& O.mapOptional("data", R.data); //
}

llvm::raw_ostream &operator<<(llvm::raw_ostream &O, const CompletionItem &I) {
O << I.label << " - " << toJSON(I);
return O;
Expand Down
7 changes: 7 additions & 0 deletions nixd/include/nixd/CommandLine/Options.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#include <llvm/Support/CommandLine.h>

namespace nixd {

extern llvm::cl::OptionCategory NixdCategory;

} // namespace nixd
16 changes: 16 additions & 0 deletions nixd/include/nixd/Controller/Controller.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,25 @@
#include "lspserver/DraftStore.h"
#include "lspserver/LSPServer.h"
#include "lspserver/Protocol.h"
#include "nixd/Eval/AttrSetClient.h"

#include <boost/asio/thread_pool.hpp>

namespace nixd {

class Controller : public lspserver::LSPServer {
std::unique_ptr<OwnedEvalClient> Eval;

// Use this worker for evaluating nixpkgs.
std::unique_ptr<AttrSetClientProc> NixpkgsEval;

AttrSetClientProc &nixpkgsEval() {
assert(NixpkgsEval);
return *NixpkgsEval;
}

AttrSetClient *nixpkgsClient() { return nixpkgsEval().client(); }

lspserver::DraftStore Store;

llvm::unique_function<void(const lspserver::PublishDiagnosticsParams &)>
Expand Down Expand Up @@ -91,6 +103,10 @@ class Controller : public lspserver::LSPServer {
void onCompletion(const lspserver::CompletionParams &Params,
lspserver::Callback<lspserver::CompletionList> Reply);

void
onCompletionItemResolve(const lspserver::CompletionItem &Params,
lspserver::Callback<lspserver::CompletionItem> Reply);

void onDefinition(const lspserver::TextDocumentPositionParams &Params,
lspserver::Callback<lspserver::Location> Reply);

Expand Down
71 changes: 71 additions & 0 deletions nixd/include/nixd/Eval/AttrSetClient.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#pragma once

#include "nixd/Protocol/AttrSet.h"
#include "nixd/Support/StreamProc.h"

#include <lspserver/LSPServer.h>

#include <thread>

namespace nixd {

class AttrSetClient : public lspserver::LSPServer {

llvm::unique_function<void(const EvalExprParams &Params,
lspserver::Callback<EvalExprResponse> Reply)>
EvalExpr;

llvm::unique_function<void(const AttrPathInfoParams &Params,
lspserver::Callback<AttrPathInfoResponse> Reply)>
AttrPathInfo;

llvm::unique_function<void(
const AttrPathCompleteParams &Params,
lspserver::Callback<AttrPathCompleteResponse> Reply)>
AttrPathComplete;

public:
AttrSetClient(std::unique_ptr<lspserver::InboundPort> In,
std::unique_ptr<lspserver::OutboundPort> Out);

/// \brief Request eval some expression.
/// The expression should be evaluted to attrset.
void evalExpr(const EvalExprParams &Params,
lspserver::Callback<EvalExprResponse> Reply) {
return EvalExpr(Params, std::move(Reply));
}

void attrpathInfo(const AttrPathInfoParams &Params,
lspserver::Callback<AttrPathInfoResponse> Reply) {
AttrPathInfo(Params, std::move(Reply));
}

void attrpathComplete(const AttrPathCompleteParams &Params,
lspserver::Callback<AttrPathCompleteResponse> Reply) {
AttrPathComplete(Params, std::move(Reply));
}

/// Get executable path for launching the server.
/// \returns null terminated string.
static const char *getExe();
};

class AttrSetClientProc {
StreamProc Proc;
AttrSetClient Client;
std::thread Input;

public:
/// \brief Check if the process is still alive
/// \returns nullptr if it has been dead.
AttrSetClient *client();
~AttrSetClientProc() {
Client.closeInbound();
Input.join();
}

/// \see StreamProc::StreamProc
AttrSetClientProc(const std::function<int()> &Action);
};

} // namespace nixd
11 changes: 9 additions & 2 deletions nixd/include/nixd/Support/ForkPiped.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
#pragma once

namespace nixd::util {
namespace nixd {

/// \brief fork this process and create some pipes connected to the new process.
///
/// stdin, stdout, stderr in the new process will be closed, and these fds could
/// be used to communicate with it.
///
/// \returns pid of child process, in parent.
/// \returns 0 in child.
int forkPiped(int &In, int &Out, int &Err);

} // namespace nixd::util
} // namespace nixd
25 changes: 25 additions & 0 deletions nixd/include/nixd/Support/StreamProc.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#pragma once

#include "nixd/Support/PipedProc.h"

#include <llvm/Support/raw_ostream.h>
#include <lspserver/Connection.h>

namespace nixd {

struct StreamProc {
std::unique_ptr<util::PipedProc> Proc;
std::unique_ptr<llvm::raw_fd_ostream> Stream;

/// \brief Launch a streamed process with \p Action.
///
/// The value returned by \p Action will be interpreted as process's exit
/// value.
StreamProc(const std::function<int()> &Action);

[[nodiscard]] std::unique_ptr<lspserver::InboundPort> mkIn() const;

[[nodiscard]] std::unique_ptr<lspserver::OutboundPort> mkOut() const;
};

} // namespace nixd
3 changes: 3 additions & 0 deletions nixd/lib/CommandLine/Options.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#include "nixd/CommandLine/Options.h"

llvm::cl::OptionCategory nixd::NixdCategory("nixd library options");
52 changes: 52 additions & 0 deletions nixd/lib/Controller/AST.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#include "AST.h"

using namespace nixf;

[[nodiscard]] const EnvNode *nixd::upEnv(const nixf::Node &Desc,
const VariableLookupAnalysis &VLA,
const ParentMapAnalysis &PM) {
const nixf::Node *N = &Desc; // @nonnull
while (!VLA.env(N) && PM.query(*N) && !PM.isRoot(*N))
N = PM.query(*N);
assert(N);
return VLA.env(N);
}

bool nixd::havePackageScope(const Node &N, const VariableLookupAnalysis &VLA,
const ParentMapAnalysis &PM) {
// Firstly find the first "env" enclosed with this variable.
const EnvNode *Env = upEnv(N, VLA, PM);
if (!Env)
return false;

// Then, search up until there are some `with`.
for (; Env; Env = Env->parent()) {
if (!Env->isWith())
continue;
// this env is "with" expression.
const Node *With = Env->syntax();
assert(With && With->kind() == Node::NK_ExprWith);
const Node *WithBody = static_cast<const ExprWith &>(*With).with();
if (!WithBody)
continue; // skip incomplete with epxression.

// Se if it is "ExprVar“. Stupid.
if (WithBody->kind() != Node::NK_ExprVar)
continue;

// Hardcoded "pkgs", even more stupid.
if (static_cast<const ExprVar &>(*WithBody).id().name() == "pkgs")
return true;
}
return false;
}

std::pair<std::vector<std::string>, std::string>
nixd::getScopeAndPrefix(const Node &N, const ParentMapAnalysis &PM) {
if (N.kind() != Node::NK_Identifer)
return {};

// FIXME: impl scoped packages
std::string Prefix = static_cast<const Identifier &>(N).name();
return {{}, Prefix};
}
32 changes: 32 additions & 0 deletions nixd/lib/Controller/AST.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/// \file
/// \brief This file declares some common analysis (tree walk) on the AST.

#include <nixf/Sema/ParentMap.h>
#include <nixf/Sema/VariableLookup.h>

namespace nixd {

/// \brief Search up until there are some node associated with "EnvNode".
[[nodiscard]] const nixf::EnvNode *
upEnv(const nixf::Node &Desc, const nixf::VariableLookupAnalysis &VLA,
const nixf::ParentMapAnalysis &PM);

/// \brief Determine whether or not some node has enclosed "with pkgs; [ ]"
///
/// Yes, this evaluation isn't flawless. What if the identifier isn't "pkgs"? We
/// can't dynamically evaluate everything each time and invalidate them
/// immediately after document updates. Therefore, this heuristic method
/// represents a trade-off between performance considerations.
[[nodiscard]] bool havePackageScope(const nixf::Node &N,
const nixf::VariableLookupAnalysis &VLA,
const nixf::ParentMapAnalysis &PM);

/// \brief get variable scope, and it's prefix name.
///
/// Nixpkgs has some packages scoped in "nested" attrs.
/// e.g. llvmPackages, pythonPackages.
/// Try to find these name as a pre-selected scope, the last value is "prefix".
std::pair<std::vector<std::string>, std::string>
getScopeAndPrefix(const nixf::Node &N, const nixf::ParentMapAnalysis &PM);

} // namespace nixd
Loading

0 comments on commit f510da2

Please sign in to comment.