From 667f028c7372f06de4090f3a76277432467a6fc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Oliveira?= Date: Tue, 2 Sep 2025 11:02:24 +0100 Subject: [PATCH 1/7] Add regression tests --- nixd/tools/nixd/test/inlay-hint/dot.md | 67 +++++++++++++++++ .../{inlay-hint.md => inlay-hint/hello.md} | 0 nixd/tools/nixd/test/inlay-hint/lambda.md | 74 +++++++++++++++++++ nixd/tools/nixd/test/inlay-hint/lib.md | 57 ++++++++++++++ 4 files changed, 198 insertions(+) create mode 100644 nixd/tools/nixd/test/inlay-hint/dot.md rename nixd/tools/nixd/test/{inlay-hint.md => inlay-hint/hello.md} (100%) create mode 100644 nixd/tools/nixd/test/inlay-hint/lambda.md create mode 100644 nixd/tools/nixd/test/inlay-hint/lib.md 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"} +``` From 0a82b7f36918bc1df1d3e91ea7dd76e0b1221ba1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Oliveira?= Date: Wed, 23 Jul 2025 09:18:07 +0100 Subject: [PATCH 2/7] Add ExprSelect support to InlayHints --- nixd/lib/Controller/InlayHints.cpp | 67 ++++++++++++++++++------------ 1 file changed, 41 insertions(+), 26 deletions(-) diff --git a/nixd/lib/Controller/InlayHints.cpp b/nixd/lib/Controller/InlayHints.cpp index 2938e0af3..1493d4f41 100644 --- a/nixd/lib/Controller/InlayHints.cpp +++ b/nixd/lib/Controller/InlayHints.cpp @@ -71,40 +71,55 @@ class NixpkgsInlayHintsProvider { void dfs(const Node *N) { if (!N) 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); + AttrPathInfoParams selector; + 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; + + const auto &Var = static_cast(*N); + selector.emplace_back(Var.id().name()); + } + } else if (N->kind() == Node::NK_ExprSelect) { + if (havePackageScope(*N, VLA, PMA)) { + if (!rangeOK(N->positionRange())) + return; + + const auto &Sel = static_cast(*N); + selector = nixd::idioms::mkSelector(Sel, VLA, PMA); + } + } + + if (!selector.empty()) { + 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); } From 1be5cedac05066e21836b62c0dbed966fecc933d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Oliveira?= Date: Wed, 23 Jul 2025 09:18:07 +0100 Subject: [PATCH 3/7] Fix ExprSelect performance --- nixd/lib/Controller/InlayHints.cpp | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/nixd/lib/Controller/InlayHints.cpp b/nixd/lib/Controller/InlayHints.cpp index 1493d4f41..063d4fccc 100644 --- a/nixd/lib/Controller/InlayHints.cpp +++ b/nixd/lib/Controller/InlayHints.cpp @@ -75,28 +75,43 @@ class NixpkgsInlayHintsProvider { // 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); AttrPathInfoParams selector; if (N->kind() == Node::NK_ExprVar) { if (havePackageScope(*N, VLA, PMA)) { + if (!rangeOK(N->positionRange())) return; const auto &Var = static_cast(*N); - selector.emplace_back(Var.id().name()); + + const std::string &name = Var.id().name(); + if (name != nixd::idioms::Lib) { + const auto &lookup = VLA.query(Var); + if (lookup.Kind != + VariableLookupAnalysis::LookupResultKind::Defined) { + selector.emplace_back(name); + } + } } } else if (N->kind() == Node::NK_ExprSelect) { if (havePackageScope(*N, VLA, PMA)) { + if (!rangeOK(N->positionRange())) return; const auto &Sel = static_cast(*N); - selector = nixd::idioms::mkSelector(Sel, VLA, PMA); + + selector.emplace_back(Sel.expr().src(Src)); + + for (const auto &name : Sel.path()->names()) { + selector.emplace_back(name->src(Src)); + } } } if (!selector.empty()) { + std::binary_semaphore Ready(0); AttrPathInfoResponse R; auto OnReply = [&Ready, &R](llvm::Expected Resp) { if (!Resp) { @@ -120,6 +135,7 @@ class NixpkgsInlayHintsProvider { Hints.emplace_back(std::move(H)); } } + for (const Node *Ch : N->children()) dfs(Ch); } From 6919c69a3e1dbb6eea27edb72da7ee4e09b28c51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Oliveira?= Date: Tue, 2 Sep 2025 17:47:05 +0100 Subject: [PATCH 4/7] Update nixd/lib/Controller/InlayHints.cpp Co-authored-by: Yingchi Long --- nixd/lib/Controller/InlayHints.cpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/nixd/lib/Controller/InlayHints.cpp b/nixd/lib/Controller/InlayHints.cpp index 063d4fccc..9220352bb 100644 --- a/nixd/lib/Controller/InlayHints.cpp +++ b/nixd/lib/Controller/InlayHints.cpp @@ -72,10 +72,7 @@ class NixpkgsInlayHintsProvider { if (!N) return; - // Ask nixpkgs eval to provide it's information. - // This is relatively slow. Maybe better query a set of packages in the - // future? - AttrPathInfoParams selector; + AttrPathInfoParams Selector; if (N->kind() == Node::NK_ExprVar) { if (havePackageScope(*N, VLA, PMA)) { @@ -85,12 +82,12 @@ class NixpkgsInlayHintsProvider { const auto &Var = static_cast(*N); - const std::string &name = Var.id().name(); - if (name != nixd::idioms::Lib) { + const std::string &Name = Var.id().name(); + if (Name != nixd::idioms::Lib) { const auto &lookup = VLA.query(Var); if (lookup.Kind != VariableLookupAnalysis::LookupResultKind::Defined) { - selector.emplace_back(name); + Selector.emplace_back(Name); } } } @@ -102,15 +99,18 @@ class NixpkgsInlayHintsProvider { const auto &Sel = static_cast(*N); - selector.emplace_back(Sel.expr().src(Src)); + Selector.emplace_back(Sel.expr().src(Src)); for (const auto &name : Sel.path()->names()) { - selector.emplace_back(name->src(Src)); + Selector.emplace_back(name->src(Src)); } } } - if (!selector.empty()) { + // 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) { @@ -121,7 +121,7 @@ class NixpkgsInlayHintsProvider { R = *Resp; Ready.release(); }; - NixpkgsProvider.attrpathInfo(selector, std::move(OnReply)); + NixpkgsProvider.attrpathInfo(Selector, std::move(OnReply)); Ready.acquire(); if (const std::optional &Version = R.PackageDesc.Version) { From b658b3e14f75e2a91807c56bc8e2cb5723dcaaca Mon Sep 17 00:00:00 2001 From: Yingchi Long Date: Fri, 12 Sep 2025 11:30:31 +0800 Subject: [PATCH 5/7] fixup clang-tidy naming --- nixd/lib/Controller/InlayHints.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nixd/lib/Controller/InlayHints.cpp b/nixd/lib/Controller/InlayHints.cpp index 9220352bb..9c8e07bb9 100644 --- a/nixd/lib/Controller/InlayHints.cpp +++ b/nixd/lib/Controller/InlayHints.cpp @@ -84,8 +84,8 @@ class NixpkgsInlayHintsProvider { const std::string &Name = Var.id().name(); if (Name != nixd::idioms::Lib) { - const auto &lookup = VLA.query(Var); - if (lookup.Kind != + const auto &Lookup = VLA.query(Var); + if (Lookup.Kind != VariableLookupAnalysis::LookupResultKind::Defined) { Selector.emplace_back(Name); } @@ -101,8 +101,8 @@ class NixpkgsInlayHintsProvider { Selector.emplace_back(Sel.expr().src(Src)); - for (const auto &name : Sel.path()->names()) { - Selector.emplace_back(name->src(Src)); + for (const auto &Name : Sel.path()->names()) { + Selector.emplace_back(Name->src(Src)); } } } From 549cc8d7ab1effaeeb952449f5195e37707cff46 Mon Sep 17 00:00:00 2001 From: Yingchi Long Date: Fri, 12 Sep 2025 11:41:25 +0800 Subject: [PATCH 6/7] fixup code style --- nixd/lib/Controller/InlayHints.cpp | 51 +++++++++++++----------------- 1 file changed, 22 insertions(+), 29 deletions(-) diff --git a/nixd/lib/Controller/InlayHints.cpp b/nixd/lib/Controller/InlayHints.cpp index 9c8e07bb9..58c4d1f7a 100644 --- a/nixd/lib/Controller/InlayHints.cpp +++ b/nixd/lib/Controller/InlayHints.cpp @@ -69,43 +69,36 @@ class NixpkgsInlayHintsProvider { } void dfs(const Node *N) { + using VariableLookupAnalysis::LookupResultKind::Defined; + using Params = AttrPathInfoParams; if (!N) return; - AttrPathInfoParams Selector; - - if (N->kind() == Node::NK_ExprVar) { - if (havePackageScope(*N, VLA, PMA)) { - - if (!rangeOK(N->positionRange())) - return; - + // 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) { - const auto &Lookup = VLA.query(Var); - if (Lookup.Kind != - VariableLookupAnalysis::LookupResultKind::Defined) { - Selector.emplace_back(Name); - } - } + if (Name == nixd::idioms::Lib) // Skip "lib" + return {}; + const auto &Lookup = VLA.query(Var); + return (Lookup.Kind != Defined) ? Params{Name} : Params{}; } - } else if (N->kind() == Node::NK_ExprSelect) { - if (havePackageScope(*N, VLA, PMA)) { - - if (!rangeOK(N->positionRange())) - return; - + case Node::NK_ExprSelect: { const auto &Sel = static_cast(*N); - - Selector.emplace_back(Sel.expr().src(Src)); - - for (const auto &Name : Sel.path()->names()) { - Selector.emplace_back(Name->src(Src)); - } + 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 From 0a4cdc39ba31fb2eae2ecf988a5dedf9626f2698 Mon Sep 17 00:00:00 2001 From: Yingchi Long Date: Fri, 12 Sep 2025 11:49:13 +0800 Subject: [PATCH 7/7] hack code --- .../nixd/test/inlay-hint/dot-incomplete.md | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 nixd/tools/nixd/test/inlay-hint/dot-incomplete.md 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"} +```