-
-
Notifications
You must be signed in to change notification settings - Fork 28
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
nixd: introduce attrset provider (#394)
- Loading branch information
Showing
11 changed files
with
558 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
/// \file | ||
/// \brief Dedicated worker for evaluating attrset. | ||
/// | ||
/// Motivation: eval things in attrset (e.g. nixpkgs). (packages, functions ...) | ||
/// | ||
/// Observation: | ||
/// 1. Fast: eval (import <nixpkgs> { }). | ||
/// 2. Slow: eval a whole NixOS config until some node is being touched. | ||
/// | ||
/// This worker is designed to answer "packages"/"functions" in nixpkgs, but not | ||
/// limited to it. | ||
/// | ||
/// For time-saving: | ||
/// Once a value is evaluated. It basically assume it will not change. | ||
/// That is, any workspace editing will not invalidate the value. | ||
/// | ||
|
||
#pragma once | ||
|
||
#include "nixd/Protocol/AttrSet.h" | ||
|
||
#include "lspserver/LSPServer.h" | ||
|
||
#include <nix/eval.hh> | ||
|
||
#include <memory> | ||
|
||
namespace nixd { | ||
|
||
/// \brief Main RPC class for attrset provider. | ||
class AttrSetProvider : public lspserver::LSPServer { | ||
|
||
std::unique_ptr<nix::EvalState> State; | ||
|
||
nix::Value Nixpkgs; | ||
|
||
/// Convenient method for get state. Basically assume this->State is not null | ||
nix::EvalState &state() { | ||
assert(State && "State should be allocated by ctor!"); | ||
return *State; | ||
} | ||
|
||
public: | ||
AttrSetProvider(std::unique_ptr<lspserver::InboundPort> In, | ||
std::unique_ptr<lspserver::OutboundPort> Out); | ||
|
||
/// \brief Eval an expression, use it for furthur requests. | ||
void onEvalExpr(const EvalExprParams &Name, | ||
lspserver::Callback<EvalExprResponse> Reply); | ||
|
||
/// \brief Query attrpath information. | ||
void onAttrPathInfo(const AttrPathInfoParams &AttrPath, | ||
lspserver::Callback<AttrPathInfoResponse> Reply); | ||
|
||
/// \brief Complete attrpath entries. | ||
void onAttrPathComplete(const AttrPathCompleteParams &Params, | ||
lspserver::Callback<AttrPathCompleteResponse> Reply); | ||
}; | ||
|
||
} // namespace nixd |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
/// \file | ||
/// \brief Types used in nixpkgs provider. | ||
|
||
#pragma once | ||
|
||
#include <optional> | ||
#include <string> | ||
#include <vector> | ||
|
||
#include <llvm/Support/JSON.h> | ||
|
||
namespace nixd { | ||
|
||
using EvalExprParams = std::string; | ||
using EvalExprResponse = std::optional<std::string>; | ||
|
||
using AttrPathInfoParams = std::vector<std::string>; | ||
|
||
struct PackageDescription { | ||
std::optional<std::string> Name; | ||
std::optional<std::string> PName; | ||
std::optional<std::string> Version; | ||
std::optional<std::string> Description; | ||
std::optional<std::string> LongDescription; | ||
std::optional<std::string> Position; | ||
std::optional<std::string> Homepage; | ||
}; | ||
|
||
using AttrPathInfoResponse = PackageDescription; | ||
|
||
llvm::json::Value toJSON(const PackageDescription &Params); | ||
bool fromJSON(const llvm::json::Value &Params, PackageDescription &R, | ||
llvm::json::Path P); | ||
|
||
struct AttrPathCompleteParams { | ||
std::vector<std::string> Scope; | ||
/// \brief Search for packages prefixed with this "prefix" | ||
std::string Prefix; | ||
}; | ||
|
||
llvm::json::Value toJSON(const AttrPathCompleteParams &Params); | ||
bool fromJSON(const llvm::json::Value &Params, AttrPathCompleteParams &R, | ||
llvm::json::Path P); | ||
|
||
using AttrPathCompleteResponse = std::vector<std::string>; | ||
|
||
} // namespace nixd |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
#include "nixd/Eval/AttrSetProvider.h" | ||
|
||
#include <nix/attr-path.hh> | ||
#include <nix/store-api.hh> | ||
#include <nixt/Value.h> | ||
|
||
using namespace nixd; | ||
using namespace lspserver; | ||
|
||
namespace { | ||
|
||
void fillString(nix::EvalState &State, nix::Value &V, | ||
const std::vector<std::string_view> &AttrPath, | ||
std::optional<std::string> &Field) { | ||
try { | ||
nix::Value &Select = nixt::selectStringViews(State, V, AttrPath); | ||
State.forceValue(Select, nix::noPos); | ||
if (Select.type() == nix::ValueType::nString) | ||
Field = Select.string.c_str; | ||
} catch (std::exception &E) { | ||
Field = std::nullopt; | ||
} | ||
} | ||
|
||
void fillPackageDescription(nix::EvalState &State, nix::Value &Package, | ||
PackageDescription &R) { | ||
fillString(State, Package, {"name"}, R.Name); | ||
fillString(State, Package, {"pname"}, R.PName); | ||
fillString(State, Package, {"version"}, R.Version); | ||
fillString(State, Package, {"meta", "description"}, R.Description); | ||
fillString(State, Package, {"meta", "longDescription"}, R.LongDescription); | ||
fillString(State, Package, {"meta", "position"}, R.Position); | ||
fillString(State, Package, {"meta", "homepage"}, R.Homepage); | ||
} | ||
|
||
} // namespace | ||
|
||
AttrSetProvider::AttrSetProvider(std::unique_ptr<InboundPort> In, | ||
std::unique_ptr<OutboundPort> Out) | ||
: LSPServer(std::move(In), std::move(Out)), | ||
State(new nix::EvalState({}, nix::openStore())) { | ||
Registry.addMethod("attrset/evalExpr", this, &AttrSetProvider::onEvalExpr); | ||
Registry.addMethod("attrset/attrpathInfo", this, | ||
&AttrSetProvider::onAttrPathInfo); | ||
Registry.addMethod("attrset/attrpathComplete", this, | ||
&AttrSetProvider::onAttrPathComplete); | ||
} | ||
|
||
void AttrSetProvider::onEvalExpr( | ||
const std::string &Name, | ||
lspserver::Callback<std::optional<std::string>> Reply) { | ||
try { | ||
nix::Expr *AST = state().parseExprFromString( | ||
Name, state().rootPath(nix::CanonPath::fromCwd())); | ||
state().eval(AST, Nixpkgs); | ||
Reply(std::nullopt); | ||
return; | ||
} catch (const nix::BaseError &Err) { | ||
Reply(error(Err.info().msg.str())); | ||
return; | ||
} catch (const std::exception &Err) { | ||
Reply(error(Err.what())); | ||
return; | ||
} | ||
} | ||
|
||
void AttrSetProvider::onAttrPathInfo( | ||
const AttrPathInfoParams &AttrPath, | ||
lspserver::Callback<AttrPathInfoResponse> Reply) { | ||
try { | ||
if (AttrPath.empty()) { | ||
Reply(error("attrpath is empty!")); | ||
return; | ||
} | ||
|
||
nix::Value &Package = nixt::selectStrings(state(), Nixpkgs, AttrPath); | ||
|
||
AttrPathInfoResponse R; | ||
fillPackageDescription(state(), Package, R); | ||
|
||
Reply(std::move(R)); | ||
return; | ||
} catch (const nix::BaseError &Err) { | ||
Reply(error(Err.info().msg.str())); | ||
return; | ||
} catch (const std::exception &Err) { | ||
Reply(error(Err.what())); | ||
return; | ||
} | ||
} | ||
|
||
void AttrSetProvider::onAttrPathComplete( | ||
const AttrPathCompleteParams &Params, | ||
lspserver::Callback<AttrPathCompleteResponse> Reply) { | ||
try { | ||
nix::Value &Scope = nixt::selectStrings(state(), Nixpkgs, Params.Scope); | ||
|
||
state().forceValue(Scope, nix::noPos); | ||
|
||
if (Scope.type() != nix::ValueType::nAttrs) { | ||
Reply(error("scope is not an attrset")); | ||
return; | ||
} | ||
|
||
std::vector<std::string> Names; | ||
int Num = 0; | ||
|
||
// FIXME: we may want to use "Trie" to speedup the string searching. | ||
// However as my (roughtly) profiling the critical in this loop is | ||
// evaluating package details. | ||
// "Trie"s may not beneficial becausae it cannot speedup eval. | ||
for (const auto *AttrPtr : | ||
Scope.attrs->lexicographicOrder(state().symbols)) { | ||
const nix::Attr &Attr = *AttrPtr; | ||
const std::string Name = state().symbols[Attr.name]; | ||
if (Name.starts_with(Params.Prefix)) { | ||
++Num; | ||
Names.emplace_back(Name); | ||
// We set this a very limited number as to speedup | ||
if (Num > 30) | ||
break; | ||
} | ||
} | ||
Reply(std::move(Names)); | ||
return; | ||
} catch (const nix::BaseError &Err) { | ||
Reply(error(Err.info().msg.str())); | ||
return; | ||
} catch (const std::exception &Err) { | ||
Reply(error(Err.what())); | ||
return; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
#include "nixd/Protocol/AttrSet.h" | ||
|
||
using namespace nixd; | ||
using namespace llvm::json; | ||
|
||
Value nixd::toJSON(const PackageDescription &Params) { | ||
return Object{ | ||
{"Name", Params.Name}, | ||
{"PName", Params.PName}, | ||
{"Version", Params.Version}, | ||
{"Description", Params.Description}, | ||
{"LongDescription", Params.LongDescription}, | ||
{"Position", Params.Position}, | ||
{"Homepage", Params.Homepage}, | ||
}; | ||
} | ||
|
||
bool nixd::fromJSON(const llvm::json::Value &Params, PackageDescription &R, | ||
llvm::json::Path P) { | ||
ObjectMapper O(Params, P); | ||
return O // | ||
&& O.map("Name", R.Name) // | ||
&& O.map("PName", R.PName) // | ||
&& O.map("Version", R.Version) // | ||
&& O.map("Description", R.Description) // | ||
&& O.map("LongDescription", R.LongDescription) // | ||
&& O.map("Position", R.Position) // | ||
&& O.map("Homepage", R.Homepage) // | ||
; | ||
} | ||
|
||
Value nixd::toJSON(const AttrPathCompleteParams &Params) { | ||
return Object{{"Scope", Params.Scope}, {"Prefix", Params.Prefix}}; | ||
} | ||
bool nixd::fromJSON(const llvm::json::Value &Params, AttrPathCompleteParams &R, | ||
llvm::json::Path P) { | ||
ObjectMapper O(Params, P); | ||
return O // | ||
&& O.map("Scope", R.Scope) // | ||
&& O.map("Prefix", R.Prefix) // | ||
; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.