diff --git a/lib/lspserver/src/SourceCode.cpp b/lib/lspserver/src/SourceCode.cpp index a87df890f..e19947301 100644 --- a/lib/lspserver/src/SourceCode.cpp +++ b/lib/lspserver/src/SourceCode.cpp @@ -141,6 +141,18 @@ llvm::Expected positionToOffset(llvm::StringRef Code, Position P, return StartOfLine + ByteInLine; } +Position offsetToPosition(llvm::StringRef Code, size_t Offset) { + Offset = std::min(Code.size(), Offset); + llvm::StringRef Before = Code.substr(0, Offset); + int Lines = Before.count('\n'); + size_t PrevNL = Before.rfind('\n'); + size_t StartOfLine = (PrevNL == llvm::StringRef::npos) ? 0 : (PrevNL + 1); + Position Pos; + Pos.line = Lines; + Pos.character = lspLength(Before.substr(StartOfLine)); + return Pos; +} + // Workaround for editors that have buggy handling of newlines at end of file. // // The editor is supposed to expose document contents over LSP as an exact diff --git a/lib/nixd/include/nixd/Server.h b/lib/nixd/include/nixd/Server.h index 624ae9f51..d8b5004ca 100644 --- a/lib/nixd/include/nixd/Server.h +++ b/lib/nixd/include/nixd/Server.h @@ -269,6 +269,9 @@ class Server : public lspserver::LSPServer { // Controller Methods + void onDecalration(const lspserver::TextDocumentPositionParams &, + lspserver::Callback); + void onDefinition(const lspserver::TextDocumentPositionParams &, lspserver::Callback); @@ -283,6 +286,9 @@ class Server : public lspserver::LSPServer { // Worker + void onOptionDeclaration(const ipc::AttrPathParams &, + lspserver::Callback); + void onWorkerDefinition(const lspserver::TextDocumentPositionParams &, lspserver::Callback); diff --git a/lib/nixd/src/ServerController.cpp b/lib/nixd/src/ServerController.cpp index 424752ea7..a243cb06e 100644 --- a/lib/nixd/src/ServerController.cpp +++ b/lib/nixd/src/ServerController.cpp @@ -10,6 +10,7 @@ #include "lspserver/Logger.h" #include "lspserver/Path.h" #include "lspserver/Protocol.h" +#include "lspserver/SourceCode.h" #include "lspserver/URI.h" #include @@ -29,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -213,6 +215,7 @@ Server::Server(std::unique_ptr In, // Language Features Registry.addMethod("textDocument/hover", this, &Server::onHover); Registry.addMethod("textDocument/completion", this, &Server::onCompletion); + Registry.addMethod("textDocument/declaration", this, &Server::onDecalration); Registry.addMethod("textDocument/definition", this, &Server::onDefinition); Registry.addMethod("textDocument/formatting", this, &Server::onFormat); @@ -239,6 +242,9 @@ Server::Server(std::unique_ptr In, Registry.addMethod("nixd/ipc/textDocument/definition", this, &Server::onWorkerDefinition); + Registry.addMethod("nixd/ipc/option/textDocument/declaration", this, + &Server::onOptionDeclaration); + readJSONConfig(); } @@ -255,6 +261,7 @@ void Server::onInitialize(const lspserver::InitializeParams &InitializeParams, {"change", (int)lspserver::TextDocumentSyncKind::Incremental}, {"save", true}, }}, + {"declarationProvider", true}, {"definitionProvider", true}, {"hoverProvider", true}, {"documentFormattingProvider", true}, @@ -312,6 +319,56 @@ void Server::onDocumentDidClose( //-----------------------------------------------------------------------------/ // Language Features +void Server::onDecalration(const lspserver::TextDocumentPositionParams &Params, + lspserver::Callback Reply) { + auto Thread = std::thread([=, Reply = std::move(Reply), this]() mutable { + ReplyRAII RR(std::move(Reply)); + + // Set the default response to "null", instead of errors + RR.Response = nullptr; + + ipc::AttrPathParams APParams; + + // Try to get current attribute path, expand the position + + auto Code = llvm::StringRef( + *DraftMgr.getDraft(Params.textDocument.uri.file())->Contents); + auto ExpectedOffset = positionToOffset(Code, Params.position); + + if (!ExpectedOffset) + RR.Response = ExpectedOffset.takeError(); + + auto Offset = ExpectedOffset.get(); + auto From = Offset; + auto To = Offset; + + std::string Punc = "\r\n\t ;"; + + auto IsPunc = [&Punc](char C) { + return std::ranges::any_of(Punc, [C](char Ck) { return Ck == C; }); + }; + + for (; From >= 0 && !IsPunc(Code[From]);) + From--; + for (; To < Code.size() && !IsPunc(Code[To]);) + To++; + + APParams.Path = Code.substr(From, To - From).trim(Punc); + lspserver::log("requesting path: {0}", APParams.Path); + + auto Responses = askWorkers( + OptionWorkers, "nixd/ipc/option/textDocument/declaration", APParams, + 2e4); + + if (Responses.empty()) + return; + + RR.Response = std::move(Responses.back()); + }); + + Thread.detach(); +} + void Server::onDefinition(const lspserver::TextDocumentPositionParams &Params, lspserver::Callback Reply) { auto Thread = std::thread([=, Reply = std::move(Reply), this]() mutable { diff --git a/lib/nixd/src/ServerWorker.cpp b/lib/nixd/src/ServerWorker.cpp index e5cf59eb8..b725202b9 100644 --- a/lib/nixd/src/ServerWorker.cpp +++ b/lib/nixd/src/ServerWorker.cpp @@ -174,6 +174,60 @@ void Server::evalInstallable(lspserver::PathRef File, int Depth = 0) { std::move(Session)); } +void Server::onOptionDeclaration( + const ipc::AttrPathParams &Params, + lspserver::Callback Reply) { + assert(Role == ServerRole::OptionProvider && + "option declaration should be calculated in option worker!"); + using namespace lspserver; + ReplyRAII RR(std::move(Reply)); + if (OptionAttrSet->type() != nix::ValueType::nAttrs) + return; + + try { + nix::Value *V = OptionAttrSet; + if (!Params.Path.empty()) { + auto &Bindings(*OptionIES->getState()->allocBindings(0)); + V = nix::findAlongAttrPath(*OptionIES->getState(), Params.Path, Bindings, + *OptionAttrSet) + .first; + } + + auto &State = *OptionIES->getState(); + State.forceValue(*V, nix::noPos); + + auto *VDecl = nix::findAlongAttrPath(State, "declarations", + *State.allocBindings(0), *V) + .first; + + State.forceValue(*VDecl, nix::noPos); + + // declarations should be a list containing file paths + if (!VDecl->isList()) + return; + + for (auto *VFile : VDecl->listItems()) { + State.forceValue(*VFile, nix::noPos); + if (VFile->type() == nix::ValueType::nString) { + auto File = VFile->str(); + Location L; + L.uri = URIForFile::canonicalize(File, File); + + // Where is the range? + L.range.start = {0, 0}; + L.range.end = {0, 0}; + + RR.Response = L; + return; + } + } + + } catch (std::exception &E) { + RR.Response = error(stripANSI(E.what())); + } catch (...) { + } +} + void Server::onWorkerDefinition( const lspserver::TextDocumentPositionParams &Params, lspserver::Callback Reply) {