diff --git a/nixd/nix-node-eval/src/EvalProvider.cpp b/nixd/nix-node-eval/src/EvalProvider.cpp index 640e4f330..ed9b865be 100644 --- a/nixd/nix-node-eval/src/EvalProvider.cpp +++ b/nixd/nix-node-eval/src/EvalProvider.cpp @@ -35,6 +35,10 @@ EvalProvider::EvalProvider(std::unique_ptr In, new nix::EvalState{{}, nix::openStore("dummy://")})) { Registry.addMethod("exprValue", this, &EvalProvider::onExprValue); Registry.addNotification("registerBC", this, &EvalProvider::onRegisterBC); + + Ready = mkOutNotifiction("ready"); + + Ready(getpid()); } void EvalProvider::onRegisterBC(const rpc::RegisterBCParams &Params) { diff --git a/nixd/nix-node-eval/src/EvalProvider.h b/nixd/nix-node-eval/src/EvalProvider.h index 89a8a2bc5..b3722a639 100644 --- a/nixd/nix-node-eval/src/EvalProvider.h +++ b/nixd/nix-node-eval/src/EvalProvider.h @@ -19,7 +19,7 @@ class EvalProvider : public lspserver::LSPServer { nixt::EnvMap EMap; std::unique_ptr State; - llvm::unique_function Exit; + llvm::unique_function Ready; public: EvalProvider(std::unique_ptr In, diff --git a/nixd/tools/nixd/src/CodeAction.cpp b/nixd/tools/nixd/src/CodeAction.cpp index 403afbf8f..300d87399 100644 --- a/nixd/tools/nixd/src/CodeAction.cpp +++ b/nixd/tools/nixd/src/CodeAction.cpp @@ -6,6 +6,8 @@ #include "Controller.h" #include "Convert.h" +#include + namespace nixd { using namespace llvm::json; @@ -13,39 +15,46 @@ using namespace lspserver; void Controller::onCodeAction(const lspserver::CodeActionParams &Params, Callback> Reply) { - PathRef File = Params.textDocument.uri.file(); + std::string File(Params.textDocument.uri.file()); Range Range = Params.range; - const std::vector &Diagnostics = TUs[File].diagnostics(); - std::vector Actions; - Actions.reserve(Diagnostics.size()); - for (const nixf::Diagnostic &D : Diagnostics) { - auto DRange = toLSPRange(D.range()); - if (!Range.overlap(DRange)) - continue; + auto Action = [Reply = std::move(Reply), File, Range, this]() mutable { + std::vector Diagnostics; + { + std::lock_guard TU(TUsLock); + Diagnostics = TUs[File].diagnostics(); + } + std::vector Actions; + Actions.reserve(Diagnostics.size()); + for (const nixf::Diagnostic &D : Diagnostics) { + auto DRange = toLSPRange(D.range()); + if (!Range.overlap(DRange)) + continue; - // Add fixes. - for (const nixf::Fix &F : D.fixes()) { - std::vector Edits; - Edits.reserve(F.edits().size()); - for (const nixf::TextEdit &TE : F.edits()) { - Edits.emplace_back(TextEdit{ - .range = toLSPRange(TE.oldRange()), - .newText = std::string(TE.newText()), + // Add fixes. + for (const nixf::Fix &F : D.fixes()) { + std::vector Edits; + Edits.reserve(F.edits().size()); + for (const nixf::TextEdit &TE : F.edits()) { + Edits.emplace_back(TextEdit{ + .range = toLSPRange(TE.oldRange()), + .newText = std::string(TE.newText()), + }); + } + using Changes = std::map>; + std::string FileURI = URIForFile::canonicalize(File, File).uri(); + WorkspaceEdit WE{.changes = Changes{ + {std::move(FileURI), std::move(Edits)}, + }}; + Actions.emplace_back(CodeAction{ + .title = F.message(), + .kind = std::string(CodeAction::QUICKFIX_KIND), + .edit = std::move(WE), }); } - using Changes = std::map>; - std::string FileURI = URIForFile::canonicalize(File, File).uri(); - WorkspaceEdit WE{.changes = Changes{ - {std::move(FileURI), std::move(Edits)}, - }}; - Actions.emplace_back(CodeAction{ - .title = F.message(), - .kind = std::string(CodeAction::QUICKFIX_KIND), - .edit = std::move(WE), - }); } - } - Reply(std::move(Actions)); + Reply(std::move(Actions)); + }; + boost::asio::post(Pool, std::move(Action)); } } // namespace nixd diff --git a/nixd/tools/nixd/src/Controller.h b/nixd/tools/nixd/src/Controller.h index 79e4f3e6e..2033169a1 100644 --- a/nixd/tools/nixd/src/Controller.h +++ b/nixd/tools/nixd/src/Controller.h @@ -6,6 +6,8 @@ #include "lspserver/DraftStore.h" #include "lspserver/LSPServer.h" +#include + namespace nixd { class Controller : public lspserver::LSPServer { @@ -15,8 +17,14 @@ class Controller : public lspserver::LSPServer { llvm::unique_function PublishDiagnostic; + std::mutex TUsLock; llvm::StringMap TUs; + boost::asio::thread_pool Pool; + + /// In lit-test mode. Disable some concurrency for better text-testing. + bool LitTest; + /// Action right after a document is added (including updates). void actOnDocumentAdd(lspserver::PathRef File, std::optional Version); @@ -51,6 +59,12 @@ class Controller : public lspserver::LSPServer { public: Controller(std::unique_ptr In, std::unique_ptr Out); + + ~Controller() { Pool.join(); } + + void setLitTest(bool LitTest) { this->LitTest = LitTest; } + + bool isReadyToEval() { return Eval && Eval->ready(); } }; } // namespace nixd diff --git a/nixd/tools/nixd/src/EvalClient.cpp b/nixd/tools/nixd/src/EvalClient.cpp index fe05404a7..bc571b5cf 100644 --- a/nixd/tools/nixd/src/EvalClient.cpp +++ b/nixd/tools/nixd/src/EvalClient.cpp @@ -45,4 +45,13 @@ std::unique_ptr OwnedEvalClient::create(int &Fail) { std::move(ProcFdStream)); } +EvalClient::EvalClient(std::unique_ptr In, + std::unique_ptr Out) + : lspserver::LSPServer(std::move(In), std::move(Out)), Ready(false) { + RegisterBC = mkOutNotifiction("registerBC"); + ExprValue = + mkOutMethod("exprValue"); + Registry.addNotification("ready", this, &EvalClient::onReady); +} + } // namespace nixd diff --git a/nixd/tools/nixd/src/EvalClient.h b/nixd/tools/nixd/src/EvalClient.h index b74a54d8a..b93c94986 100644 --- a/nixd/tools/nixd/src/EvalClient.h +++ b/nixd/tools/nixd/src/EvalClient.h @@ -12,6 +12,8 @@ namespace nixd { class EvalClient : public lspserver::LSPServer { + std::atomic Ready; + public: llvm::unique_function RegisterBC; llvm::unique_function In, - std::unique_ptr Out) - : lspserver::LSPServer(std::move(In), std::move(Out)) { - RegisterBC = mkOutNotifiction("registerBC"); - ExprValue = - mkOutMethod("exprValue"); - } + std::unique_ptr Out); virtual ~EvalClient() = default; + + void onReady(const int &Flags) { + lspserver::log( + "nix-node-eval({0}) reported it's ready for processing requests", + Flags); + Ready = true; + } + + bool ready() { return Ready; } }; class OwnedEvalClient : public EvalClient { diff --git a/nixd/tools/nixd/src/Hover.cpp b/nixd/tools/nixd/src/Hover.cpp index c53a07180..ad7701add 100644 --- a/nixd/tools/nixd/src/Hover.cpp +++ b/nixd/tools/nixd/src/Hover.cpp @@ -6,30 +6,42 @@ #include "Controller.h" #include "Convert.h" +#include + +#include + +#include + namespace nixd { using namespace llvm::json; using namespace lspserver; +using namespace rpc; void Controller::onHover(const TextDocumentPositionParams &Params, Callback> Reply) { - PathRef File = Params.textDocument.uri.file(); - const nixf::Node *AST = TUs[File].ast().get(); - nixf::Position Pos{Params.position.line, Params.position.character}; - const nixf::Node *N = AST->descend({Pos, Pos}); - if (!N) { - Reply(std::nullopt); - return; - } - std::string Name = N->name(); - Reply(Hover{ - .contents = - MarkupContent{ - .kind = MarkupKind::Markdown, - .value = "`" + Name + "`", - }, - .range = toLSPRange(N->range()), - }); + auto Action = [Reply = std::move(Reply), + File = std::string(Params.textDocument.uri.file()), + RawPos = Params.position, this]() mutable { + std::lock_guard G(TUsLock); + const std::shared_ptr &AST = TUs[File].ast(); + nixf::Position Pos{RawPos.line, RawPos.character}; + const nixf::Node *N = AST->descend({Pos, Pos}); + if (!N) { + Reply(std::nullopt); + return; + } + std::string Name = N->name(); + Reply(Hover{ + .contents = + MarkupContent{ + .kind = MarkupKind::Markdown, + .value = "`" + Name + "`", + }, + .range = toLSPRange(N->range()), + }); + }; + boost::asio::post(Pool, std::move(Action)); } } // namespace nixd diff --git a/nixd/tools/nixd/src/Main.cpp b/nixd/tools/nixd/src/Main.cpp index 9d008d8dc..9250fabad 100644 --- a/nixd/tools/nixd/src/Main.cpp +++ b/nixd/tools/nixd/src/Main.cpp @@ -80,6 +80,9 @@ int main(int argc, char *argv[]) { auto Controller = std::make_unique(std::move(In), std::move(Out)); + if (LitTest) + Controller->setLitTest(LitTest); + Controller->run(); return 0; diff --git a/nixd/tools/nixd/src/Support.cpp b/nixd/tools/nixd/src/Support.cpp index 03f988b8a..96657717f 100644 --- a/nixd/tools/nixd/src/Support.cpp +++ b/nixd/tools/nixd/src/Support.cpp @@ -7,6 +7,10 @@ #include "nixf/Bytecode/Write.h" #include "nixf/Parse/Parser.h" +#include + +#include + using namespace lspserver; using namespace nixd; @@ -27,54 +31,70 @@ namespace nixd { void Controller::actOnDocumentAdd(PathRef File, std::optional Version) { - auto Draft = Store.getDraft(File); - assert(Draft && "Added document is not in the store?"); - - std::vector Diagnostics; - std::shared_ptr AST = nixf::parse(*Draft->Contents, Diagnostics); - publishDiagnostics(File, Version, Diagnostics); - - if (!AST) { - TUs[File] = NixTU(std::move(Diagnostics), std::move(AST), std::nullopt); - return; - } - - // Serialize the AST into shared memory. Prepare for evaluation. - std::stringstream OS; - nixf::writeBytecode(OS, *AST); - std::string Buf = OS.str(); - if (Buf.empty()) { - lspserver::log("empty AST for {0}", File); - TUs[File] = NixTU(std::move(Diagnostics), std::move(AST), std::nullopt); - return; - } - - // Create an mmap()-ed region, and write AST byte code there. - std::string ShmName = getShmName(File); - - auto Shm = std::make_unique( - ShmName, static_cast(Buf.size())); - - auto Region = - std::make_unique(Shm->get(), bipc::read_write); - - std::memcpy(Region->get_address(), Buf.data(), Buf.size()); - - lspserver::log("serialized AST {0} to {1}, size: {2}", File, ShmName, - Buf.size()); - - TUs[File] = NixTU(std::move(Diagnostics), std::move(AST), - util::OwnedRegion{std::move(Shm), std::move(Region)}); - - if (Eval) { - Eval->RegisterBC(rpc::RegisterBCParams{ - ShmName, ".", ".", static_cast(Buf.size())}); + auto Action = [this, File = std::string(File), Version]() { + auto Draft = Store.getDraft(File); + assert(Draft && "Added document is not in the store?"); + + std::vector Diagnostics; + std::shared_ptr AST = + nixf::parse(*Draft->Contents, Diagnostics); + publishDiagnostics(File, Version, Diagnostics); + + if (!AST) { + std::lock_guard G(TUsLock); + TUs[File] = NixTU(std::move(Diagnostics), std::move(AST), std::nullopt); + return; + } + + // Serialize the AST into shared memory. Prepare for evaluation. + std::stringstream OS; + nixf::writeBytecode(OS, *AST); + std::string Buf = OS.str(); + if (Buf.empty()) { + lspserver::log("empty AST for {0}", File); + std::lock_guard G(TUsLock); + TUs[File] = NixTU(std::move(Diagnostics), std::move(AST), std::nullopt); + return; + } + + // Create an mmap()-ed region, and write AST byte code there. + std::string ShmName = getShmName(File); + + auto Shm = std::make_unique( + ShmName, static_cast(Buf.size())); + + auto Region = + std::make_unique(Shm->get(), bipc::read_write); + + std::memcpy(Region->get_address(), Buf.data(), Buf.size()); + + lspserver::log("serialized AST {0} to {1}, size: {2}", File, ShmName, + Buf.size()); + + std::lock_guard G(TUsLock); + TUs[File] = NixTU(std::move(Diagnostics), std::move(AST), + util::OwnedRegion{std::move(Shm), std::move(Region)}); + + if (Eval) { + Eval->RegisterBC(rpc::RegisterBCParams{ + ShmName, ".", ".", static_cast(Buf.size())}); + } + }; + if (LitTest) { + // In lit-testing mode we don't want to having requests processed before + // document updates. (e.g. Hover before parsing) + // So just invoke the action here. + Action(); + } else { + // Otherwise we may want to concurently parse & serialize the file, so post + // it to the thread pool. + boost::asio::post(Pool, std::move(Action)); } } Controller::Controller(std::unique_ptr In, std::unique_ptr Out) - : LSPServer(std::move(In), std::move(Out)) { + : LSPServer(std::move(In), std::move(Out)), LitTest(false) { // Life Cycle Registry.addMethod("initialize", this, &Controller::onInitialize); diff --git a/nixd/tools/nixd/test/hover.md b/nixd/tools/nixd/test/hover-1.md similarity index 63% rename from nixd/tools/nixd/test/hover.md rename to nixd/tools/nixd/test/hover-1.md index 64ad2174d..9bdd4e05b 100644 --- a/nixd/tools/nixd/test/hover.md +++ b/nixd/tools/nixd/test/hover-1.md @@ -76,47 +76,6 @@ CHECK-NEXT: } CHECK-NEXT: } ``` -<-- textDocument/hover(2) - - -```json -{ - "jsonrpc":"2.0", - "id":2, - "method":"textDocument/hover", - "params":{ - "textDocument":{ - "uri":"file:///basic.nix" - }, - "position":{ - "line":0, - "character":4 - } - } -} -``` - -``` - CHECK: "id": 2, -CHECK-NEXT: "jsonrpc": "2.0", -CHECK-NEXT: "result": { -CHECK-NEXT: "contents": { -CHECK-NEXT: "kind": "markdown", -CHECK-NEXT: "value": "`Binding`" -CHECK-NEXT: }, -CHECK-NEXT: "range": { -CHECK-NEXT: "end": { -CHECK-NEXT: "character": 8, -CHECK-NEXT: "line": 0 -CHECK-NEXT: }, -CHECK-NEXT: "start": { -CHECK-NEXT: "character": 2, -CHECK-NEXT: "line": 0 -CHECK-NEXT: } -CHECK-NEXT: } -CHECK-NEXT: } -``` - ```json {"jsonrpc":"2.0","method":"exit"} ``` diff --git a/nixd/tools/nixd/test/hover-2.md b/nixd/tools/nixd/test/hover-2.md new file mode 100644 index 000000000..c1da45158 --- /dev/null +++ b/nixd/tools/nixd/test/hover-2.md @@ -0,0 +1,81 @@ +# RUN: nixd --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":"{ a = 1; }" + } + } +} +``` + +<-- textDocument/hover(2) + + +```json +{ + "jsonrpc":"2.0", + "id":2, + "method":"textDocument/hover", + "params":{ + "textDocument":{ + "uri":"file:///basic.nix" + }, + "position":{ + "line":0, + "character":4 + } + } +} +``` + +``` + CHECK: "id": 2, +CHECK-NEXT: "jsonrpc": "2.0", +CHECK-NEXT: "result": { +CHECK-NEXT: "contents": { +CHECK-NEXT: "kind": "markdown", +CHECK-NEXT: "value": "`Binding`" +CHECK-NEXT: }, +CHECK-NEXT: "range": { +CHECK-NEXT: "end": { +CHECK-NEXT: "character": 8, +CHECK-NEXT: "line": 0 +CHECK-NEXT: }, +CHECK-NEXT: "start": { +CHECK-NEXT: "character": 2, +CHECK-NEXT: "line": 0 +CHECK-NEXT: } +CHECK-NEXT: } +CHECK-NEXT: } +``` + +```json +{"jsonrpc":"2.0","method":"exit"} +```