diff --git a/nixd/include/nixd/Controller/Controller.h b/nixd/include/nixd/Controller/Controller.h index 1f4ac3611..33b1753c3 100644 --- a/nixd/include/nixd/Controller/Controller.h +++ b/nixd/include/nixd/Controller/Controller.h @@ -180,7 +180,7 @@ class Controller : public lspserver::LSPServer { lspserver::Callback Reply); void onDefinition(const lspserver::TextDocumentPositionParams &Params, - lspserver::Callback Reply); + lspserver::Callback Reply); void onReferences(const lspserver::TextDocumentPositionParams &Params, diff --git a/nixd/lib/Controller/AST.cpp b/nixd/lib/Controller/AST.cpp index 6bc0aadf8..8c8c46595 100644 --- a/nixd/lib/Controller/AST.cpp +++ b/nixd/lib/Controller/AST.cpp @@ -132,7 +132,9 @@ nixd::findAttrPath(const nixf::Node &N, const nixf::ParentMapAnalysis &PM) { AttrPath = getValueAttrPath(*Expr, PM); auto Select = getSelectAttrPath(static_cast(*Name), PM); AttrPath.insert(AttrPath.end(), Select.begin(), Select.end()); - assert(!AttrPath.empty()); + // Select "AttrPath" might come from "inherit", thus it can be empty. + if (AttrPath.empty()) + return std::nullopt; return AttrPath; } diff --git a/nixd/lib/Controller/Definition.cpp b/nixd/lib/Controller/Definition.cpp index 1c804e387..99e723a0f 100644 --- a/nixd/lib/Controller/Definition.cpp +++ b/nixd/lib/Controller/Definition.cpp @@ -26,6 +26,7 @@ using namespace llvm; using LookupResult = VariableLookupAnalysis::LookupResult; using ResultKind = VariableLookupAnalysis::LookupResultKind; +using Locations = std::vector; namespace { @@ -165,6 +166,36 @@ class NixpkgsDefinitionProvider { } }; +/// \brief Try to get "location" by invoking options worker +class OptionsDefinitionProvider { + AttrSetClient &Client; + +public: + OptionsDefinitionProvider(AttrSetClient &Client) : Client(Client) {} + void resolveLocations(const std::vector &Params, + Locations &Locs) { + std::binary_semaphore Ready(0); + Expected Info = error("not replied"); + OptionCompleteResponse Names; + auto OnReply = [&Ready, &Info](llvm::Expected Resp) { + Info = std::move(Resp); + Ready.release(); + }; + // Send request. + + Client.optionInfo(Params, std::move(OnReply)); + Ready.acquire(); + + if (!Info) { + elog("getting locations: {0}", Info.takeError()); + return; + } + + for (const auto &Decl : Info->Declarations) + Locs.emplace_back(Decl); + } +}; + } // namespace Expected @@ -191,7 +222,7 @@ nixd::findDefinition(const Node &N, const ParentMapAnalysis &PMA, } void Controller::onDefinition(const TextDocumentPositionParams &Params, - Callback Reply) { + Callback Reply) { auto Action = [Reply = std::move(Reply), URI = Params.textDocument.uri, Pos = toNixfPosition(Params.position), this]() mutable { std::string File(URI.file()); @@ -200,24 +231,54 @@ void Controller::onDefinition(const TextDocumentPositionParams &Params, const VariableLookupAnalysis &VLA = *TU->variableLookup(); const ParentMapAnalysis &PM = *TU->parentMap(); const Node *N = AST->descend({Pos, Pos}); + Locations Locs; if (!N) [[unlikely]] { Reply(error("cannot find AST node on given position")); return; } + if (std::optional> Path = + findAttrPath(*N, PM)) { + std::lock_guard _(OptionsLock); + // For each option worker, try to get it's decl position. + for (const auto &[_, Client] : Options) { + if (AttrSetClient *C = Client->client()) { + OptionsDefinitionProvider ODP(*C); + std::vector Params; + Params.reserve(Path->size()); + for (const auto &P : *Path) + Params.emplace_back(P); + ODP.resolveLocations(Params, Locs); + } + } + } if (havePackageScope(*N, VLA, PM) && nixpkgsClient()) { // Ask nixpkgs client what's current package documentation. NixpkgsDefinitionProvider NDP(*nixpkgsClient()); auto [Scope, Name] = getScopeAndPrefix(*N, PM); - Expected Loc = - NDP.resolvePackage(std::move(Scope), std::move(Name)); - if (Loc) { - Reply(std::move(*Loc)); - return; - } - elog("cannot get nixpkgs definition for this package: {0}", - Loc.takeError()); + if (Expected Loc = + NDP.resolvePackage(std::move(Scope), std::move(Name))) + Locs.emplace_back(*Loc); + else + elog("cannot get nixpkgs definition for package: {0}", + Loc.takeError()); + } + + if (Expected StaticLoc = staticDef(URI, *N, PM, VLA)) + Locs.emplace_back(*StaticLoc); + else + elog("cannot get static def location: {0}", StaticLoc.takeError()); + + if (Locs.empty()) { + Reply(nullptr); + return; + } + + if (Locs.size() == 1) { + Reply(Locs.back()); + return; } - Reply(staticDef(URI, *N, PM, VLA)); + + Reply(std::move(Locs)); return; } } diff --git a/nixd/tools/nixd/test/definition-options.md b/nixd/tools/nixd/test/definition-options.md new file mode 100644 index 000000000..d59454e57 --- /dev/null +++ b/nixd/tools/nixd/test/definition-options.md @@ -0,0 +1,80 @@ +# RUN: nixd \ +# RUN: --nixos-options-expr="{ foo.declarationPositions = [ { file = \"/foo\"; line = 8; column = 7; } ]; }" \ +# RUN: --lit-test < %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 = 1; }" + } + } +} +``` + +<-- textDocument/definition(2) + + +```json +{ + "jsonrpc":"2.0", + "id":2, + "method":"textDocument/definition", + "params":{ + "textDocument":{ + "uri":"file:///basic.nix" + }, + "position":{ + "line": 0, + "character":3 + } + } +} +``` + +``` + CHECK: "id": 2, +CHECK-NEXT: "jsonrpc": "2.0", +CHECK-NEXT: "result": { +CHECK-NEXT: "range": { +CHECK-NEXT: "end": { +CHECK-NEXT: "character": 7, +CHECK-NEXT: "line": 8 +CHECK-NEXT: }, +CHECK-NEXT: "start": { +CHECK-NEXT: "character": 7, +CHECK-NEXT: "line": 8 +CHECK-NEXT: } +CHECK-NEXT: }, +CHECK-NEXT: "uri": "file:///foo" +``` + + +```json +{"jsonrpc":"2.0","method":"exit"} +``` diff --git a/nixd/tools/nixd/test/definition-package.md b/nixd/tools/nixd/test/definition-package.md index 868dff7e5..e25d43883 100644 --- a/nixd/tools/nixd/test/definition-package.md +++ b/nixd/tools/nixd/test/definition-package.md @@ -58,18 +58,33 @@ ``` CHECK: "id": 2, CHECK-NEXT: "jsonrpc": "2.0", -CHECK-NEXT: "result": { -CHECK-NEXT: "range": { -CHECK-NEXT: "end": { -CHECK-NEXT: "character": 33, -CHECK-NEXT: "line": 33 +CHECK-NEXT: "result": [ +CHECK-NEXT: { +CHECK-NEXT: "range": { +CHECK-NEXT: "end": { +CHECK-NEXT: "character": 33, +CHECK-NEXT: "line": 33 +CHECK-NEXT: }, +CHECK-NEXT: "start": { +CHECK-NEXT: "character": 33, +CHECK-NEXT: "line": 33 +CHECK-NEXT: } CHECK-NEXT: }, -CHECK-NEXT: "start": { -CHECK-NEXT: "character": 33, -CHECK-NEXT: "line": 33 -CHECK-NEXT: } +CHECK-NEXT: "uri": "file:///foo" CHECK-NEXT: }, -CHECK-NEXT: "uri": "file:///foo" +CHECK-NEXT: { +CHECK-NEXT: "range": { +CHECK-NEXT: "end": { +CHECK-NEXT: "character": 4, +CHECK-NEXT: "line": 0 +CHECK-NEXT: }, +CHECK-NEXT: "start": { +CHECK-NEXT: "character": 0, +CHECK-NEXT: "line": 0 +CHECK-NEXT: } +CHECK-NEXT: }, +CHECK-NEXT: "uri": "file:///basic.nix" +CHECK-NEXT: } ```