diff --git a/nixd/include/nixd/Protocol/AttrSet.h b/nixd/include/nixd/Protocol/AttrSet.h index aa379045d..983d463a2 100644 --- a/nixd/include/nixd/Protocol/AttrSet.h +++ b/nixd/include/nixd/Protocol/AttrSet.h @@ -10,6 +10,18 @@ #include #include +// https://github.com/NixOS/nix/issues/11136 +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdocumentation" +#endif + +#include + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + namespace nixd { namespace rpcMethod { @@ -44,12 +56,40 @@ struct PackageDescription { std::optional Homepage; }; -using AttrPathInfoResponse = PackageDescription; - llvm::json::Value toJSON(const PackageDescription &Params); bool fromJSON(const llvm::json::Value &Params, PackageDescription &R, llvm::json::Path P); +/// \brief General metadata of all `nix::Value`s +struct ValueMeta { + /// \brief Type of this value. + int Type; + + /// \brief Location of the value. + /// + /// This presence of this value is determined by the nix evaluator. + /// In nix 2.19.x and later: + /// 1. It is available only for attribute sets and lambdas. + /// 2. There is no practical "range" information, only the starting point. + std::optional Location; +}; + +llvm::json::Value toJSON(const ValueMeta &Params); +bool fromJSON(const llvm::json::Value &Params, ValueMeta &R, + llvm::json::Path P); + +struct AttrPathInfoResponse { + /// \brief General value description + ValueMeta Meta; + + /// \brief Package description of the attribute path, if available. + PackageDescription PackageDesc; +}; + +llvm::json::Value toJSON(const AttrPathInfoResponse &Params); +bool fromJSON(const llvm::json::Value &Params, AttrPathInfoResponse &R, + llvm::json::Path P); + struct AttrPathCompleteParams { Selector Scope; /// \brief Search for packages prefixed with this "prefix" diff --git a/nixd/lib/Controller/Completion.cpp b/nixd/lib/Controller/Completion.cpp index 05be65048..6ec35bcbd 100644 --- a/nixd/lib/Controller/Completion.cpp +++ b/nixd/lib/Controller/Completion.cpp @@ -9,6 +9,7 @@ #include "lspserver/Protocol.h" #include "nixd/Controller/Controller.h" +#include "nixd/Protocol/AttrSet.h" #include @@ -105,7 +106,7 @@ class NixpkgsCompletionProvider { void resolvePackage(std::vector Scope, std::string Name, CompletionItem &Item) { std::binary_semaphore Ready(0); - PackageDescription Desc; + AttrPathInfoResponse Desc; auto OnReply = [&Ready, &Desc](llvm::Expected Resp) { if (Resp) Desc = *Resp; @@ -115,12 +116,13 @@ class NixpkgsCompletionProvider { NixpkgsClient.attrpathInfo(Scope, std::move(OnReply)); Ready.acquire(); // Format "detail" and document. + const PackageDescription &PD = Desc.PackageDesc; Item.documentation = MarkupContent{ .kind = MarkupKind::Markdown, - .value = Desc.Description.value_or("") + "\n\n" + - Desc.LongDescription.value_or(""), + .value = PD.Description.value_or("") + "\n\n" + + PD.LongDescription.value_or(""), }; - Item.detail = Desc.Version.value_or("?"); + Item.detail = PD.Version.value_or("?"); } /// \brief Ask nixpkgs provider, give us a list of names. (thunks) diff --git a/nixd/lib/Controller/Definition.cpp b/nixd/lib/Controller/Definition.cpp index 7ab68f419..5d20f5444 100644 --- a/nixd/lib/Controller/Definition.cpp +++ b/nixd/lib/Controller/Definition.cpp @@ -8,6 +8,7 @@ #include "Convert.h" #include "nixd/Controller/Controller.h" +#include "nixd/Protocol/AttrSet.h" #include @@ -140,7 +141,7 @@ class NixpkgsDefinitionProvider { Expected resolvePackage(std::vector Scope, std::string Name) { std::binary_semaphore Ready(0); - Expected Desc = error("not replied"); + Expected Desc = error("not replied"); auto OnReply = [&Ready, &Desc](llvm::Expected Resp) { if (Resp) Desc = *Resp; @@ -155,8 +156,7 @@ class NixpkgsDefinitionProvider { if (!Desc) return Desc.takeError(); - const std::optional &Position = Desc->Position; - + const std::optional &Position = Desc->PackageDesc.Position; if (!Position) return error("meta.position is not available for this package"); diff --git a/nixd/lib/Controller/Hover.cpp b/nixd/lib/Controller/Hover.cpp index 64f5656fa..cc72f92db 100644 --- a/nixd/lib/Controller/Hover.cpp +++ b/nixd/lib/Controller/Hover.cpp @@ -7,6 +7,7 @@ #include "Convert.h" #include "nixd/Controller/Controller.h" +#include "nixd/Protocol/AttrSet.h" #include @@ -91,7 +92,7 @@ class NixpkgsHoverProvider { std::optional resolvePackage(std::vector Scope, std::string Name) { std::binary_semaphore Ready(0); - std::optional Desc; + std::optional Desc; auto OnReply = [&Ready, &Desc](llvm::Expected Resp) { if (Resp) Desc = *Resp; @@ -106,7 +107,7 @@ class NixpkgsHoverProvider { if (!Desc) return std::nullopt; - return mkMarkdown(*Desc); + return mkMarkdown(Desc->PackageDesc); } }; diff --git a/nixd/lib/Controller/InlayHints.cpp b/nixd/lib/Controller/InlayHints.cpp index cf8da2cfb..58bab19c3 100644 --- a/nixd/lib/Controller/InlayHints.cpp +++ b/nixd/lib/Controller/InlayHints.cpp @@ -86,11 +86,11 @@ class NixpkgsInlayHintsProvider { NixpkgsProvider.attrpathInfo({Name}, std::move(OnReply)); Ready.acquire(); - if (R.Version) { + if (const std::optional &Version = R.PackageDesc.Version) { // Construct inlay hints. InlayHint H{ .position = toLSPPosition(N->rCur()), - .label = ": " + *R.Version, + .label = ": " + *Version, .kind = InlayHintKind::Designator, .range = toLSPRange(N->range()), }; diff --git a/nixd/lib/Eval/AttrSetProvider.cpp b/nixd/lib/Eval/AttrSetProvider.cpp index 31289dc69..963ffdf8b 100644 --- a/nixd/lib/Eval/AttrSetProvider.cpp +++ b/nixd/lib/Eval/AttrSetProvider.cpp @@ -1,6 +1,10 @@ #include "nixd/Eval/AttrSetProvider.h" +#include "nixd/Protocol/AttrSet.h" + +#include "lspserver/Protocol.h" #include +#include #include #include @@ -24,8 +28,9 @@ void fillString(nix::EvalState &State, nix::Value &V, } } -void fillPackageDescription(nix::EvalState &State, nix::Value &Package, - PackageDescription &R) { +/// Describe the value as if \p Package is actually a nixpkgs package. +PackageDescription describePackage(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); @@ -33,6 +38,36 @@ void fillPackageDescription(nix::EvalState &State, nix::Value &Package, fillString(State, Package, {"meta", "longDescription"}, R.LongDescription); fillString(State, Package, {"meta", "position"}, R.Position); fillString(State, Package, {"meta", "homepage"}, R.Homepage); + return R; +} + +std::optional locationOf(nix::PosTable &PTable, nix::Value &V) { + nix::PosIdx P = V.determinePos(nix::noPos); + if (!P) + return std::nullopt; + + nix::Pos NixPos = PTable[P]; + const auto *SP = std::get_if(&NixPos.origin); + + if (!SP) + return std::nullopt; + + Position LPos = { + .line = static_cast(NixPos.line - 1), + .character = static_cast(NixPos.column - 1), + }; + + return Location{ + .uri = URIForFile::canonicalize(SP->path.abs(), SP->path.abs()), + .range = {LPos, LPos}, + }; +} + +ValueMeta metadataOf(nix::EvalState &State, nix::Value &V) { + return { + .Type = V.type(true), + .Location = locationOf(State.positions, V), + }; } void fillUnsafeGetAttrPosLocation(nix::EvalState &State, nix::Value &V, @@ -158,26 +193,24 @@ void AttrSetProvider::onEvalExpr( void AttrSetProvider::onAttrPathInfo( const AttrPathInfoParams &AttrPath, lspserver::Callback Reply) { - try { - if (AttrPath.empty()) { - Reply(error("attrpath is empty!")); - return; + using RespT = AttrPathInfoResponse; + Reply([&]() -> llvm::Expected { + try { + if (AttrPath.empty()) + return error("attrpath is empty!"); + + nix::Value &V = nixt::selectStrings(state(), Nixpkgs, AttrPath); + state().forceValue(V, nix::noPos); + return RespT{ + .Meta = metadataOf(state(), V), + .PackageDesc = describePackage(state(), V), + }; + } catch (const nix::BaseError &Err) { + return error(Err.info().msg.str()); + } catch (const std::exception &Err) { + return error(Err.what()); } - - 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( diff --git a/nixd/lib/Protocol/AttrSet.cpp b/nixd/lib/Protocol/AttrSet.cpp index 278b4bd38..b7220ddc4 100644 --- a/nixd/lib/Protocol/AttrSet.cpp +++ b/nixd/lib/Protocol/AttrSet.cpp @@ -77,6 +77,38 @@ bool nixd::fromJSON(const llvm::json::Value &Params, PackageDescription &R, ; } +Value nixd::toJSON(const ValueMeta &Params) { + return Object{ + {"Type", Params.Type}, + {"Location", Params.Location}, + }; +} + +bool nixd::fromJSON(const llvm::json::Value &Params, ValueMeta &R, + llvm::json::Path P) { + ObjectMapper O(Params, P); + return O // + && O.map("Type", R.Type) // + && O.mapOptional("Location", R.Location) // + ; +} + +Value nixd::toJSON(const AttrPathInfoResponse &Params) { + return Object{ + {"Meta", Params.Meta}, + {"PackageDesc", Params.PackageDesc}, + }; +} + +bool nixd::fromJSON(const llvm::json::Value &Params, AttrPathInfoResponse &R, + llvm::json::Path P) { + ObjectMapper O(Params, P); + return O // + && O.map("Meta", R.Meta) // + && O.mapOptional("PackageDesc", R.PackageDesc) // + ; +} + Value nixd::toJSON(const AttrPathCompleteParams &Params) { return Object{{"Scope", Params.Scope}, {"Prefix", Params.Prefix}}; } diff --git a/nixd/tools/nixd-attrset-eval/test/attrs-info.md b/nixd/tools/nixd-attrset-eval/test/attrs-info.md index 001e07e07..afcd93872 100644 --- a/nixd/tools/nixd-attrset-eval/test/attrs-info.md +++ b/nixd/tools/nixd-attrset-eval/test/attrs-info.md @@ -21,10 +21,16 @@ ``` ``` - CHECK: "id": 1, + CHECK: "id": 1, CHECK-NEXT: "jsonrpc": "2.0", CHECK-NEXT: "result": { -CHECK-NEXT: "Description": "A program that produces a familiar, friendly greeting", +CHECK-NEXT: "Meta": { +CHECK-NEXT: "Location": null, +CHECK-NEXT: "Type": 7 +CHECK-NEXT: }, +CHECK-NEXT: "PackageDesc": { +CHECK-NEXT: "Description": "A program that produces a familiar, friendly greeting", +CHECK-NEXT: "Homepage": null, ``` ```json