diff --git a/nixd/lib/Controller/Hover.cpp b/nixd/lib/Controller/Hover.cpp index 597240483..64f5656fa 100644 --- a/nixd/lib/Controller/Hover.cpp +++ b/nixd/lib/Controller/Hover.cpp @@ -22,6 +22,30 @@ using namespace lspserver; namespace { +class OptionsHoverProvider { + AttrSetClient &Client; + +public: + OptionsHoverProvider(AttrSetClient &Client) : Client(Client) {} + std::optional + resolveHover(const std::vector &Scope) { + std::binary_semaphore Ready(0); + std::optional Desc; + auto OnReply = [&Ready, &Desc](llvm::Expected Resp) { + if (Resp) + Desc = *Resp; + else + elog("options hover: {0}", Resp.takeError()); + Ready.release(); + }; + + Client.optionInfo(Scope, std::move(OnReply)); + Ready.acquire(); + + return Desc; + } +}; + /// \brief Provide package information, library information ... , from nixpkgs. class NixpkgsHoverProvider { AttrSetClient &NixpkgsClient; @@ -46,8 +70,7 @@ class NixpkgsHoverProvider { } if (Package.Description) { - OS << "## Description" - << "\n\n"; + OS << "## Description" << "\n\n"; OS << *Package.Description; OS << "\n\n"; @@ -122,6 +145,41 @@ void Controller::onHover(const TextDocumentPositionParams &Params, return; } } + + std::vector Scope; + auto R = findAttrPath(*N, PM, Scope); + if (R == FindAttrPathResult::OK) { + std::lock_guard _(OptionsLock); + for (const auto &[_, Client] : Options) { + if (AttrSetClient *C = Client->client()) { + OptionsHoverProvider OHP(*C); + std::optional Desc = OHP.resolveHover(Scope); + std::string Docs; + if (Desc) { + if (Desc->Type) { + std::string TypeName = Desc->Type->Name.value_or(""); + std::string TypeDesc = Desc->Type->Description.value_or(""); + Docs += llvm::formatv("{0} ({1})", TypeName, TypeDesc); + } else { + Docs += "? (missing type)"; + } + if (Desc->Description) { + Docs += "\n\n" + Desc->Description.value_or(""); + } + Reply(Hover{ + .contents = + MarkupContent{ + .kind = MarkupKind::Markdown, + .value = std::move(Docs), + }, + .range = toLSPRange(N->range()), + }); + return; + } + } + } + } + // Reply it's kind by static analysis // FIXME: support more. Reply(Hover{ diff --git a/nixd/tools/nixd/test/hover-options.md b/nixd/tools/nixd/test/hover-options.md new file mode 100644 index 000000000..87e99ec00 --- /dev/null +++ b/nixd/tools/nixd/test/hover-options.md @@ -0,0 +1,84 @@ +# RUN: nixd --lit-test \ +# RUN: --nixos-options-expr="{ foo.bar = { _type = \"option\"; description = \"test\"; type.description = \"hello type\"; }; }" \ +# 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":"{ foo.bar = 1 }" + } + } +} +``` + +<-- textDocument/hover(2) + + +```json +{ + "jsonrpc":"2.0", + "id":2, + "method":"textDocument/hover", + "params":{ + "textDocument":{ + "uri":"file:///basic.nix" + }, + "position":{ + "line":0, + "character":6 + } + } +} +``` + +``` + CHECK: "id": 2, +CHECK-NEXT: "jsonrpc": "2.0", +CHECK-NEXT: "result": { +CHECK-NEXT: "contents": { +CHECK-NEXT: "kind": "markdown", +CHECK-NEXT: "value": " (hello type)\n\ntest" +CHECK-NEXT: }, +CHECK-NEXT: "range": { +CHECK-NEXT: "end": { +CHECK-NEXT: "character": 9, +CHECK-NEXT: "line": 0 +CHECK-NEXT: }, +CHECK-NEXT: "start": { +CHECK-NEXT: "character": 6, +CHECK-NEXT: "line": 0 +CHECK-NEXT: } +CHECK-NEXT: } +CHECK-NEXT: } +``` + +```json +{"jsonrpc":"2.0","method":"exit"} +```