Skip to content

Commit

Permalink
nixd/tools/nixd-attrset-eval: support completion for "builtins"
Browse files Browse the repository at this point in the history
Basic support for option completion on the worker process.
This is the premise for doing this in LSP.
  • Loading branch information
inclyc committed Nov 19, 2024
1 parent 0f3c0c7 commit f05b086
Show file tree
Hide file tree
Showing 7 changed files with 214 additions and 25 deletions.
5 changes: 5 additions & 0 deletions libnixt/include/nixt/Value.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,9 @@ selectStringViews(nix::EvalState &State, nix::Value &V,
return selectSymbols(State, V, toSymbols(State.symbols, AttrPath));
}

/// \brief Get nix's `builtins` constant
inline nix::Value &getBuiltins(const nix::EvalState &State) {
return *State.baseEnv.values[0];
}

} // namespace nixt
8 changes: 8 additions & 0 deletions nixd/include/nixd/Eval/AttrSetProvider.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,14 @@ class AttrSetProvider : public lspserver::LSPServer {
/// FIXME: suppport list names. i.e. `foo.*.submodule`
void onOptionComplete(const AttrPathCompleteParams &Params,
lspserver::Callback<OptionCompleteResponse> Reply);

/// \brief Provide information on builtin values.
void onBuiltinInfo(const AttrPathInfoParams &AttrPath,
lspserver::Callback<BuiltinInfoResponse> Reply);

/// \brief Complete builtin values.
void onBuiltinComplete(const AttrPathCompleteParams &Params,
lspserver::Callback<BuiltinCompleteResponse> Reply);
};

} // namespace nixd
20 changes: 20 additions & 0 deletions nixd/include/nixd/Protocol/AttrSet.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#pragma once

#include <cstdint>
#include <optional>
#include <string>
#include <vector>
Expand Down Expand Up @@ -31,6 +32,13 @@ constexpr inline std::string_view AttrPathInfo = "attrset/attrpathInfo";
constexpr inline std::string_view AttrPathComplete = "attrset/attrpathComplete";
constexpr inline std::string_view OptionInfo = "attrset/optionInfo";
constexpr inline std::string_view OptionComplete = "attrset/optionComplete";

/// \brief ↩︎ Request information
constexpr inline std::string_view BuiltinInfo = "attrset/builtinInfo";

/// \brief ↩︎ Request completion of builtins.
constexpr inline std::string_view BuiltinComplete = "attrset/builtinComplete";

constexpr inline std::string_view Exit = "exit";

} // namespace rpcMethod
Expand Down Expand Up @@ -136,4 +144,16 @@ using OptionInfoResponse = OptionDescription;

using OptionCompleteResponse = std::vector<OptionField>;

struct BuiltinDescription {
std::string Doc;
std::int64_t Arity;
};

llvm::json::Value toJSON(const BuiltinDescription &Params);
bool fromJSON(const llvm::json::Value &Params, BuiltinDescription &R,
llvm::json::Path P);

using BuiltinInfoResponse = BuiltinDescription;
using BuiltinCompleteResponse = std::vector<std::string>;

} // namespace nixd
87 changes: 62 additions & 25 deletions nixd/lib/Eval/AttrSetProvider.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

#include <nix/attr-path.hh>
#include <nix/common-eval-args.hh>
#include <nix/eval.hh>
#include <nix/nixexpr.hh>
#include <nix/store-api.hh>
#include <nixt/Value.h>
Expand Down Expand Up @@ -156,6 +157,30 @@ void fillOptionDescription(nix::EvalState &State, nix::Value &V,
}
}

std::vector<std::string> completeNames(nix::Value &Scope,
const nix::EvalState &State,
std::string_view Prefix) {
int Num = 0;
std::vector<std::string> Names;

// 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_view Name = State.symbols[Attr.name];
if (Name.starts_with(Prefix)) {
++Num;
Names.emplace_back(Name);
// We set this a very limited number as to speedup
if (Num > MaxItems)
break;
}
}
return Names;
}

} // namespace

AttrSetProvider::AttrSetProvider(std::unique_ptr<InboundPort> In,
Expand All @@ -172,6 +197,10 @@ AttrSetProvider::AttrSetProvider(std::unique_ptr<InboundPort> In,
&AttrSetProvider::onOptionInfo);
Registry.addMethod(rpcMethod::OptionComplete, this,
&AttrSetProvider::onOptionComplete);
Registry.addMethod(rpcMethod::BuiltinInfo, this,
&AttrSetProvider::onBuiltinInfo);
Registry.addMethod(rpcMethod::BuiltinComplete, this,
&AttrSetProvider::onBuiltinComplete);
}

void AttrSetProvider::onEvalExpr(
Expand Down Expand Up @@ -227,33 +256,11 @@ void AttrSetProvider::onAttrPathComplete(
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_view 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 > MaxItems)
break;
}
}
Reply(std::move(Names));
return;
return Reply(completeNames(Scope, state(), Params.Prefix));
} catch (const nix::BaseError &Err) {
Reply(error(Err.info().msg.str()));
return;
return Reply(error(Err.info().msg.str()));
} catch (const std::exception &Err) {
Reply(error(Err.what()));
return;
return Reply(error(Err.what()));
}
}

Expand Down Expand Up @@ -339,3 +346,33 @@ void AttrSetProvider::onOptionComplete(
return;
}
}
void nixd::AttrSetProvider::onBuiltinInfo(
const AttrPathInfoParams &AttrPath,
lspserver::Callback<BuiltinDescription> Reply) {
return Reply([&]() -> llvm::Expected<BuiltinDescription> {
auto &Builtins = nixt::getBuiltins(state());
if (AttrPath.empty())
return error("attrpath is empty!");

auto &TheBuiltin = nixt::selectStrings(state(), Builtins, AttrPath);
const auto &Doc = state().getDoc(TheBuiltin);
if (!Doc) {
return error("No documentation available");
} else {
return BuiltinDescription{
.Doc = Doc->doc,
.Arity = static_cast<int64_t>(Doc->arity),
};
}
}());
}

void nixd::AttrSetProvider::onBuiltinComplete(
const AttrPathCompleteParams &Params,
lspserver::Callback<BuiltinCompleteResponse> Reply) {
return Reply([&]() -> llvm::Expected<BuiltinCompleteResponse> {
auto &Builtins = nixt::getBuiltins(state());
auto &Scope = nixt::selectStrings(state(), Builtins, Params.Scope);
return completeNames(Scope, state(), Params.Prefix);
}());
}
16 changes: 16 additions & 0 deletions nixd/lib/Protocol/AttrSet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -120,3 +120,19 @@ bool nixd::fromJSON(const llvm::json::Value &Params, AttrPathCompleteParams &R,
&& O.map("Prefix", R.Prefix) //
;
}

llvm::json::Value nixd::toJSON(const BuiltinDescription &Params) {
return Object{
{"arity", Params.Arity},
{"doc", Params.Doc},
};
}
bool nixd::fromJSON(const llvm::json::Value &Params, BuiltinDescription &R,
llvm::json::Path P) {

ObjectMapper O(Params, P);
return O //
&& O.map("arity", R.Arity) //
&& O.map("doc", R.Doc) //
;
}
67 changes: 67 additions & 0 deletions nixd/tools/nixd-attrset-eval/test/builtin-completion.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# RUN: nixd-attrset-eval --lit-test < %s | FileCheck %s


```json
{
"jsonrpc":"2.0",
"id":0,
"method":"attrset/evalExpr",
"params": "{ py1 = 1; py2 = 2; py3 = 3; }"
}
```


```json
{
"jsonrpc":"2.0",
"id":1,
"method":"attrset/builtinComplete",
"params": {
"Scope": [ ],
"Prefix": ""
}
}
```

```
CHECK: "id": 1,
CHECK-NEXT: "jsonrpc": "2.0",
CHECK-NEXT: "result": [
CHECK-NEXT: "abort",
CHECK-NEXT: "add",
CHECK-NEXT: "addDrvOutputDependencies",
CHECK-NEXT: "addErrorContext",
CHECK-NEXT: "all",
CHECK-NEXT: "any",
CHECK-NEXT: "appendContext",
CHECK-NEXT: "attrNames",
CHECK-NEXT: "attrValues",
CHECK-NEXT: "baseNameOf",
CHECK-NEXT: "bitAnd",
CHECK-NEXT: "bitOr",
CHECK-NEXT: "bitXor",
CHECK-NEXT: "break",
CHECK-NEXT: "builtins",
CHECK-NEXT: "catAttrs",
CHECK-NEXT: "ceil",
CHECK-NEXT: "compareVersions",
CHECK-NEXT: "concatLists",
CHECK-NEXT: "concatMap",
CHECK-NEXT: "concatStringsSep",
CHECK-NEXT: "convertHash",
CHECK-NEXT: "currentSystem",
CHECK-NEXT: "currentTime",
CHECK-NEXT: "deepSeq",
CHECK-NEXT: "derivation",
CHECK-NEXT: "derivationStrict",
CHECK-NEXT: "dirOf",
CHECK-NEXT: "div",
CHECK-NEXT: "elem",
CHECK-NEXT: "elemAt"
CHECK-NEXT: ]
```

```json
{"jsonrpc":"2.0","method":"exit"}
```

36 changes: 36 additions & 0 deletions nixd/tools/nixd-attrset-eval/test/builtin-info.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# RUN: nixd-attrset-eval --lit-test < %s | FileCheck %s


```json
{
"jsonrpc":"2.0",
"id":0,
"method":"attrset/evalExpr",
"params": "{ hello.meta.description = \"A program that produces a familiar, friendly greeting\"; }"
}
```


```json
{
"jsonrpc":"2.0",
"id":1,
"method":"attrset/builtinInfo",
"params": [ "any" ]
}
```

```
CHECK: "id": 1,
CHECK-NEXT: "jsonrpc": "2.0",
CHECK-NEXT: "result": {
CHECK-NEXT: "arity": 2,
CHECK-NEXT: "doc": "\n Return `true` if the function *pred* returns `true` for at least one\n element of *list*, and `false` otherwise.\n "
CHECK-NEXT: }
CHECK-NEXT:}
```

```json
{"jsonrpc":"2.0","method":"exit"}
```

0 comments on commit f05b086

Please sign in to comment.