From f05b086b34e67e1fe6ed59116dddf3cbdfdfe4ec Mon Sep 17 00:00:00 2001 From: Yingchi Long Date: Tue, 19 Nov 2024 16:43:02 +0800 Subject: [PATCH] nixd/tools/nixd-attrset-eval: support completion for "builtins" Basic support for option completion on the worker process. This is the premise for doing this in LSP. --- libnixt/include/nixt/Value.h | 5 ++ nixd/include/nixd/Eval/AttrSetProvider.h | 8 ++ nixd/include/nixd/Protocol/AttrSet.h | 20 +++++ nixd/lib/Eval/AttrSetProvider.cpp | 87 +++++++++++++------ nixd/lib/Protocol/AttrSet.cpp | 16 ++++ .../test/builtin-completion.md | 67 ++++++++++++++ .../nixd-attrset-eval/test/builtin-info.md | 36 ++++++++ 7 files changed, 214 insertions(+), 25 deletions(-) create mode 100644 nixd/tools/nixd-attrset-eval/test/builtin-completion.md create mode 100644 nixd/tools/nixd-attrset-eval/test/builtin-info.md diff --git a/libnixt/include/nixt/Value.h b/libnixt/include/nixt/Value.h index bbcc58893..89887a598 100644 --- a/libnixt/include/nixt/Value.h +++ b/libnixt/include/nixt/Value.h @@ -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 diff --git a/nixd/include/nixd/Eval/AttrSetProvider.h b/nixd/include/nixd/Eval/AttrSetProvider.h index 22ac5104d..d69267024 100644 --- a/nixd/include/nixd/Eval/AttrSetProvider.h +++ b/nixd/include/nixd/Eval/AttrSetProvider.h @@ -66,6 +66,14 @@ class AttrSetProvider : public lspserver::LSPServer { /// FIXME: suppport list names. i.e. `foo.*.submodule` void onOptionComplete(const AttrPathCompleteParams &Params, lspserver::Callback Reply); + + /// \brief Provide information on builtin values. + void onBuiltinInfo(const AttrPathInfoParams &AttrPath, + lspserver::Callback Reply); + + /// \brief Complete builtin values. + void onBuiltinComplete(const AttrPathCompleteParams &Params, + lspserver::Callback Reply); }; } // namespace nixd diff --git a/nixd/include/nixd/Protocol/AttrSet.h b/nixd/include/nixd/Protocol/AttrSet.h index 983d463a2..b7fa83fe7 100644 --- a/nixd/include/nixd/Protocol/AttrSet.h +++ b/nixd/include/nixd/Protocol/AttrSet.h @@ -3,6 +3,7 @@ #pragma once +#include #include #include #include @@ -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 @@ -136,4 +144,16 @@ using OptionInfoResponse = OptionDescription; using OptionCompleteResponse = std::vector; +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; + } // namespace nixd diff --git a/nixd/lib/Eval/AttrSetProvider.cpp b/nixd/lib/Eval/AttrSetProvider.cpp index dd01a23f7..d2ba4203f 100644 --- a/nixd/lib/Eval/AttrSetProvider.cpp +++ b/nixd/lib/Eval/AttrSetProvider.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -156,6 +157,30 @@ void fillOptionDescription(nix::EvalState &State, nix::Value &V, } } +std::vector completeNames(nix::Value &Scope, + const nix::EvalState &State, + std::string_view Prefix) { + int Num = 0; + std::vector 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 In, @@ -172,6 +197,10 @@ AttrSetProvider::AttrSetProvider(std::unique_ptr 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( @@ -227,33 +256,11 @@ void AttrSetProvider::onAttrPathComplete( return; } - std::vector 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())); } } @@ -339,3 +346,33 @@ void AttrSetProvider::onOptionComplete( return; } } +void nixd::AttrSetProvider::onBuiltinInfo( + const AttrPathInfoParams &AttrPath, + lspserver::Callback Reply) { + return Reply([&]() -> llvm::Expected { + 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(Doc->arity), + }; + } + }()); +} + +void nixd::AttrSetProvider::onBuiltinComplete( + const AttrPathCompleteParams &Params, + lspserver::Callback Reply) { + return Reply([&]() -> llvm::Expected { + auto &Builtins = nixt::getBuiltins(state()); + auto &Scope = nixt::selectStrings(state(), Builtins, Params.Scope); + return completeNames(Scope, state(), Params.Prefix); + }()); +} diff --git a/nixd/lib/Protocol/AttrSet.cpp b/nixd/lib/Protocol/AttrSet.cpp index b7220ddc4..5c525a06b 100644 --- a/nixd/lib/Protocol/AttrSet.cpp +++ b/nixd/lib/Protocol/AttrSet.cpp @@ -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) // + ; +} diff --git a/nixd/tools/nixd-attrset-eval/test/builtin-completion.md b/nixd/tools/nixd-attrset-eval/test/builtin-completion.md new file mode 100644 index 000000000..3d08f0259 --- /dev/null +++ b/nixd/tools/nixd-attrset-eval/test/builtin-completion.md @@ -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"} +``` + diff --git a/nixd/tools/nixd-attrset-eval/test/builtin-info.md b/nixd/tools/nixd-attrset-eval/test/builtin-info.md new file mode 100644 index 000000000..68812784e --- /dev/null +++ b/nixd/tools/nixd-attrset-eval/test/builtin-info.md @@ -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"} +``` +