diff --git a/nixd/lib/Controller/InlayHints.cpp b/nixd/lib/Controller/InlayHints.cpp index 2938e0af3..58c4d1f7a 100644 --- a/nixd/lib/Controller/InlayHints.cpp +++ b/nixd/lib/Controller/InlayHints.cpp @@ -69,42 +69,66 @@ class NixpkgsInlayHintsProvider { } void dfs(const Node *N) { + using VariableLookupAnalysis::LookupResultKind::Defined; + using Params = AttrPathInfoParams; if (!N) return; - if (N->kind() == Node::NK_ExprVar) { - if (havePackageScope(*N, VLA, PMA)) { - if (!rangeOK(N->positionRange())) - return; - // Ask nixpkgs eval to provide it's information. - // This is relatively slow. Maybe better query a set of packages in the - // future? - std::binary_semaphore Ready(0); - const std::string &Name = static_cast(*N).id().name(); - AttrPathInfoResponse R; - auto OnReply = [&Ready, &R](llvm::Expected Resp) { - if (!Resp) { - Ready.release(); - return; - } - R = *Resp; + + // Build the attribute path info selector. + const auto Selector = [&]() -> Params { + if (!havePackageScope(*N, VLA, PMA) || !rangeOK(N->positionRange())) + return {}; + switch (N->kind()) { + default: + return {}; + case Node::NK_ExprVar: { + const auto &Var = static_cast(*N); + const std::string &Name = Var.id().name(); + if (Name == nixd::idioms::Lib) // Skip "lib" + return {}; + const auto &Lookup = VLA.query(Var); + return (Lookup.Kind != Defined) ? Params{Name} : Params{}; + } + case Node::NK_ExprSelect: { + const auto &Sel = static_cast(*N); + Params P; + P.emplace_back(Sel.expr().src(Src)); + for (const auto &Name : Sel.path()->names()) + P.emplace_back(Name->src(Src)); + return P; + } + } + }(); + + // Ask nixpkgs eval to provide it's information. + // This is relatively slow. Maybe better query a set of packages in the + // future? + if (!Selector.empty()) { + std::binary_semaphore Ready(0); + AttrPathInfoResponse R; + auto OnReply = [&Ready, &R](llvm::Expected Resp) { + if (!Resp) { Ready.release(); - }; - NixpkgsProvider.attrpathInfo({Name}, std::move(OnReply)); - Ready.acquire(); - - if (const std::optional &Version = R.PackageDesc.Version) { - // Construct inlay hints. - InlayHint H{ - .position = toLSPPosition(Src, N->rCur()), - .label = ": " + *Version, - .kind = InlayHintKind::Designator, - .range = toLSPRange(Src, N->range()), - }; - Hints.emplace_back(std::move(H)); + return; } + R = *Resp; + Ready.release(); + }; + NixpkgsProvider.attrpathInfo(Selector, std::move(OnReply)); + Ready.acquire(); + + if (const std::optional &Version = R.PackageDesc.Version) { + // Construct inlay hints. + InlayHint H{ + .position = toLSPPosition(Src, N->rCur()), + .label = ": " + *Version, + .kind = InlayHintKind::Designator, + .range = toLSPRange(Src, N->range()), + }; + Hints.emplace_back(std::move(H)); } } - // FIXME: process other node kinds. e.g. ExprSelect. + for (const Node *Ch : N->children()) dfs(Ch); } diff --git a/nixd/tools/nixd/test/inlay-hint/dot-incomplete.md b/nixd/tools/nixd/test/inlay-hint/dot-incomplete.md new file mode 100644 index 000000000..68e963192 --- /dev/null +++ b/nixd/tools/nixd/test/inlay-hint/dot-incomplete.md @@ -0,0 +1,51 @@ +# RUN: nixd --lit-test \ +# RUN: --nixpkgs-expr="{ nerd-fonts.fira-code.version = \"3.4.0\"; }" \ +# RUN: < %s + +<-- initialize(0) + +```json +{ + "jsonrpc":"2.0", + "id":0, + "method":"initialize", + "params":{ + "processId":123, + "rootPath":"", + "capabilities":{ + }, + "trace":"off" + } +} +``` + +```nix file:///basic.nix +with pkgs; [ nerd-fonts. ] +``` + +```json +{ + "jsonrpc": "2.0", + "id": 1, + "method": "textDocument/inlayHint", + "params": { + "textDocument":{ + "uri":"file:///basic.nix" + }, + "range": { + "start":{ + "line": 0, + "character":0 + }, + "end":{ + "line":0, + "character":26 + } + } + } +} +``` + +```json +{"jsonrpc":"2.0","method":"exit"} +``` diff --git a/nixd/tools/nixd/test/inlay-hint/dot.md b/nixd/tools/nixd/test/inlay-hint/dot.md new file mode 100644 index 000000000..a5285f0a3 --- /dev/null +++ b/nixd/tools/nixd/test/inlay-hint/dot.md @@ -0,0 +1,67 @@ +# RUN: nixd --lit-test \ +# RUN: --nixpkgs-expr="{ nerd-fonts.fira-code.version = \"3.4.0\"; }" \ +# RUN: < %s | FileCheck %s + +<-- initialize(0) + +```json +{ + "jsonrpc":"2.0", + "id":0, + "method":"initialize", + "params":{ + "processId":123, + "rootPath":"", + "capabilities":{ + }, + "trace":"off" + } +} +``` + +```nix file:///basic.nix +with pkgs; [ nerd-fonts.fira-code ] +``` + +```json +{ + "jsonrpc": "2.0", + "id": 1, + "method": "textDocument/inlayHint", + "params": { + "textDocument":{ + "uri":"file:///basic.nix" + }, + "range": { + "start":{ + "line": 0, + "character":0 + }, + "end":{ + "line":0, + "character":35 + } + } + } +} +``` + +``` + CHECK: "id": 1, +CHECK-NEXT: "jsonrpc": "2.0", +CHECK-NEXT: "result": [ +CHECK-NEXT: { +CHECK-NEXT: "label": ": 3.4.0", +CHECK-NEXT: "paddingLeft": false, +CHECK-NEXT: "paddingRight": false, +CHECK-NEXT: "position": { +CHECK-NEXT: "character": 33, +CHECK-NEXT: "line": 0 +CHECK-NEXT: } +CHECK-NEXT: } +CHECK-NEXT: ] +``` + +```json +{"jsonrpc":"2.0","method":"exit"} +``` diff --git a/nixd/tools/nixd/test/inlay-hint.md b/nixd/tools/nixd/test/inlay-hint/hello.md similarity index 100% rename from nixd/tools/nixd/test/inlay-hint.md rename to nixd/tools/nixd/test/inlay-hint/hello.md diff --git a/nixd/tools/nixd/test/inlay-hint/lambda.md b/nixd/tools/nixd/test/inlay-hint/lambda.md new file mode 100644 index 000000000..80b902b26 --- /dev/null +++ b/nixd/tools/nixd/test/inlay-hint/lambda.md @@ -0,0 +1,74 @@ +# RUN: nixd --lit-test \ +# RUN: --nixpkgs-expr="{ python3.version = \"3.13.6\"; ps.version = \"231\"; }" \ +# RUN: < %s | FileCheck %s + +<-- initialize(0) + +```json +{ + "jsonrpc":"2.0", + "id":0, + "method":"initialize", + "params":{ + "processId":123, + "rootPath":"", + "capabilities":{ + }, + "trace":"off" + } +} +``` + +```nix file:///basic.nix +with pkgs; [ + (python3.withPackages ( + ps: [ + ps.numpy + ps.pandas + ] + )) +] +``` + +```json +{ + "jsonrpc": "2.0", + "id": 1, + "method": "textDocument/inlayHint", + "params": { + "textDocument":{ + "uri":"file:///basic.nix" + }, + "range": { + "start":{ + "line": 0, + "character":0 + }, + "end":{ + "line":7, + "character":1 + } + } + } +} +``` + +``` + CHECK: "id": 1, +CHECK-NEXT: "jsonrpc": "2.0", +CHECK-NEXT: "result": [ +CHECK-NEXT: { +CHECK-NEXT: "label": ": 3.13.6", +CHECK-NEXT: "paddingLeft": false, +CHECK-NEXT: "paddingRight": false, +CHECK-NEXT: "position": { +CHECK-NEXT: "character": 10, +CHECK-NEXT: "line": 1 +CHECK-NEXT: } +CHECK-NEXT: } +CHECK-NEXT: ] +``` + +```json +{"jsonrpc":"2.0","method":"exit"} +``` diff --git a/nixd/tools/nixd/test/inlay-hint/lib.md b/nixd/tools/nixd/test/inlay-hint/lib.md new file mode 100644 index 000000000..a4c63d7f5 --- /dev/null +++ b/nixd/tools/nixd/test/inlay-hint/lib.md @@ -0,0 +1,57 @@ +# RUN: nixd --lit-test \ +# RUN: --nixpkgs-expr="{ lib.version = \"25.11pre851662.84c256e42600\"; }" \ +# RUN: < %s | FileCheck %s + +<-- initialize(0) + +```json +{ + "jsonrpc":"2.0", + "id":0, + "method":"initialize", + "params":{ + "processId":123, + "rootPath":"", + "capabilities":{ + }, + "trace":"off" + } +} +``` + +```nix file:///basic.nix +with pkgs; [ lib ] +``` + +```json +{ + "jsonrpc": "2.0", + "id": 1, + "method": "textDocument/inlayHint", + "params": { + "textDocument":{ + "uri":"file:///basic.nix" + }, + "range": { + "start":{ + "line": 0, + "character":0 + }, + "end":{ + "line":7, + "character":1 + } + } + } +} +``` + +``` + CHECK: "id": 1, +CHECK-NEXT: "jsonrpc": "2.0", +CHECK-NEXT: "result": [] +``` + +```json +{"jsonrpc":"2.0","method":"exit"} +```