From 2e51fe055be2894a25fecaee6f1096853a879b84 Mon Sep 17 00:00:00 2001 From: Yingchi Long Date: Fri, 19 Apr 2024 10:20:43 +0800 Subject: [PATCH] nixd: provide package information on hover request (#430) --- nixd/lib/Controller/Hover.cpp | 102 ++++++++++++++++++++++++-- nixd/tools/nixd/test/hover-package.md | 83 +++++++++++++++++++++ 2 files changed, 178 insertions(+), 7 deletions(-) create mode 100644 nixd/tools/nixd/test/hover-package.md diff --git a/nixd/lib/Controller/Hover.cpp b/nixd/lib/Controller/Hover.cpp index fd13e012a..548dc09fa 100644 --- a/nixd/lib/Controller/Hover.cpp +++ b/nixd/lib/Controller/Hover.cpp @@ -3,21 +3,90 @@ /// [Hover Request]: /// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_hover +#include "AST.h" #include "Convert.h" #include "nixd/Controller/Controller.h" -#include - #include -#include +#include -namespace nixd { +#include +using namespace nixd; using namespace llvm::json; +using namespace nixf; using namespace lspserver; -using namespace rpc; + +namespace { + +/// \brief Provide package information, library information ... , from nixpkgs. +class NixpkgsHoverProvider { + AttrSetClient &NixpkgsClient; + + /// \brief Make markdown documentation by package description + /// + /// FIXME: there are many markdown generation in language server. + /// Maybe we can add structured generating first? + static std::string mkMarkdown(const PackageDescription &Package) { + std::ostringstream OS; + // Make each field a new section + + if (Package.Name) { + OS << "`" << *Package.Name << "`"; + OS << "\n"; + } + + // Make links to homepage. + if (Package.Homepage) { + OS << "[homepage](" << *Package.Homepage << ")"; + OS << "\n"; + } + + if (Package.Description) { + OS << "## Description" + << "\n\n"; + OS << *Package.Description; + OS << "\n\n"; + + if (Package.LongDescription) { + OS << "\n\n"; + OS << *Package.LongDescription; + OS << "\n\n"; + } + } + + return OS.str(); + } + +public: + NixpkgsHoverProvider(AttrSetClient &NixpkgsClient) + : NixpkgsClient(NixpkgsClient) {} + + std::optional resolvePackage(std::vector Scope, + std::string Name) { + std::binary_semaphore Ready(0); + std::optional Desc; + auto OnReply = [&Ready, &Desc](llvm::Expected Resp) { + if (Resp) + Desc = *Resp; + else + elog("nixpkgs provider: {0}", Resp.takeError()); + Ready.release(); + }; + Scope.emplace_back(std::move(Name)); + NixpkgsClient.attrpathInfo(Scope, std::move(OnReply)); + Ready.acquire(); + + if (!Desc) + return std::nullopt; + + return mkMarkdown(*Desc); + } +}; + +} // namespace void Controller::onHover(const TextDocumentPositionParams &Params, Callback> Reply) { @@ -33,6 +102,27 @@ void Controller::onHover(const TextDocumentPositionParams &Params, return; } std::string Name = N->name(); + const VariableLookupAnalysis &VLA = *TU->variableLookup(); + const ParentMapAnalysis &PM = *TU->parentMap(); + if (havePackageScope(*N, VLA, PM) && nixpkgsClient()) { + // Ask nixpkgs client what's current package documentation. + NixpkgsHoverProvider NHP(*nixpkgsClient()); + auto [Scope, Name] = getScopeAndPrefix(*N, PM); + if (std::optional Doc = + NHP.resolvePackage(Scope, Name)) { + Reply(Hover{ + .contents = + MarkupContent{ + .kind = MarkupKind::Markdown, + .value = std::move(*Doc), + }, + .range = toLSPRange(N->range()), + }); + return; + } + } + // Reply it's kind by static analysis + // FIXME: support more. Reply(Hover{ .contents = MarkupContent{ @@ -46,5 +136,3 @@ void Controller::onHover(const TextDocumentPositionParams &Params, }; boost::asio::post(Pool, std::move(Action)); } - -} // namespace nixd diff --git a/nixd/tools/nixd/test/hover-package.md b/nixd/tools/nixd/test/hover-package.md new file mode 100644 index 000000000..7762679e2 --- /dev/null +++ b/nixd/tools/nixd/test/hover-package.md @@ -0,0 +1,83 @@ +# RUN: nixd --lit-test \ +# RUN: --nixpkgs-expr="{ hello.meta.description = \"Very Nice\"; }" \ +# RUN: < %s | FileCheck %s + + +<-- initialize(0) + +```json +{ + "jsonrpc":"2.0", + "id":0, + "method":"initialize", + "params":{ + "processId":123, + "rootPath":"", + "capabilities":{ + }, + "trace":"off" + } +} +``` + + +<-- textDocument/didOpen + +```json +{ + "jsonrpc":"2.0", + "method":"textDocument/didOpen", + "params":{ + "textDocument":{ + "uri":"file:///basic.nix", + "languageId":"nix", + "version":1, + "text":"with pkgs; hello" + } + } +} +``` + +<-- textDocument/hover(2) + + +```json +{ + "jsonrpc":"2.0", + "id":2, + "method":"textDocument/hover", + "params":{ + "textDocument":{ + "uri":"file:///basic.nix" + }, + "position":{ + "line":0, + "character":16 + } + } +} +``` + +``` + CHECK: "id": 2, +CHECK-NEXT: "jsonrpc": "2.0", +CHECK-NEXT: "result": { +CHECK-NEXT: "contents": { +CHECK-NEXT: "kind": "markdown", +CHECK-NEXT: "value": "## Description\n\nVery Nice\n\n" +CHECK-NEXT: }, +CHECK-NEXT: "range": { +CHECK-NEXT: "end": { +CHECK-NEXT: "character": 17, +CHECK-NEXT: "line": 0 +CHECK-NEXT: }, +CHECK-NEXT: "start": { +CHECK-NEXT: "character": 12, +CHECK-NEXT: "line": 0 +CHECK-NEXT: } +CHECK-NEXT: } +``` + +```json +{"jsonrpc":"2.0","method":"exit"} +```