From 80b67f85f27738551acf555cbc6df92fb5309838 Mon Sep 17 00:00:00 2001 From: Yingchi Long Date: Tue, 25 Jul 2023 22:44:09 +0800 Subject: [PATCH 1/7] nixd/Server: split worker in a dedicated class TODO: sync the draft store --- nixd/include/nixd/Nix/Eval.h | 22 ++ nixd/include/nixd/Nix/Init.h | 16 ++ .../nixd/Server/{Server.h => Controller.h} | 69 +----- nixd/include/nixd/Server/EvalWorker.h | 56 +++++ nixd/include/nixd/Support/CompletionHelper.h | 18 ++ nixd/include/nixd/Support/JSONSerialization.h | 2 + nixd/include/nixd/Support/ReplyRAII.h | 22 ++ nixd/lib/Server/Controller.cpp | 140 ++++++------ nixd/lib/Server/Eval.cpp | 194 +---------------- nixd/lib/Server/EvalWorker.cpp | 205 ++++++++++++++++++ nixd/lib/Server/Option.cpp | 18 +- nixd/lib/Server/meson.build | 4 +- .../Nix.cpp => Support/CompletionHelper.cpp} | 34 +-- nixd/lib/Support/meson.build | 1 + nixd/tools/nixd/nixd.cpp | 56 +++-- 15 files changed, 481 insertions(+), 376 deletions(-) create mode 100644 nixd/include/nixd/Nix/Eval.h create mode 100644 nixd/include/nixd/Nix/Init.h rename nixd/include/nixd/Server/{Server.h => Controller.h} (84%) create mode 100644 nixd/include/nixd/Server/EvalWorker.h create mode 100644 nixd/include/nixd/Support/CompletionHelper.h create mode 100644 nixd/include/nixd/Support/ReplyRAII.h create mode 100644 nixd/lib/Server/EvalWorker.cpp rename nixd/lib/{Server/Nix.cpp => Support/CompletionHelper.cpp} (62%) diff --git a/nixd/include/nixd/Nix/Eval.h b/nixd/include/nixd/Nix/Eval.h new file mode 100644 index 000000000..43f729994 --- /dev/null +++ b/nixd/include/nixd/Nix/Eval.h @@ -0,0 +1,22 @@ +#include +#include + +namespace nix { + +// Copy-paste from nix source code, do not know why it is inlined. + +inline void EvalState::evalAttrs(Env &env, Expr *e, Value &v, const PosIdx pos, + std::string_view errorCtx) { + try { + e->eval(*this, env, v); + if (v.type() != nAttrs) + error("value is %1% while a set was expected", showType(v)) + .withFrame(env, *e) + .debugThrow(); + } catch (Error &e) { + e.addTrace(positions[pos], errorCtx); + throw; + } +} + +} // namespace nix diff --git a/nixd/include/nixd/Nix/Init.h b/nixd/include/nixd/Nix/Init.h new file mode 100644 index 000000000..e9b5064ae --- /dev/null +++ b/nixd/include/nixd/Nix/Init.h @@ -0,0 +1,16 @@ +#pragma once + +#include +#include +#include + +namespace nixd { + +inline void initEval() { + nix::initNix(); + nix::initLibStore(); + nix::initPlugins(); + nix::initGC(); +} + +} // namespace nixd diff --git a/nixd/include/nixd/Server/Server.h b/nixd/include/nixd/Server/Controller.h similarity index 84% rename from nixd/include/nixd/Server/Server.h rename to nixd/include/nixd/Server/Controller.h index c79852cb2..e1d1791b6 100644 --- a/nixd/include/nixd/Server/Server.h +++ b/nixd/include/nixd/Server/Controller.h @@ -38,15 +38,8 @@ namespace nixd { -struct CompletionHelper { - using Items = std::vector; - static void fromEnv(nix::EvalState &State, nix::Env &NixEnv, Items &Items); - static void fromStaticEnv(const nix::SymbolTable &STable, - const nix::StaticEnv &SEnv, Items &Items); -}; - /// The server instance, nix-related language features goes here -class Server : public lspserver::LSPServer { +class Controller : public lspserver::LSPServer { public: using WorkspaceVersionTy = ipc::WorkspaceVersionTy; @@ -191,17 +184,11 @@ class Server : public lspserver::LSPServer { nix::Value *OptionAttrSet; std::unique_ptr OptionIES; - // Worker::Eval - - llvm::unique_function EvalDiagnostic; - - std::unique_ptr IER; - public: - Server(std::unique_ptr In, - std::unique_ptr Out, int WaitWorker = 0); + Controller(std::unique_ptr In, + std::unique_ptr Out, int WaitWorker = 0); - ~Server() override { + ~Controller() override { if (WaitWorker) { Pool.join(); std::lock_guard Guard(EvalWorkerLock); @@ -325,10 +312,6 @@ class Server : public lspserver::LSPServer { return Vec; } - // Worker - - void initWorker(); - // Worker::Nix::Option void forkOptionWorker(); @@ -340,31 +323,11 @@ class Server : public lspserver::LSPServer { void onOptionCompletion(const ipc::AttrPathParams &, lspserver::Callback); - - // Worker::Nix::Eval - - void switchToEvaluator(); - - template - void withAST( - const std::string &, ReplyRAII, - llvm::unique_function, ReplyRAII &&)>); - - void evalInstallable(); - - void onEvalDefinition(const lspserver::TextDocumentPositionParams &, - lspserver::Callback); - - void onEvalHover(const lspserver::TextDocumentPositionParams &, - lspserver::Callback); - - void onEvalCompletion(const lspserver::CompletionParams &, - lspserver::Callback); }; template -auto Server::askWorkers( - const std::deque> &Workers, +auto Controller::askWorkers( + const std::deque> &Workers, std::shared_mutex &WorkerLock, llvm::StringRef IPCMethod, const Arg &Params, unsigned Timeout) { // For all active workers, send the completion request @@ -418,24 +381,4 @@ auto Server::askWorkers( return AnsweredResp; } -template -void Server::withAST( - const std::string &RequestedFile, ReplyRAII RR, - llvm::unique_function, ReplyRAII &&)> - Action) { - try { - auto AST = IER->Forest.at(RequestedFile); - try { - Action(AST, std::move(RR)); - } catch (std::exception &E) { - RR.Response = - lspserver::error("something uncaught in the AST action, reason {0}", - stripANSI(E.what())); - } - } catch (std::out_of_range &E) { - RR.Response = lspserver::error("no AST available on requested file {0}", - RequestedFile); - } -} - }; // namespace nixd diff --git a/nixd/include/nixd/Server/EvalWorker.h b/nixd/include/nixd/Server/EvalWorker.h new file mode 100644 index 000000000..e6907f79c --- /dev/null +++ b/nixd/include/nixd/Server/EvalWorker.h @@ -0,0 +1,56 @@ +#pragma once + +#include "nixd/Server/EvalDraftStore.h" +#include "nixd/Support/Diagnostic.h" +#include "nixd/Support/JSONSerialization.h" +#include "nixd/Support/ReplyRAII.h" + +#include "lspserver/LSPServer.h" + +namespace nixd { + +class EvalWorker : public lspserver::LSPServer { + llvm::unique_function EvalDiagnostic; + + std::unique_ptr IER; + + template + void withAST( + const std::string &, ReplyRAII, + llvm::unique_function, ReplyRAII &&)>); + +public: + EvalWorker(std::unique_ptr In, + std::unique_ptr Out); + + void onEvalDefinition(const lspserver::TextDocumentPositionParams &, + lspserver::Callback); + + void onEvalHover(const lspserver::TextDocumentPositionParams &, + lspserver::Callback); + + void onEvalCompletion(const lspserver::CompletionParams &, + lspserver::Callback); +}; + +template +void EvalWorker::withAST( + const std::string &RequestedFile, ReplyRAII RR, + llvm::unique_function, ReplyRAII &&)> + Action) { + try { + auto AST = IER->Forest.at(RequestedFile); + try { + Action(AST, std::move(RR)); + } catch (std::exception &E) { + RR.Response = + lspserver::error("something uncaught in the AST action, reason {0}", + stripANSI(E.what())); + } + } catch (std::out_of_range &E) { + RR.Response = lspserver::error("no AST available on requested file {0}", + RequestedFile); + } +} + +} // namespace nixd diff --git a/nixd/include/nixd/Support/CompletionHelper.h b/nixd/include/nixd/Support/CompletionHelper.h new file mode 100644 index 000000000..848f63c24 --- /dev/null +++ b/nixd/include/nixd/Support/CompletionHelper.h @@ -0,0 +1,18 @@ +#pragma once + +#include "lspserver/Protocol.h" + +#include + +#include + +namespace nixd { + +struct CompletionHelper { + using Items = std::vector; + static void fromEnv(nix::EvalState &State, nix::Env &NixEnv, Items &Items); + static void fromStaticEnv(const nix::SymbolTable &STable, + const nix::StaticEnv &SEnv, Items &Items); +}; + +} // namespace nixd diff --git a/nixd/include/nixd/Support/JSONSerialization.h b/nixd/include/nixd/Support/JSONSerialization.h index 6d2e553c1..2c21eca93 100644 --- a/nixd/include/nixd/Support/JSONSerialization.h +++ b/nixd/include/nixd/Support/JSONSerialization.h @@ -1,3 +1,5 @@ +#pragma once + #include "lspserver/Protocol.h" #include diff --git a/nixd/include/nixd/Support/ReplyRAII.h b/nixd/include/nixd/Support/ReplyRAII.h new file mode 100644 index 000000000..1e1bd3a02 --- /dev/null +++ b/nixd/include/nixd/Support/ReplyRAII.h @@ -0,0 +1,22 @@ +#pragma once + +#include "lspserver/Function.h" +#include "lspserver/Logger.h" + +namespace nixd { + +template struct ReplyRAII { + lspserver::Callback R; + llvm::Expected Response = lspserver::error("no response available"); + ReplyRAII(decltype(R) R) : R(std::move(R)) {} + ~ReplyRAII() { + if (R) + R(std::move(Response)); + }; + ReplyRAII(ReplyRAII &&Old) noexcept { + R = std::move(Old.R); + Response = std::move(Old.Response); + } +}; + +} // namespace nixd diff --git a/nixd/lib/Server/Controller.cpp b/nixd/lib/Server/Controller.cpp index 3bb452324..49cb800d7 100644 --- a/nixd/lib/Server/Controller.cpp +++ b/nixd/lib/Server/Controller.cpp @@ -5,8 +5,8 @@ #include "nixd/Parser/Parser.h" #include "nixd/Parser/Require.h" #include "nixd/Server/ASTManager.h" +#include "nixd/Server/Controller.h" #include "nixd/Server/EvalDraftStore.h" -#include "nixd/Server/Server.h" #include "nixd/Support/Diagnostic.h" #include "nixd/Support/Support.h" @@ -45,9 +45,9 @@ namespace nixd { -void Server::forkWorker(llvm::unique_function WorkerAction, - std::deque> &WorkerPool, - size_t Size) { +void Controller::forkWorker(llvm::unique_function WorkerAction, + std::deque> &WorkerPool, + size_t Size) { if (Role != ServerRole::Controller) return; auto To = std::make_unique(); @@ -61,22 +61,11 @@ void Server::forkWorker(llvm::unique_function WorkerAction, lspserver::elog("Cannot create child worker process"); // TODO reason? } else if (ForkPID == 0) { - // child, it should be a COW fork of the parent process info, as a snapshot - // we will leave the evaluation task (or any language feature) to the child - // process, and forward language feature requests to this childs, and choose - // the best response. - auto ChildPID = getpid(); - lspserver::elog("created child worker process {0}", ChildPID); - // Redirect stdin & stdout to our pipes, instead of LSP clients dup2(To->readSide.get(), 0); dup2(From->writeSide.get(), 1); - WorkerAction(); - // Communicate the controller in stadnard mode, instead of lit testing - switchStreamStyle(lspserver::JSONStreamStyle::Standard); - } else { auto WorkerInputDispatcher = @@ -112,18 +101,24 @@ void Server::forkWorker(llvm::unique_function WorkerAction, } } -void Server::updateWorkspaceVersion() { +void Controller::updateWorkspaceVersion() { if (Role != ServerRole::Controller) return; WorkspaceVersion++; std::lock_guard EvalGuard(EvalWorkerLock); // The eval worker - forkWorker([this]() { switchToEvaluator(); }, EvalWorkers, - Config.eval.workers); + forkWorker( + []() { + if (auto Name = nix::getSelfExe()) { + execv(Name->c_str(), + nix::stringsToCharPtrs({"--role=worker"}).data()); + } + }, + EvalWorkers, Config.eval.workers); } -void Server::addDocument(lspserver::PathRef File, llvm::StringRef Contents, - llvm::StringRef Version) { +void Controller::addDocument(lspserver::PathRef File, llvm::StringRef Contents, + llvm::StringRef Version) { using namespace lspserver; auto IVersion = DraftStore::decodeVersion(Version); // Since this file is updated, we first clear its diagnostic @@ -138,13 +133,13 @@ void Server::addDocument(lspserver::PathRef File, llvm::StringRef Contents, updateWorkspaceVersion(); } -void Server::updateConfig(configuration::TopLevel &&NewConfig) { +void Controller::updateConfig(configuration::TopLevel &&NewConfig) { Config = std::move(NewConfig); forkOptionWorker(); updateWorkspaceVersion(); } -void Server::fetchConfig() { +void Controller::fetchConfig() { if (ClientCaps.WorkspaceConfiguration) { WorkspaceConfiguration( lspserver::ConfigurationParams{ @@ -159,7 +154,7 @@ void Server::fetchConfig() { } llvm::Expected -Server::parseConfig(llvm::StringRef JSON) { +Controller::parseConfig(llvm::StringRef JSON) { using namespace configuration; auto ExpectedValue = llvm::json::parse(JSON); @@ -172,7 +167,7 @@ Server::parseConfig(llvm::StringRef JSON) { return lspserver::error("value cannot be converted to internal config type"); } -void Server::readJSONConfig(lspserver::PathRef File) noexcept { +void Controller::readJSONConfig(lspserver::PathRef File) noexcept { try { std::string ConfigStr; std::ostringstream SS; @@ -189,70 +184,72 @@ void Server::readJSONConfig(lspserver::PathRef File) noexcept { } } -std::string Server::encodeVersion(std::optional LSPVersion) { +std::string Controller::encodeVersion(std::optional LSPVersion) { return LSPVersion ? llvm::to_string(*LSPVersion) : ""; } std::shared_ptr -Server::getDraft(lspserver::PathRef File) const { +Controller::getDraft(lspserver::PathRef File) const { auto Draft = DraftMgr.getDraft(File); if (!Draft) return nullptr; return std::move(Draft->Contents); } -Server::Server(std::unique_ptr In, - std::unique_ptr Out, int WaitWorker) +Controller::Controller(std::unique_ptr In, + std::unique_ptr Out, + int WaitWorker) : LSPServer(std::move(In), std::move(Out)), WaitWorker(WaitWorker), ASTMgr(Pool) { // Life Cycle - Registry.addMethod("initialize", this, &Server::onInitialize); - Registry.addNotification("initialized", this, &Server::onInitialized); + Registry.addMethod("initialize", this, &Controller::onInitialize); + Registry.addNotification("initialized", this, &Controller::onInitialized); // Text Document Synchronization Registry.addNotification("textDocument/didOpen", this, - &Server::onDocumentDidOpen); + &Controller::onDocumentDidOpen); Registry.addNotification("textDocument/didChange", this, - &Server::onDocumentDidChange); + &Controller::onDocumentDidChange); Registry.addNotification("textDocument/didClose", this, - &Server::onDocumentDidClose); + &Controller::onDocumentDidClose); // Language Features Registry.addMethod("textDocument/documentLink", this, - &Server::onDocumentLink); + &Controller::onDocumentLink); Registry.addMethod("textDocument/documentSymbol", this, - &Server::onDocumentSymbol); - 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); - Registry.addMethod("textDocument/rename", this, &Server::onRename); + &Controller::onDocumentSymbol); + Registry.addMethod("textDocument/hover", this, &Controller::onHover); + Registry.addMethod("textDocument/completion", this, + &Controller::onCompletion); + Registry.addMethod("textDocument/declaration", this, + &Controller::onDecalration); + Registry.addMethod("textDocument/definition", this, + &Controller::onDefinition); + Registry.addMethod("textDocument/formatting", this, &Controller::onFormat); + Registry.addMethod("textDocument/rename", this, &Controller::onRename); Registry.addMethod("textDocument/prepareRename", this, - &Server::onPrepareRename); + &Controller::onPrepareRename); PublishDiagnostic = mkOutNotifiction( "textDocument/publishDiagnostics"); // Workspace Registry.addNotification("workspace/didChangeConfiguration", this, - &Server::onWorkspaceDidChangeConfiguration); + &Controller::onWorkspaceDidChangeConfiguration); WorkspaceConfiguration = mkOutMethod( "workspace/configuration"); /// IPC Registry.addNotification("nixd/ipc/diagnostic", this, - &Server::onEvalDiagnostic); - - Registry.addMethod("nixd/ipc/textDocument/hover", this, &Server::onEvalHover); + &Controller::onEvalDiagnostic); Registry.addMethod("nixd/ipc/option/textDocument/declaration", this, - &Server::onOptionDeclaration); + &Controller::onOptionDeclaration); - Registry.addNotification("nixd/ipc/finished", this, &Server::onFinished); + Registry.addNotification("nixd/ipc/finished", this, &Controller::onFinished); readJSONConfig(); } @@ -260,8 +257,9 @@ Server::Server(std::unique_ptr In, //-----------------------------------------------------------------------------/ // Life Cycle -void Server::onInitialize(const lspserver::InitializeParams &InitializeParams, - lspserver::Callback Reply) { +void Controller::onInitialize( + const lspserver::InitializeParams &InitializeParams, + lspserver::Callback Reply) { ClientCaps = InitializeParams.capabilities; llvm::json::Object ServerCaps{ {"textDocumentSync", @@ -292,7 +290,7 @@ void Server::onInitialize(const lspserver::InitializeParams &InitializeParams, //-----------------------------------------------------------------------------/ // Text Document Synchronization -void Server::onDocumentDidOpen( +void Controller::onDocumentDidOpen( const lspserver::DidOpenTextDocumentParams &Params) { lspserver::PathRef File = Params.textDocument.uri.file(); @@ -301,7 +299,7 @@ void Server::onDocumentDidOpen( addDocument(File, Contents, encodeVersion(Params.textDocument.version)); } -void Server::onDocumentDidChange( +void Controller::onDocumentDidChange( const lspserver::DidChangeTextDocumentParams &Params) { lspserver::PathRef File = Params.textDocument.uri.file(); auto Code = getDraft(File); @@ -324,7 +322,7 @@ void Server::onDocumentDidChange( addDocument(File, NewCode, encodeVersion(Params.textDocument.version)); } -void Server::onDocumentDidClose( +void Controller::onDocumentDidClose( const lspserver::DidCloseTextDocumentParams &Params) { lspserver::PathRef File = Params.textDocument.uri.file(); auto Code = getDraft(File); @@ -334,8 +332,9 @@ void Server::onDocumentDidClose( //-----------------------------------------------------------------------------/ // Language Features -void Server::onDecalration(const lspserver::TextDocumentPositionParams &Params, - lspserver::Callback Reply) { +void Controller::onDecalration( + const lspserver::TextDocumentPositionParams &Params, + lspserver::Callback Reply) { if (!Config.options.enable) { Reply(nullptr); return; @@ -393,8 +392,9 @@ void Server::onDecalration(const lspserver::TextDocumentPositionParams &Params, boost::asio::post(Pool, std::move(Task)); } -void Server::onDefinition(const lspserver::TextDocumentPositionParams &Params, - lspserver::Callback Reply) { +void Controller::onDefinition( + const lspserver::TextDocumentPositionParams &Params, + lspserver::Callback Reply) { using RTy = lspserver::Location; using namespace lspserver; using V = llvm::json::Value; @@ -439,7 +439,7 @@ void Server::onDefinition(const lspserver::TextDocumentPositionParams &Params, boost::asio::post(Pool, std::move(Task)); } -void Server::onDocumentLink( +void Controller::onDocumentLink( const lspserver::DocumentLinkParams &Params, lspserver::Callback> Reply) { auto Task = [=, Reply = std::move(Reply), this]() mutable { @@ -456,7 +456,7 @@ void Server::onDocumentLink( boost::asio::post(Pool, std::move(Task)); } -void Server::onDocumentSymbol( +void Controller::onDocumentSymbol( const lspserver::DocumentSymbolParams &Params, lspserver::Callback> Reply) { @@ -473,8 +473,8 @@ void Server::onDocumentSymbol( boost::asio::post(Pool, std::move(Task)); } -void Server::onHover(const lspserver::TextDocumentPositionParams &Params, - lspserver::Callback Reply) { +void Controller::onHover(const lspserver::TextDocumentPositionParams &Params, + lspserver::Callback Reply) { using RTy = lspserver::Hover; constexpr auto Method = "nixd/ipc/textDocument/hover"; auto Task = [=, Reply = std::move(Reply), this]() mutable { @@ -488,7 +488,7 @@ void Server::onHover(const lspserver::TextDocumentPositionParams &Params, boost::asio::post(Pool, std::move(Task)); } -void Server::onCompletion( +void Controller::onCompletion( const lspserver::CompletionParams &Params, lspserver::Callback Reply) { auto EnableOption = Config.options.enable; @@ -560,8 +560,8 @@ void Server::onCompletion( boost::asio::post(Pool, std::move(Task)); } -void Server::onRename(const lspserver::RenameParams &Params, - lspserver::Callback Reply) { +void Controller::onRename(const lspserver::RenameParams &Params, + lspserver::Callback Reply) { auto Task = [Params, Reply = std::move(Reply), this]() mutable { auto URI = Params.textDocument.uri; @@ -586,7 +586,7 @@ void Server::onRename(const lspserver::RenameParams &Params, boost::asio::post(Pool, std::move(Task)); } -void Server::onPrepareRename( +void Controller::onPrepareRename( const lspserver::TextDocumentPositionParams &Params, lspserver::Callback Reply) { auto Task = [Params, Reply = std::move(Reply), this]() mutable { @@ -613,19 +613,19 @@ void Server::onPrepareRename( boost::asio::post(Pool, std::move(Task)); } -void Server::clearDiagnostic(lspserver::PathRef Path) { +void Controller::clearDiagnostic(lspserver::PathRef Path) { lspserver::URIForFile Uri = lspserver::URIForFile::canonicalize(Path, Path); clearDiagnostic(Uri); } -void Server::clearDiagnostic(const lspserver::URIForFile &FileUri) { +void Controller::clearDiagnostic(const lspserver::URIForFile &FileUri) { lspserver::PublishDiagnosticsParams Notification; Notification.uri = FileUri; Notification.diagnostics = {}; PublishDiagnostic(Notification); } -void Server::onEvalDiagnostic(const ipc::Diagnostics &Diag) { +void Controller::onEvalDiagnostic(const ipc::Diagnostics &Diag) { lspserver::log("received diagnostic from worker: {0}", Diag.WorkspaceVersion); { @@ -649,9 +649,9 @@ void Server::onEvalDiagnostic(const ipc::Diagnostics &Diag) { } } -void Server::onFinished(const ipc::WorkerMessage &) { FinishSmp.release(); } +void Controller::onFinished(const ipc::WorkerMessage &) { FinishSmp.release(); } -void Server::onFormat( +void Controller::onFormat( const lspserver::DocumentFormattingParams &Params, lspserver::Callback> Reply) { diff --git a/nixd/lib/Server/Eval.cpp b/nixd/lib/Server/Eval.cpp index 9a025a9dc..d7ac628e8 100644 --- a/nixd/lib/Server/Eval.cpp +++ b/nixd/lib/Server/Eval.cpp @@ -29,23 +29,23 @@ namespace nixd { -void Server::switchToEvaluator() { - initWorker(); +void Controller::switchToEvaluator() { + // initWorker(); Role = ServerRole::Evaluator; EvalDiagnostic = mkOutNotifiction("nixd/ipc/diagnostic"); - Registry.addMethod("nixd/ipc/textDocument/completion", this, - &Server::onEvalCompletion); + // Registry.addMethod("nixd/ipc/textDocument/completion", this, + // &Server::onEvalCompletion); - Registry.addMethod("nixd/ipc/textDocument/definition", this, - &Server::onEvalDefinition); + // Registry.addMethod("nixd/ipc/textDocument/definition", this, + // &Server::onEvalDefinition); evalInstallable(); mkOutNotifiction("nixd/ipc/finished")( ipc::WorkerMessage{WorkspaceVersion}); } -void Server::evalInstallable() { +void Controller::evalInstallable() { assert(Role != ServerRole::Controller && "must be called in child workers."); auto Session = std::make_unique(); @@ -83,184 +83,4 @@ void Server::evalInstallable() { std::move(Session)); } -void Server::onEvalDefinition( - const lspserver::TextDocumentPositionParams &Params, - lspserver::Callback Reply) { - using namespace lspserver; - withAST( - Params.textDocument.uri.file().str(), - ReplyRAII(std::move(Reply)), - [Params, this](const nix::ref &AST, ReplyRAII &&RR) { - auto State = IER->Session->getState(); - - const auto *Node = AST->lookupContainMin(Params.position); - if (!Node) - return; - - // If the expression evaluates to a "derivation", try to bring our user - // to the location which defines the package. - try { - auto V = AST->getValueEval(Node, *State); - if (nix::nixd::isDerivation(*State, V)) { - if (auto S = nix::nixd::attrPathStr(*State, V, "meta.position")) { - llvm::StringRef PositionStr = S.value(); - auto [Path, LineStr] = PositionStr.split(':'); - int Line; - if (LLVM_LIKELY(!LineStr.getAsInteger(10, Line))) { - lspserver::Position Position{.line = Line, .character = 0}; - RR.Response = Location{URIForFile::canonicalize(Path, Path), - {Position, Position}}; - return; - } - } - } else { - // There is a value avaiable, this might be useful for locations - auto P = V.determinePos(nix::noPos); - if (P != nix::noPos) { - auto Pos = State->positions[P]; - if (auto *SourcePath = - std::get_if(&Pos.origin)) { - auto Path = SourcePath->to_string(); - lspserver::Position Position = toLSPPos(State->positions[P]); - RR.Response = Location{URIForFile::canonicalize(Path, Path), - {Position, Position}}; - return; - } - } - } - } catch (...) { - lspserver::vlog("no associated value on this expression"); - } - }); -} - -void Server::onEvalHover(const lspserver::TextDocumentPositionParams &Params, - lspserver::Callback Reply) { - using namespace lspserver; - withAST( - Params.textDocument.uri.file().str(), ReplyRAII(std::move(Reply)), - [Params, this](const nix::ref &AST, ReplyRAII &&RR) { - const auto *Node = AST->lookupContainMin(Params.position); - if (!Node) - return; - const auto *ExprName = getExprName(Node); - RR.Response = Hover{{MarkupKind::Markdown, ""}, std::nullopt}; - auto &HoverText = RR.Response->contents.value; - try { - auto Value = AST->getValueEval(Node, *IER->Session->getState()); - std::stringstream Res{}; - nix::nixd::PrintDepth = 3; - Value.print(IER->Session->getState()->symbols, Res); - HoverText = - llvm::formatv("## {0} \n Value: `{1}`", ExprName, Res.str()); - } catch (const std::out_of_range &) { - // No such value, just reply dummy item - std::stringstream NodeOut; - Node->show(IER->Session->getState()->symbols, NodeOut); - lspserver::vlog("no associated value on node {0}!", NodeOut.str()); - HoverText = llvm::formatv("`{0}`", ExprName); - } - }); -} - -void Server::onEvalCompletion(const lspserver::CompletionParams &Params, - lspserver::Callback Reply) { - using namespace lspserver; - auto Action = [Params, this](const nix::ref &AST, - ReplyRAII &&RR) { - auto State = IER->Session->getState(); - // Lookup an AST node, and get it's 'Env' after evaluation - CompletionHelper::Items Items; - lspserver::log("current trigger character is {0}", - Params.context.triggerCharacter); - - if (Params.context.triggerCharacter == ".") { - const auto *Node = AST->lookupEnd(Params.position); - if (!Node) - return; - try { - auto Value = AST->getValueEval(Node, *IER->Session->getState()); - if (Value.type() == nix::ValueType::nAttrs) { - // Traverse attribute bindings - for (auto Binding : *Value.attrs) { - Items.emplace_back( - CompletionItem{.label = State->symbols[Binding.name], - .kind = CompletionItemKind::Field}); - if (Items.size() > 5000) - break; - } - } - } catch (std::out_of_range &) { - RR.Response = error("no associated value on requested attrset."); - return; - } - } else { - - const auto *Node = AST->lookupContainMin(Params.position); - - // TODO: share the same code with static worker - std::vector Symbols; - AST->collectSymbols(Node, Symbols); - // Insert symbols to our completion list. - std::transform(Symbols.begin(), Symbols.end(), std::back_inserter(Items), - [&](const nix::Symbol &V) -> decltype(Items)::value_type { - decltype(Items)::value_type R; - R.kind = CompletionItemKind::Interface; - R.label = State->symbols[V]; - return R; - }); - - auto FromLambdaFormals = [&]() { - // Firstly, we check that if we are in "ExprAttrs", consider this - // is a - // special case for completion lambda formals - if (!dynamic_cast(Node)) - return; - // Then, check that if the parent of evaluates to a "" - try { - const auto *Parent = AST->parent(Node); - if (const auto *SomeExprCall = - dynamic_cast(Parent)) { - auto Value = AST->getValue(SomeExprCall->fun); - if (!Value.isLambda()) - return; - // Then, filling the completion list using that lambda - // formals. - auto *Fun = Value.lambda.fun; - if (!Fun->hasFormals()) - return; - for (auto Formal : Fun->formals->formals) { - CompletionItem Item; - Item.label = State->symbols[Formal.name]; - Item.kind = CompletionItemKind::Constructor; - Items.emplace_back(std::move(Item)); - } - } - - } catch (...) { - } - }; - FromLambdaFormals(); - try { - if (!Node) - return; - auto *ExprEnv = AST->searchUpEnv(Node); - CompletionHelper::fromEnv(*State, *ExprEnv, Items); - } catch (std::out_of_range &) { - } - CompletionHelper::fromStaticEnv(State->symbols, *State->staticBaseEnv, - Items); - } - // Make the response. - CompletionList List; - List.isIncomplete = false; - List.items = Items; - RR.Response = List; - }; - - withAST(Params.textDocument.uri.file().str(), - ReplyRAII(std::move(Reply)), - std::move(Action)); -} - } // namespace nixd diff --git a/nixd/lib/Server/EvalWorker.cpp b/nixd/lib/Server/EvalWorker.cpp new file mode 100644 index 000000000..8441980cf --- /dev/null +++ b/nixd/lib/Server/EvalWorker.cpp @@ -0,0 +1,205 @@ +#include "nixd/Server/EvalWorker.h" +#include "nixd/Nix/Value.h" +#include "nixd/Support/CompletionHelper.h" + +#include "lspserver/LSPServer.h" + +namespace nixd { + +EvalWorker::EvalWorker(std::unique_ptr In, + std::unique_ptr Out) + : lspserver::LSPServer(std::move(In), std::move(Out)) { + + EvalDiagnostic = mkOutNotifiction("nixd/ipc/diagnostic"); + + Registry.addMethod("nixd/ipc/textDocument/hover", this, + &EvalWorker::onEvalHover); + Registry.addMethod("nixd/ipc/textDocument/completion", this, + &EvalWorker::onEvalCompletion); + Registry.addMethod("nixd/ipc/textDocument/definition", this, + &EvalWorker::onEvalDefinition); +} + +void EvalWorker::onEvalDefinition( + const lspserver::TextDocumentPositionParams &Params, + lspserver::Callback Reply) { + using namespace lspserver; + withAST( + Params.textDocument.uri.file().str(), + ReplyRAII(std::move(Reply)), + [Params, this](const nix::ref &AST, ReplyRAII &&RR) { + auto State = IER->Session->getState(); + + const auto *Node = AST->lookupContainMin(Params.position); + if (!Node) + return; + + // If the expression evaluates to a "derivation", try to bring our user + // to the location which defines the package. + try { + auto V = AST->getValueEval(Node, *State); + if (nix::nixd::isDerivation(*State, V)) { + if (auto S = nix::nixd::attrPathStr(*State, V, "meta.position")) { + llvm::StringRef PositionStr = S.value(); + auto [Path, LineStr] = PositionStr.split(':'); + int Line; + if (LLVM_LIKELY(!LineStr.getAsInteger(10, Line))) { + lspserver::Position Position{.line = Line, .character = 0}; + RR.Response = Location{URIForFile::canonicalize(Path, Path), + {Position, Position}}; + return; + } + } + } else { + // There is a value avaiable, this might be useful for locations + auto P = V.determinePos(nix::noPos); + if (P != nix::noPos) { + auto Pos = State->positions[P]; + if (auto *SourcePath = + std::get_if(&Pos.origin)) { + auto Path = SourcePath->to_string(); + lspserver::Position Position = toLSPPos(State->positions[P]); + RR.Response = Location{URIForFile::canonicalize(Path, Path), + {Position, Position}}; + return; + } + } + } + } catch (...) { + lspserver::vlog("no associated value on this expression"); + } + }); +} + +void EvalWorker::onEvalHover( + const lspserver::TextDocumentPositionParams &Params, + lspserver::Callback Reply) { + using namespace lspserver; + withAST( + Params.textDocument.uri.file().str(), ReplyRAII(std::move(Reply)), + [Params, this](const nix::ref &AST, ReplyRAII &&RR) { + const auto *Node = AST->lookupContainMin(Params.position); + if (!Node) + return; + const auto *ExprName = getExprName(Node); + RR.Response = Hover{{MarkupKind::Markdown, ""}, std::nullopt}; + auto &HoverText = RR.Response->contents.value; + try { + auto Value = AST->getValueEval(Node, *IER->Session->getState()); + std::stringstream Res{}; + nix::nixd::PrintDepth = 3; + Value.print(IER->Session->getState()->symbols, Res); + HoverText = + llvm::formatv("## {0} \n Value: `{1}`", ExprName, Res.str()); + } catch (const std::out_of_range &) { + // No such value, just reply dummy item + std::stringstream NodeOut; + Node->show(IER->Session->getState()->symbols, NodeOut); + lspserver::vlog("no associated value on node {0}!", NodeOut.str()); + HoverText = llvm::formatv("`{0}`", ExprName); + } + }); +} + +void EvalWorker::onEvalCompletion( + const lspserver::CompletionParams &Params, + lspserver::Callback Reply) { + using namespace lspserver; + auto Action = [Params, this](const nix::ref &AST, + ReplyRAII &&RR) { + auto State = IER->Session->getState(); + // Lookup an AST node, and get it's 'Env' after evaluation + CompletionHelper::Items Items; + lspserver::log("current trigger character is {0}", + Params.context.triggerCharacter); + + if (Params.context.triggerCharacter == ".") { + const auto *Node = AST->lookupEnd(Params.position); + if (!Node) + return; + try { + auto Value = AST->getValueEval(Node, *IER->Session->getState()); + if (Value.type() == nix::ValueType::nAttrs) { + // Traverse attribute bindings + for (auto Binding : *Value.attrs) { + Items.emplace_back( + CompletionItem{.label = State->symbols[Binding.name], + .kind = CompletionItemKind::Field}); + if (Items.size() > 5000) + break; + } + } + } catch (std::out_of_range &) { + RR.Response = error("no associated value on requested attrset."); + return; + } + } else { + + const auto *Node = AST->lookupContainMin(Params.position); + + // TODO: share the same code with static worker + std::vector Symbols; + AST->collectSymbols(Node, Symbols); + // Insert symbols to our completion list. + std::transform(Symbols.begin(), Symbols.end(), std::back_inserter(Items), + [&](const nix::Symbol &V) -> decltype(Items)::value_type { + decltype(Items)::value_type R; + R.kind = CompletionItemKind::Interface; + R.label = State->symbols[V]; + return R; + }); + + auto FromLambdaFormals = [&]() { + // Firstly, we check that if we are in "ExprAttrs", consider this + // is a + // special case for completion lambda formals + if (!dynamic_cast(Node)) + return; + // Then, check that if the parent of evaluates to a "" + try { + const auto *Parent = AST->parent(Node); + if (const auto *SomeExprCall = + dynamic_cast(Parent)) { + auto Value = AST->getValue(SomeExprCall->fun); + if (!Value.isLambda()) + return; + // Then, filling the completion list using that lambda + // formals. + auto *Fun = Value.lambda.fun; + if (!Fun->hasFormals()) + return; + for (auto Formal : Fun->formals->formals) { + CompletionItem Item; + Item.label = State->symbols[Formal.name]; + Item.kind = CompletionItemKind::Constructor; + Items.emplace_back(std::move(Item)); + } + } + + } catch (...) { + } + }; + FromLambdaFormals(); + try { + if (!Node) + return; + auto *ExprEnv = AST->searchUpEnv(Node); + CompletionHelper::fromEnv(*State, *ExprEnv, Items); + } catch (std::out_of_range &) { + } + CompletionHelper::fromStaticEnv(State->symbols, *State->staticBaseEnv, + Items); + } + // Make the response. + CompletionList List; + List.isIncomplete = false; + List.items = Items; + RR.Response = List; + }; + + withAST(Params.textDocument.uri.file().str(), + ReplyRAII(std::move(Reply)), + std::move(Action)); +} + +} // namespace nixd diff --git a/nixd/lib/Server/Option.cpp b/nixd/lib/Server/Option.cpp index 74ff53a59..562c2bf1c 100644 --- a/nixd/lib/Server/Option.cpp +++ b/nixd/lib/Server/Option.cpp @@ -1,19 +1,20 @@ #include "nixd/Nix/Option.h" +#include "nixd/Nix/Init.h" #include "nixd/Nix/Value.h" -#include "nixd/Server/Server.h" +#include "nixd/Server/Controller.h" #include "nixd/Support/Diagnostic.h" #include namespace nixd { -void Server::forkOptionWorker() { +void Controller::forkOptionWorker() { std::lock_guard _(OptionWorkerLock); forkWorker( [this]() { switchToOptionProvider(); Registry.addMethod("nixd/ipc/textDocument/completion/options", this, - &Server::onOptionCompletion); + &Controller::onOptionCompletion); for (auto &W : OptionWorkers) { W->Pid.release(); } @@ -21,7 +22,7 @@ void Server::forkOptionWorker() { OptionWorkers, 1); } -void Server::onOptionDeclaration( +void Controller::onOptionDeclaration( const ipc::AttrPathParams &Params, lspserver::Callback Reply) { assert(Role == ServerRole::OptionProvider && @@ -77,8 +78,8 @@ void Server::onOptionDeclaration( } } -void Server::switchToOptionProvider() { - initWorker(); +void Controller::switchToOptionProvider() { + initEval(); Role = ServerRole::OptionProvider; if (!Config.options.enable) @@ -103,8 +104,9 @@ void Server::switchToOptionProvider() { } } -void Server::onOptionCompletion(const ipc::AttrPathParams &Params, - lspserver::Callback Reply) { +void Controller::onOptionCompletion( + const ipc::AttrPathParams &Params, + lspserver::Callback Reply) { using namespace lspserver; using namespace nix::nixd; ReplyRAII RR(std::move(Reply)); diff --git a/nixd/lib/Server/meson.build b/nixd/lib/Server/meson.build index 317de866d..ab233306e 100644 --- a/nixd/lib/Server/meson.build +++ b/nixd/lib/Server/meson.build @@ -7,9 +7,9 @@ libnixdServerDeps = [ nixd_lsp_server libnixdServer = library('nixdServer' , 'ASTManager.cpp' , 'Controller.cpp' -, 'Eval.cpp' +# , 'Eval.cpp' , 'EvalDraftStore.cpp' -, 'Nix.cpp' +, 'EvalWorker.cpp' , 'Option.cpp' , include_directories: nixd_inc , dependencies: libnixdServerDeps diff --git a/nixd/lib/Server/Nix.cpp b/nixd/lib/Support/CompletionHelper.cpp similarity index 62% rename from nixd/lib/Server/Nix.cpp rename to nixd/lib/Support/CompletionHelper.cpp index 83fb4f429..42925c9d7 100644 --- a/nixd/lib/Server/Nix.cpp +++ b/nixd/lib/Support/CompletionHelper.cpp @@ -1,27 +1,6 @@ -#include "nixd/Server/Server.h" +#include "nixd/Support/CompletionHelper.h" +#include "nixd/Nix/Eval.h" -#include -#include - -namespace nix { - -// Copy-paste from nix source code, do not know why it is inlined. - -inline void EvalState::evalAttrs(Env &env, Expr *e, Value &v, const PosIdx pos, - std::string_view errorCtx) { - try { - e->eval(*this, env, v); - if (v.type() != nAttrs) - error("value is %1% while a set was expected", showType(v)) - .withFrame(env, *e) - .debugThrow(); - } catch (Error &e) { - e.addTrace(positions[pos], errorCtx); - throw; - } -} - -} // namespace nix namespace nixd { void CompletionHelper::fromStaticEnv(const nix::SymbolTable &STable, @@ -61,13 +40,4 @@ void CompletionHelper::fromEnv(nix::EvalState &State, nix::Env &NixEnv, fromEnv(State, *NixEnv.up, Items); } -void Server::initWorker() { - assert(Role == ServerRole::Controller && - "Must switch from controller's fork!"); - nix::initNix(); - nix::initLibStore(); - nix::initPlugins(); - nix::initGC(); -} - } // namespace nixd diff --git a/nixd/lib/Support/meson.build b/nixd/lib/Support/meson.build index 485d196e3..4021dc90d 100644 --- a/nixd/lib/Support/meson.build +++ b/nixd/lib/Support/meson.build @@ -5,6 +5,7 @@ libnixdSupportDeps = [ nix_all ] libnixdSupport = library('nixdSupport' +, 'CompletionHelper.cpp' , 'Diagnostic.cpp' , 'JSONSerialization.cpp' , include_directories: nixd_inc diff --git a/nixd/tools/nixd/nixd.cpp b/nixd/tools/nixd/nixd.cpp index 2089a9c23..819d1faad 100644 --- a/nixd/tools/nixd/nixd.cpp +++ b/nixd/tools/nixd/nixd.cpp @@ -1,11 +1,12 @@ #include "nixd-config.h" +#include "nixd/Server/Controller.h" +#include "nixd/Server/EvalWorker.h" + #include "lspserver/Connection.h" #include "lspserver/LSPServer.h" #include "lspserver/Logger.h" -#include "nixd/Server/Server.h" - #include #include @@ -62,11 +63,16 @@ void registerSigHanlder() { } // namespace nixd +namespace { + using namespace llvm::cl; OptionCategory Misc("miscellaneous options"); +OptionCategory Debug("debug-only options (for developers)"); +OptionCategory Controller( + "options for the controller (the process interacting with users)"); -const OptionCategory *NixdCatogories[] = {&Misc}; +const OptionCategory *NixdCatogories[] = {&Misc, &Debug}; opt InputStyle{ "input-style", @@ -77,14 +83,14 @@ opt InputStyle{ "messages delimited by `// -----` lines, " "with // comment support")), init(JSONStreamStyle::Standard), - cat(Misc), + cat(Debug), Hidden, }; opt LitTest{"lit-test", desc("Abbreviation for -input-style=delimited -pretty " "-log=verbose -wait-worker. " "Intended to simplify lit tests"), - init(false), cat(Misc)}; + init(false), cat(Debug)}; opt LogLevel{ "log", desc("Verbosity of log messages written to stderr"), values( @@ -94,18 +100,25 @@ opt LogLevel{ clEnumValN(Logger::Level::Verbose, "verbose", "Low level details")), init(Logger::Level::Info), cat(Misc)}; opt PrettyPrint{"pretty", desc("Pretty-print JSON output"), init(false), - cat(Misc)}; + cat(Debug)}; opt WaitWorker{"wait-worker", desc("wait all response from workers, instead of having " "any timeout logic"), - init(false), cat(Misc)}; + init(false), cat(Debug)}; + +using NSS = nixd::Controller::ServerRole; + +opt Role{"role", desc("The role of this process, worker, controller, ..."), + values(clEnumValN(NSS::Controller, "controller", "Controller"), + clEnumValN(NSS::Evaluator, "evaluator", "Evaluator"), + clEnumValN(NSS::OptionProvider, "option", "Option")), + init(NSS::Controller), cat(Debug)}; + +} // namespace int main(int argc, char *argv[]) { using namespace lspserver; -#ifdef __linux__ - prctl(PR_SET_PDEATHSIG, SIGKILL); -#endif nixd::registerSigHanlder(); const char *FlagsEnvVar = "NIXD_FLAGS"; HideUnrelatedOptions(NixdCatogories); @@ -128,9 +141,24 @@ int main(int argc, char *argv[]) { #else lspserver::log("nixd {0} started", NIXD_VERSION); #endif - nixd::Server Server{ - std::make_unique(STDIN_FILENO, InputStyle), - std::make_unique(PrettyPrint), WaitWorker}; - Server.run(); + switch (static_cast(Role)) { + case nixd::Controller::ServerRole::Controller: { + nixd::Controller Controller{ + std::make_unique(STDIN_FILENO, InputStyle), + std::make_unique(PrettyPrint), WaitWorker}; + Controller.run(); + break; + } + case nixd::Controller::ServerRole::Evaluator: { + nixd::EvalWorker Worker{ + std::make_unique( + STDIN_FILENO, lspserver::JSONStreamStyle::Standard), + std::make_unique(/*PrettyPrint=*/false)}; + Worker.run(); + break; + } + case nixd::Controller::ServerRole::OptionProvider: + break; + } return 0; } From 11ece92fcaae57814a3247353d660d351cad5ca3 Mon Sep 17 00:00:00 2001 From: Yingchi Long Date: Thu, 27 Jul 2023 18:04:14 +0800 Subject: [PATCH 2/7] nixd/Server: sync the draft store --- lspserver/include/lspserver/DraftStore.h | 4 + lspserver/include/lspserver/LSPServer.h | 3 - lspserver/src/DraftStore.cpp | 4 + nixd/include/nixd/Server/Config.h | 52 +++++++++ .../include/nixd/Server/ConfigSerialization.h | 23 ++++ nixd/include/nixd/Server/Controller.h | 90 ++++++++-------- nixd/include/nixd/Server/EvalWorker.h | 24 +++-- nixd/include/nixd/Server/IPC.h | 28 +++++ nixd/include/nixd/Server/IPCSerialization.h | 17 +++ nixd/include/nixd/Server/Role.h | 13 +++ nixd/include/nixd/Support/JSONSerialization.h | 97 ++--------------- nixd/lib/Server/ConfigSerialization.cpp | 53 +++++++++ nixd/lib/Server/Controller.cpp | 101 +++++++++++------- nixd/lib/Server/Eval.cpp | 86 --------------- nixd/lib/Server/EvalWorker.cpp | 77 ++++++++++--- nixd/lib/Server/IPCSerialization.cpp | 36 +++++++ nixd/lib/Server/meson.build | 5 +- nixd/lib/Support/JSONSerialization.cpp | 96 +++-------------- nixd/tools/nixd/nixd.cpp | 9 +- 19 files changed, 444 insertions(+), 374 deletions(-) create mode 100644 nixd/include/nixd/Server/Config.h create mode 100644 nixd/include/nixd/Server/ConfigSerialization.h create mode 100644 nixd/include/nixd/Server/IPC.h create mode 100644 nixd/include/nixd/Server/IPCSerialization.h create mode 100644 nixd/include/nixd/Server/Role.h create mode 100644 nixd/lib/Server/ConfigSerialization.cpp delete mode 100644 nixd/lib/Server/Eval.cpp create mode 100644 nixd/lib/Server/IPCSerialization.cpp diff --git a/lspserver/include/lspserver/DraftStore.h b/lspserver/include/lspserver/DraftStore.h index 19fb9cb3f..91a4d9e80 100644 --- a/lspserver/include/lspserver/DraftStore.h +++ b/lspserver/include/lspserver/DraftStore.h @@ -47,6 +47,10 @@ class DraftStore { llvm::IntrusiveRefCntPtr asVFS() const; + /// LSP defines file versions as numbers that increase. + /// treats them as opaque and therefore uses strings instead. + static std::string encodeVersion(std::optional LSPVersion); + static std::optional decodeVersion(llvm::StringRef Encoded); private: diff --git a/lspserver/include/lspserver/LSPServer.h b/lspserver/include/lspserver/LSPServer.h index 33feb8be4..4ea65c635 100644 --- a/lspserver/include/lspserver/LSPServer.h +++ b/lspserver/include/lspserver/LSPServer.h @@ -10,9 +10,6 @@ #include -#include -#include - namespace lspserver { /// LSPServer wraps inputs & outputs, associate message IDs between calls/reply, diff --git a/lspserver/src/DraftStore.cpp b/lspserver/src/DraftStore.cpp index 58763b97f..0170971c5 100644 --- a/lspserver/src/DraftStore.cpp +++ b/lspserver/src/DraftStore.cpp @@ -81,6 +81,10 @@ static void updateVersion(DraftStore::Draft &D, } } +std::string DraftStore::encodeVersion(std::optional LSPVersion) { + return LSPVersion ? std::to_string(*LSPVersion) : ""; +} + std::string DraftStore::addDraft(PathRef File, llvm::StringRef Version, llvm::StringRef Contents) { std::lock_guard Lock(Mutex); diff --git a/nixd/include/nixd/Server/Config.h b/nixd/include/nixd/Server/Config.h new file mode 100644 index 000000000..4e1b1d4d4 --- /dev/null +++ b/nixd/include/nixd/Server/Config.h @@ -0,0 +1,52 @@ +#pragma once + +#include +#include +#include +#include + +namespace nixd::configuration { + +inline std::list toNixArgs(const std::vector &Args) { + return {Args.begin(), Args.end()}; +} +struct InstallableConfigurationItem { + std::vector args; + std::string installable; + + [[nodiscard]] bool empty() const { + return args.empty() && installable.empty(); + } + + [[nodiscard]] auto nArgs() const { return toNixArgs(args); } +}; + +struct TopLevel { + + struct Eval { + /// Nix installables that will be used for root translation unit. + InstallableConfigurationItem target; + + /// The depth you'd like to eval *after* reached "installable" target. + int depth = 0; + /// Number of workers forking + /// defaults to std::thread::hardware_concurrency + int workers = static_cast(std::thread::hardware_concurrency()); + }; + + Eval eval; + + struct Formatting { + std::string command = "nixpkgs-fmt"; + }; + Formatting formatting; + + struct Options { + bool enable = false; + InstallableConfigurationItem target; + }; + + Options options; +}; + +} // namespace nixd::configuration diff --git a/nixd/include/nixd/Server/ConfigSerialization.h b/nixd/include/nixd/Server/ConfigSerialization.h new file mode 100644 index 000000000..148856857 --- /dev/null +++ b/nixd/include/nixd/Server/ConfigSerialization.h @@ -0,0 +1,23 @@ +#pragma once + +#include "Config.h" + +#include + +namespace nixd::configuration { + +bool fromJSON(const llvm::json::Value &Params, TopLevel::Eval &R, + llvm::json::Path P); + +bool fromJSON(const llvm::json::Value &Params, TopLevel::Formatting &R, + llvm::json::Path P); + +bool fromJSON(const llvm::json::Value &Params, TopLevel::Options &R, + llvm::json::Path P); + +bool fromJSON(const llvm::json::Value &Params, TopLevel &R, llvm::json::Path P); + +bool fromJSON(const llvm::json::Value &Params, InstallableConfigurationItem &R, + llvm::json::Path P); + +} // namespace nixd::configuration diff --git a/nixd/include/nixd/Server/Controller.h b/nixd/include/nixd/Server/Controller.h index e1d1791b6..9f13e2829 100644 --- a/nixd/include/nixd/Server/Controller.h +++ b/nixd/include/nixd/Server/Controller.h @@ -4,6 +4,8 @@ #include "nixd/Parser/Require.h" #include "nixd/Server/ASTManager.h" +#include "nixd/Server/ConfigSerialization.h" +#include "nixd/Server/IPCSerialization.h" #include "nixd/Support/Diagnostic.h" #include "nixd/Support/JSONSerialization.h" @@ -50,7 +52,6 @@ class Controller : public lspserver::LSPServer { std::unique_ptr OwnedStream; nix::Pid Pid; - WorkspaceVersionTy WorkspaceVersion; std::thread InputDispatcher; std::counting_semaphore<> &Smp; @@ -74,14 +75,6 @@ class Controller : public lspserver::LSPServer { } }; - enum class ServerRole { - /// Parent process of the server - Controller, - /// Child process - Evaluator, - OptionProvider, - }; - template struct ReplyRAII { lspserver::Callback R; llvm::Expected Response = @@ -119,8 +112,6 @@ class Controller : public lspserver::LSPServer { private: bool WaitWorker = false; - ServerRole Role = ServerRole::Controller; - using WorkerContainer = std::deque>; using WC = std::tuple; @@ -142,21 +133,16 @@ class Controller : public lspserver::LSPServer { lspserver::ClientCapabilities ClientCaps; - configuration::TopLevel Config; + std::mutex ConfigLock; + + configuration::TopLevel Config; // GUARDED_BY(ConfigLock) std::shared_ptr getDraft(lspserver::PathRef File) const; void addDocument(lspserver::PathRef File, llvm::StringRef Contents, llvm::StringRef Version); - void removeDocument(lspserver::PathRef File) { - DraftMgr.removeDraft(File); - updateWorkspaceVersion(); - } - - /// LSP defines file versions as numbers that increase. - /// treats them as opaque and therefore uses strings instead. - static std::string encodeVersion(std::optional LSPVersion); + void removeDocument(lspserver::PathRef File) { DraftMgr.removeDraft(File); } llvm::unique_function PublishDiagnostic; @@ -184,6 +170,31 @@ class Controller : public lspserver::LSPServer { nix::Value *OptionAttrSet; std::unique_ptr OptionIES; + // IPC Utils + + template + auto askWorkers(const WorkerContainer &Workers, std::shared_mutex &WorkerLock, + llvm::StringRef IPCMethod, const Arg &Params, + unsigned Timeout); + + template + auto askWC(llvm::StringRef IPCMethod, const Arg &Params, WC CL) + -> std::vector { + auto &[W, L, T] = CL; + return askWorkers(W, L, IPCMethod, Params, T); + } + + template + auto askWC(llvm::StringRef IPCMethod, const Arg &Params, WC CL, A... Rest) + -> std::vector { + auto Vec = askWC(IPCMethod, Params, CL); + if (Vec.empty()) + return askWC(IPCMethod, Params, Rest...); + return Vec; + } + + void syncDrafts(Proc &); + public: Controller(std::unique_ptr In, std::unique_ptr Out, int WaitWorker = 0); @@ -215,8 +226,6 @@ class Controller : public lspserver::LSPServer { //---------------------------------------------------------------------------/ // Text Document Synchronization - void updateWorkspaceVersion(); - void onDocumentDidOpen(const lspserver::DidOpenTextDocumentParams &Params); void @@ -284,33 +293,24 @@ class Controller : public lspserver::LSPServer { // Controller - void forkWorker(llvm::unique_function WorkerAction, - std::deque> &WorkerPool, size_t Size); - - void onEvalDiagnostic(const ipc::Diagnostics &); + /// Returns non-null worker process handle. + std::unique_ptr forkWorker(llvm::unique_function WorkerAction); - void onFinished(const ipc::WorkerMessage &); + std::unique_ptr selfExec(char **Argv) { + return forkWorker([Argv]() { + if (auto Name = nix::getSelfExe()) { + execv(Name->c_str(), Argv); + } else { + throw std::runtime_error("nix::getSelfExe() returned nullopt"); + } + }); + } - template - auto askWorkers(const WorkerContainer &Workers, std::shared_mutex &WorkerLock, - llvm::StringRef IPCMethod, const Arg &Params, - unsigned Timeout); + std::unique_ptr createEvalWorker(); - template - auto askWC(llvm::StringRef IPCMethod, const Arg &Params, WC CL) - -> std::vector { - auto &[W, L, T] = CL; - return askWorkers(W, L, IPCMethod, Params, T); - } + void onEvalDiagnostic(const ipc::Diagnostics &); - template - auto askWC(llvm::StringRef IPCMethod, const Arg &Params, WC CL, A... Rest) - -> std::vector { - auto Vec = askWC(IPCMethod, Params, CL); - if (Vec.empty()) - return askWC(IPCMethod, Params, Rest...); - return Vec; - } + void onFinished(const ipc::WorkerMessage &); // Worker::Nix::Option diff --git a/nixd/include/nixd/Server/EvalWorker.h b/nixd/include/nixd/Server/EvalWorker.h index e6907f79c..c4a334939 100644 --- a/nixd/include/nixd/Server/EvalWorker.h +++ b/nixd/include/nixd/Server/EvalWorker.h @@ -1,6 +1,8 @@ #pragma once +#include "nixd/Server/ConfigSerialization.h" #include "nixd/Server/EvalDraftStore.h" +#include "nixd/Server/IPCSerialization.h" #include "nixd/Support/Diagnostic.h" #include "nixd/Support/JSONSerialization.h" #include "nixd/Support/ReplyRAII.h" @@ -10,10 +12,14 @@ namespace nixd { class EvalWorker : public lspserver::LSPServer { - llvm::unique_function EvalDiagnostic; + llvm::unique_function NotifyDiagnostics; std::unique_ptr IER; + configuration::TopLevel Config; + + EvalDraftStore DraftMgr; + template void withAST( const std::string &, ReplyRAII, @@ -23,14 +29,18 @@ class EvalWorker : public lspserver::LSPServer { EvalWorker(std::unique_ptr In, std::unique_ptr Out); - void onEvalDefinition(const lspserver::TextDocumentPositionParams &, - lspserver::Callback); + void onDocumentDidOpen(const lspserver::DidOpenTextDocumentParams &Params); + + void onEval(const ipc::WorkerMessage &Params); + + void onDefinition(const lspserver::TextDocumentPositionParams &, + lspserver::Callback); - void onEvalHover(const lspserver::TextDocumentPositionParams &, - lspserver::Callback); + void onHover(const lspserver::TextDocumentPositionParams &, + lspserver::Callback); - void onEvalCompletion(const lspserver::CompletionParams &, - lspserver::Callback); + void onCompletion(const lspserver::CompletionParams &, + lspserver::Callback); }; template diff --git a/nixd/include/nixd/Server/IPC.h b/nixd/include/nixd/Server/IPC.h new file mode 100644 index 000000000..1f74bf90f --- /dev/null +++ b/nixd/include/nixd/Server/IPC.h @@ -0,0 +1,28 @@ +#pragma once + +#include "lspserver/Protocol.h" + +namespace nixd::ipc { + +using WorkspaceVersionTy = uint64_t; + +/// Messages sent by workers must tell it's version +struct WorkerMessage { + WorkspaceVersionTy WorkspaceVersion; +}; + +/// Sent by the worker process, tell us it has prepared diagnostics +/// <---- +struct Diagnostics : WorkerMessage { + std::vector Params; +}; + +struct AttrPathParams { + std::string Path; +}; + +bool fromJSON(const llvm::json::Value &, AttrPathParams &, llvm::json::Path); + +llvm::json::Value toJSON(const AttrPathParams &); + +} // namespace nixd::ipc diff --git a/nixd/include/nixd/Server/IPCSerialization.h b/nixd/include/nixd/Server/IPCSerialization.h new file mode 100644 index 000000000..acd556dc3 --- /dev/null +++ b/nixd/include/nixd/Server/IPCSerialization.h @@ -0,0 +1,17 @@ +#pragma once + +#include "IPC.h" + +#include + +#include + +namespace nixd::ipc { + +bool fromJSON(const llvm::json::Value &, WorkerMessage &, llvm::json::Path); +llvm::json::Value toJSON(const WorkerMessage &); + +bool fromJSON(const llvm::json::Value &, Diagnostics &, llvm::json::Path); +llvm::json::Value toJSON(const Diagnostics &); + +} // namespace nixd::ipc diff --git a/nixd/include/nixd/Server/Role.h b/nixd/include/nixd/Server/Role.h new file mode 100644 index 000000000..782208d37 --- /dev/null +++ b/nixd/include/nixd/Server/Role.h @@ -0,0 +1,13 @@ +#pragma once + +namespace nixd { + +enum class ServerRole { + /// Parent process of the server + Controller, + /// Child process + Evaluator, + OptionProvider, +}; + +} // namespace nixd diff --git a/nixd/include/nixd/Support/JSONSerialization.h b/nixd/include/nixd/Support/JSONSerialization.h index 2c21eca93..387e18f19 100644 --- a/nixd/include/nixd/Support/JSONSerialization.h +++ b/nixd/include/nixd/Support/JSONSerialization.h @@ -19,6 +19,13 @@ llvm::json::Value toJSON(const CompletionParams &R); llvm::json::Value toJSON(const RenameParams &); +llvm::json::Value toJSON(const TextDocumentItem &R); + +llvm::json::Value toJSON(const DidOpenTextDocumentParams &R); + +bool fromJSON(const llvm::json::Value &Params, PublishDiagnosticsParams &R, + llvm::json::Path P); + bool fromJSON(const llvm::json::Value &Params, CompletionItem &R, llvm::json::Path P); @@ -39,93 +46,3 @@ bool fromJSON(const llvm::json::Value &Params, DocumentSymbol &R, llvm::json::Path P); } // namespace lspserver - -namespace nixd { -namespace configuration { - -inline std::list toNixArgs(const std::vector &Args) { - return {Args.begin(), Args.end()}; -} -struct InstallableConfigurationItem { - std::vector args; - std::string installable; - - [[nodiscard]] bool empty() const { - return args.empty() && installable.empty(); - } - - [[nodiscard]] auto nArgs() const { return toNixArgs(args); } -}; - -struct TopLevel { - - struct Eval { - /// Nix installables that will be used for root translation unit. - InstallableConfigurationItem target; - - /// The depth you'd like to eval *after* reached "installable" target. - int depth = 0; - /// Number of workers forking - /// defaults to std::thread::hardware_concurrency - int workers = static_cast(std::thread::hardware_concurrency()); - }; - - Eval eval; - - struct Formatting { - std::string command = "nixpkgs-fmt"; - }; - Formatting formatting; - - struct Options { - bool enable = false; - InstallableConfigurationItem target; - }; - - Options options; -}; -bool fromJSON(const llvm::json::Value &Params, TopLevel::Eval &R, - llvm::json::Path P); -bool fromJSON(const llvm::json::Value &Params, TopLevel::Formatting &R, - llvm::json::Path P); -bool fromJSON(const llvm::json::Value &Params, TopLevel::Options &R, - llvm::json::Path P); -bool fromJSON(const llvm::json::Value &Params, TopLevel &R, llvm::json::Path P); -bool fromJSON(const llvm::json::Value &Params, InstallableConfigurationItem &R, - llvm::json::Path P); -} // namespace configuration - -namespace ipc { - -using WorkspaceVersionTy = uint64_t; - -/// Messages sent by workers must tell it's version -struct WorkerMessage { - WorkspaceVersionTy WorkspaceVersion; -}; - -bool fromJSON(const llvm::json::Value &, WorkerMessage &, llvm::json::Path); -llvm::json::Value toJSON(const WorkerMessage &); - -/// Sent by the worker process, tell us it has prepared diagnostics -/// <---- -struct Diagnostics : WorkerMessage { - std::vector Params; -}; - -bool fromJSON(const llvm::json::Value &, lspserver::PublishDiagnosticsParams &, - llvm::json::Path); - -bool fromJSON(const llvm::json::Value &, Diagnostics &, llvm::json::Path); -llvm::json::Value toJSON(const Diagnostics &); - -struct AttrPathParams { - std::string Path; -}; - -bool fromJSON(const llvm::json::Value &, AttrPathParams &, llvm::json::Path); - -llvm::json::Value toJSON(const AttrPathParams &); - -} // namespace ipc -} // namespace nixd diff --git a/nixd/lib/Server/ConfigSerialization.cpp b/nixd/lib/Server/ConfigSerialization.cpp new file mode 100644 index 000000000..b4ac1c346 --- /dev/null +++ b/nixd/lib/Server/ConfigSerialization.cpp @@ -0,0 +1,53 @@ +#include "nixd/Server/ConfigSerialization.h" + +#include + +namespace nixd::configuration { + +using llvm::json::ObjectMapper; +using llvm::json::Path; +using llvm::json::Value; + +bool fromJSON(const Value &Params, TopLevel::Eval &R, Path P) { + ObjectMapper O(Params, P); + return O && O.mapOptional("depth", R.depth) && + O.mapOptional("target", R.target) && + O.mapOptional("workers", R.workers); +} + +bool fromJSON(const Value &Params, TopLevel::Formatting &R, Path P) { + ObjectMapper O(Params, P); + return O && O.mapOptional("command", R.command); +} + +bool fromJSON(const Value &Params, TopLevel::Options &R, Path P) { + ObjectMapper O(Params, P); + return O && O.mapOptional("enable", R.enable) && + O.mapOptional("target", R.target); +} + +bool fromJSON(const Value &Params, TopLevel &R, Path P) { + Value X = Params; + if (Params.kind() == Value::Array) { + const auto *PA = Params.getAsArray(); + X = PA->front(); + } + ObjectMapper O(X, P); + + return O && O.mapOptional("eval", R.eval) && + O.mapOptional("formatting", R.formatting) && + O.mapOptional("options", R.options); +} + +bool fromJSON(const Value &Params, std::list &R, Path P) { + std::vector RVec{std::begin(R), std::end(R)}; + return fromJSON(Params, RVec, P); +} + +bool fromJSON(const Value &Params, InstallableConfigurationItem &R, Path P) { + ObjectMapper O(Params, P); + return O && O.mapOptional("args", R.args) && + O.mapOptional("installable", R.installable); +} + +} // namespace nixd::configuration diff --git a/nixd/lib/Server/Controller.cpp b/nixd/lib/Server/Controller.cpp index 49cb800d7..7dffb9db3 100644 --- a/nixd/lib/Server/Controller.cpp +++ b/nixd/lib/Server/Controller.cpp @@ -1,3 +1,4 @@ +#include "lspserver/DraftStore.h" #include "nixd-config.h" #include "nixd/AST/ParseAST.h" @@ -7,6 +8,7 @@ #include "nixd/Server/ASTManager.h" #include "nixd/Server/Controller.h" #include "nixd/Server/EvalDraftStore.h" +#include "nixd/Server/IPC.h" #include "nixd/Support/Diagnostic.h" #include "nixd/Support/Support.h" @@ -16,7 +18,9 @@ #include "lspserver/Protocol.h" #include "lspserver/SourceCode.h" #include "lspserver/URI.h" +#include "util.hh" +#include #include #include #include @@ -38,6 +42,7 @@ #include #include #include +#include #include #include @@ -45,11 +50,10 @@ namespace nixd { -void Controller::forkWorker(llvm::unique_function WorkerAction, - std::deque> &WorkerPool, - size_t Size) { - if (Role != ServerRole::Controller) - return; +using lspserver::DraftStore; + +std::unique_ptr +Controller::forkWorker(llvm::unique_function WorkerAction) { auto To = std::make_unique(); auto From = std::make_unique(); @@ -58,16 +62,17 @@ void Controller::forkWorker(llvm::unique_function WorkerAction, auto ForkPID = fork(); if (ForkPID == -1) { - lspserver::elog("Cannot create child worker process"); - // TODO reason? - } else if (ForkPID == 0) { + throw std::system_error( + std::make_error_code(static_cast(errno))); + } + + if (ForkPID == 0) { // Redirect stdin & stdout to our pipes, instead of LSP clients dup2(To->readSide.get(), 0); dup2(From->writeSide.get(), 1); WorkerAction(); - + abort(); } else { - auto WorkerInputDispatcher = std::thread([In = From->readSide.get(), this]() { // Start a thread that handles outputs from WorkerProc, and call the @@ -84,42 +89,49 @@ void Controller::forkWorker(llvm::unique_function WorkerAction, auto OutPort = std::make_unique(*ProcFdStream, false); - auto WorkerProc = std::unique_ptr( + return std::unique_ptr( new Proc{.ToPipe = std::move(To), .FromPipe = std::move(From), .OutPort = std::move(OutPort), .OwnedStream = std::move(ProcFdStream), .Pid = ForkPID, - .WorkspaceVersion = WorkspaceVersion, .InputDispatcher = std::move(WorkerInputDispatcher), .Smp = std::ref(FinishSmp), .WaitWorker = WaitWorker}); + } +} - WorkerPool.emplace_back(std::move(WorkerProc)); - if (WorkerPool.size() > Size && !WaitWorker) - WorkerPool.pop_front(); +void Controller::syncDrafts(Proc &WorkerProc) { + using OP = lspserver::DidOpenTextDocumentParams; + using TI = lspserver::TextDocumentItem; + auto SendIPCDocumentOpen = mkOutNotifiction( + "nixd/ipc/textDocument/didOpen", WorkerProc.OutPort.get()); + for (const auto &Path : DraftMgr.getActiveFiles()) { + if (auto Draft = DraftMgr.getDraft(Path)) { + const auto &[Content, Version] = *Draft; + SendIPCDocumentOpen(OP{TI{ + .uri = lspserver::URIForFile::canonicalize(Path, Path), + .version = EvalDraftStore::decodeVersion(Version), + .text = *Content, + }}); + } } } -void Controller::updateWorkspaceVersion() { - if (Role != ServerRole::Controller) - return; - WorkspaceVersion++; - std::lock_guard EvalGuard(EvalWorkerLock); - // The eval worker - forkWorker( - []() { - if (auto Name = nix::getSelfExe()) { - execv(Name->c_str(), - nix::stringsToCharPtrs({"--role=worker"}).data()); - } - }, - EvalWorkers, Config.eval.workers); +std::unique_ptr Controller::createEvalWorker() { + auto Args = nix::Strings{"nixd", "-role=evaluator"}; + auto EvalWorker = selfExec(nix::stringsToCharPtrs(Args).data()); + syncDrafts(*EvalWorker); + auto AskEval = mkOutNotifiction( + "nixd/ipc/eval", EvalWorker->OutPort.get()); + AskEval(ipc::WorkerMessage{WorkspaceVersion}); + return EvalWorker; } void Controller::addDocument(lspserver::PathRef File, llvm::StringRef Contents, llvm::StringRef Version) { using namespace lspserver; + WorkspaceVersion++; auto IVersion = DraftStore::decodeVersion(Version); // Since this file is updated, we first clear its diagnostic PublishDiagnosticsParams Notification; @@ -130,13 +142,20 @@ void Controller::addDocument(lspserver::PathRef File, llvm::StringRef Contents, DraftMgr.addDraft(File, Version, Contents); ASTMgr.schedParse(Contents.str(), File.str(), IVersion.value_or(0)); - updateWorkspaceVersion(); + try { + auto EvalWorker = createEvalWorker(); + std::lock_guard Guard1(EvalWorkerLock); + std::lock_guard Guard2(ConfigLock); + EvalWorkers.emplace_back(std::move(EvalWorker)); + if (EvalWorkers.size() > Config.eval.workers) + EvalWorkers.pop_front(); + } catch (...) { + } } void Controller::updateConfig(configuration::TopLevel &&NewConfig) { Config = std::move(NewConfig); - forkOptionWorker(); - updateWorkspaceVersion(); + // forkOptionWorker(); } void Controller::fetchConfig() { @@ -184,10 +203,6 @@ void Controller::readJSONConfig(lspserver::PathRef File) noexcept { } } -std::string Controller::encodeVersion(std::optional LSPVersion) { - return LSPVersion ? llvm::to_string(*LSPVersion) : ""; -} - std::shared_ptr Controller::getDraft(lspserver::PathRef File) const { auto Draft = DraftMgr.getDraft(File); @@ -246,8 +261,8 @@ Controller::Controller(std::unique_ptr In, Registry.addNotification("nixd/ipc/diagnostic", this, &Controller::onEvalDiagnostic); - Registry.addMethod("nixd/ipc/option/textDocument/declaration", this, - &Controller::onOptionDeclaration); + // Registry.addMethod("nixd/ipc/option/textDocument/declaration", this, + // &Controller::onOptionDeclaration); Registry.addNotification("nixd/ipc/finished", this, &Controller::onFinished); @@ -296,7 +311,8 @@ void Controller::onDocumentDidOpen( const std::string &Contents = Params.textDocument.text; - addDocument(File, Contents, encodeVersion(Params.textDocument.version)); + addDocument(File, Contents, + DraftStore::encodeVersion(Params.textDocument.version)); } void Controller::onDocumentDidChange( @@ -319,7 +335,8 @@ void Controller::onDocumentDidChange( return; } } - addDocument(File, NewCode, encodeVersion(Params.textDocument.version)); + addDocument(File, NewCode, + DraftStore::encodeVersion(Params.textDocument.version)); } void Controller::onDocumentDidClose( @@ -627,7 +644,9 @@ void Controller::clearDiagnostic(const lspserver::URIForFile &FileUri) { void Controller::onEvalDiagnostic(const ipc::Diagnostics &Diag) { lspserver::log("received diagnostic from worker: {0}", Diag.WorkspaceVersion); - + auto Defer = std::shared_ptr(nullptr, [this, Diag](...) { + onFinished(ipc::WorkerMessage{Diag.WorkspaceVersion}); + }); { std::lock_guard Guard(DiagStatusLock); if (DiagStatus.WorkspaceVersion > Diag.WorkspaceVersion) { diff --git a/nixd/lib/Server/Eval.cpp b/nixd/lib/Server/Eval.cpp deleted file mode 100644 index d7ac628e8..000000000 --- a/nixd/lib/Server/Eval.cpp +++ /dev/null @@ -1,86 +0,0 @@ -#include "nixd/AST/EvalAST.h" -#include "nixd/Expr/Expr.h" -#include "nixd/Nix/Option.h" -#include "nixd/Nix/Value.h" -#include "nixd/Server/Server.h" -#include "nixd/Support/Diagnostic.h" -#include "nixd/Support/Position.h" - -#include "lspserver/Connection.h" -#include "lspserver/Logger.h" -#include "lspserver/Protocol.h" -#include "lspserver/SourceCode.h" - -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace nixd { - -void Controller::switchToEvaluator() { - // initWorker(); - Role = ServerRole::Evaluator; - EvalDiagnostic = mkOutNotifiction("nixd/ipc/diagnostic"); - - // Registry.addMethod("nixd/ipc/textDocument/completion", this, - // &Server::onEvalCompletion); - - // Registry.addMethod("nixd/ipc/textDocument/definition", this, - // &Server::onEvalDefinition); - - evalInstallable(); - mkOutNotifiction("nixd/ipc/finished")( - ipc::WorkerMessage{WorkspaceVersion}); -} - -void Controller::evalInstallable() { - assert(Role != ServerRole::Controller && "must be called in child workers."); - auto Session = std::make_unique(); - - auto I = Config.eval.target; - auto Depth = Config.eval.depth; - - if (!I.empty()) - Session->parseArgs(I.nArgs()); - - auto ILR = DraftMgr.injectFiles(Session->getState()); - - ipc::Diagnostics Diagnostics; - std::map DiagMap; - for (const auto &ErrInfo : ILR.InjectionErrors) { - insertDiagnostic(*ErrInfo.Err, DiagMap, - decltype(DraftMgr)::decodeVersion(ErrInfo.Version)); - } - Diagnostics.WorkspaceVersion = WorkspaceVersion; - EvalDiagnostic(Diagnostics); - try { - if (!I.empty()) { - Session->eval(I.installable, Depth); - lspserver::log("evaluation done on worspace version: {0}", - WorkspaceVersion); - } - } catch (nix::BaseError &BE) { - insertDiagnostic(BE, DiagMap); - } catch (...) { - } - std::transform(DiagMap.begin(), DiagMap.end(), - std::back_inserter(Diagnostics.Params), - [](const auto &V) { return V.second; }); - EvalDiagnostic(Diagnostics); - IER = std::make_unique(std::move(ILR.Forest), - std::move(Session)); -} - -} // namespace nixd diff --git a/nixd/lib/Server/EvalWorker.cpp b/nixd/lib/Server/EvalWorker.cpp index 8441980cf..0aefb35e8 100644 --- a/nixd/lib/Server/EvalWorker.cpp +++ b/nixd/lib/Server/EvalWorker.cpp @@ -1,5 +1,8 @@ #include "nixd/Server/EvalWorker.h" +#include "nixd/Nix/Init.h" #include "nixd/Nix/Value.h" +#include "nixd/Server/EvalDraftStore.h" +#include "nixd/Server/IPC.h" #include "nixd/Support/CompletionHelper.h" #include "lspserver/LSPServer.h" @@ -10,17 +13,69 @@ EvalWorker::EvalWorker(std::unique_ptr In, std::unique_ptr Out) : lspserver::LSPServer(std::move(In), std::move(Out)) { - EvalDiagnostic = mkOutNotifiction("nixd/ipc/diagnostic"); + initEval(); - Registry.addMethod("nixd/ipc/textDocument/hover", this, - &EvalWorker::onEvalHover); + NotifyDiagnostics = mkOutNotifiction("nixd/ipc/diagnostic"); + + Registry.addNotification("nixd/ipc/eval", this, &EvalWorker::onEval); + Registry.addNotification("nixd/ipc/textDocument/didOpen", this, + &EvalWorker::onDocumentDidOpen); + Registry.addMethod("nixd/ipc/textDocument/hover", this, &EvalWorker::onHover); Registry.addMethod("nixd/ipc/textDocument/completion", this, - &EvalWorker::onEvalCompletion); + &EvalWorker::onCompletion); Registry.addMethod("nixd/ipc/textDocument/definition", this, - &EvalWorker::onEvalDefinition); + &EvalWorker::onDefinition); +} + +void EvalWorker::onDocumentDidOpen( + const lspserver::DidOpenTextDocumentParams &Params) { + lspserver::PathRef File = Params.textDocument.uri.file(); + + const std::string &Contents = Params.textDocument.text; + + auto Version = EvalDraftStore::encodeVersion(Params.textDocument.version); + + DraftMgr.addDraft(File, Version, Contents); +} + +void EvalWorker::onEval(const ipc::WorkerMessage &Params) { + auto Session = std::make_unique(); + + auto I = Config.eval.target; + auto Depth = Config.eval.depth; + + if (!I.empty()) + Session->parseArgs(I.nArgs()); + + auto ILR = DraftMgr.injectFiles(Session->getState()); + + std::map DiagMap; + for (const auto &ErrInfo : ILR.InjectionErrors) { + insertDiagnostic(*ErrInfo.Err, DiagMap, + lspserver::DraftStore::decodeVersion(ErrInfo.Version)); + } + try { + if (!I.empty()) { + Session->eval(I.installable, Depth); + lspserver::log("finished evaluation"); + } + } catch (nix::BaseError &BE) { + insertDiagnostic(BE, DiagMap); + } catch (...) { + } + + ipc::Diagnostics Diagnostics; + std::transform(DiagMap.begin(), DiagMap.end(), + std::back_inserter(Diagnostics.Params), + [](const auto &V) { return V.second; }); + + Diagnostics.WorkspaceVersion = Params.WorkspaceVersion; + NotifyDiagnostics(Diagnostics); + IER = std::make_unique(std::move(ILR.Forest), + std::move(Session)); } -void EvalWorker::onEvalDefinition( +void EvalWorker::onDefinition( const lspserver::TextDocumentPositionParams &Params, lspserver::Callback Reply) { using namespace lspserver; @@ -71,9 +126,8 @@ void EvalWorker::onEvalDefinition( }); } -void EvalWorker::onEvalHover( - const lspserver::TextDocumentPositionParams &Params, - lspserver::Callback Reply) { +void EvalWorker::onHover(const lspserver::TextDocumentPositionParams &Params, + lspserver::Callback Reply) { using namespace lspserver; withAST( Params.textDocument.uri.file().str(), ReplyRAII(std::move(Reply)), @@ -101,9 +155,8 @@ void EvalWorker::onEvalHover( }); } -void EvalWorker::onEvalCompletion( - const lspserver::CompletionParams &Params, - lspserver::Callback Reply) { +void EvalWorker::onCompletion(const lspserver::CompletionParams &Params, + lspserver::Callback Reply) { using namespace lspserver; auto Action = [Params, this](const nix::ref &AST, ReplyRAII &&RR) { diff --git a/nixd/lib/Server/IPCSerialization.cpp b/nixd/lib/Server/IPCSerialization.cpp new file mode 100644 index 000000000..c6fc86781 --- /dev/null +++ b/nixd/lib/Server/IPCSerialization.cpp @@ -0,0 +1,36 @@ +#include "nixd/Server/IPCSerialization.h" +#include "nixd/Support/JSONSerialization.h" + +namespace nixd::ipc { + +using namespace llvm::json; + +bool fromJSON(const Value &Params, WorkerMessage &R, Path P) { + ObjectMapper O(Params, P); + return O && O.map("WorkspaceVersion", R.WorkspaceVersion); +} + +Value toJSON(const WorkerMessage &R) { + return Object{{"WorkspaceVersion", R.WorkspaceVersion}}; +} + +bool fromJSON(const Value &Params, Diagnostics &R, Path P) { + WorkerMessage &Base = R; + ObjectMapper O(Params, P); + return fromJSON(Params, Base, P) && O.map("Params", R.Params); +} + +Value toJSON(const Diagnostics &R) { + Value Base = toJSON(WorkerMessage(R)); + Base.getAsObject()->insert({"Params", R.Params}); + return Base; +} + +bool fromJSON(const Value &Params, AttrPathParams &R, Path P) { + ObjectMapper O(Params, P); + return O && O.map("Path", R.Path); +} + +Value toJSON(const AttrPathParams &R) { return Object{{"Path", R.Path}}; } + +} // namespace nixd::ipc diff --git a/nixd/lib/Server/meson.build b/nixd/lib/Server/meson.build index ab233306e..9a005083d 100644 --- a/nixd/lib/Server/meson.build +++ b/nixd/lib/Server/meson.build @@ -6,11 +6,12 @@ libnixdServerDeps = [ nixd_lsp_server ] libnixdServer = library('nixdServer' , 'ASTManager.cpp' +, 'ConfigSerialization.cpp' , 'Controller.cpp' -# , 'Eval.cpp' , 'EvalDraftStore.cpp' , 'EvalWorker.cpp' -, 'Option.cpp' +, 'IPCSerialization.cpp' +# , 'Option.cpp' , include_directories: nixd_inc , dependencies: libnixdServerDeps , install: true diff --git a/nixd/lib/Support/JSONSerialization.cpp b/nixd/lib/Support/JSONSerialization.cpp index d65ef1b77..10746885b 100644 --- a/nixd/lib/Support/JSONSerialization.cpp +++ b/nixd/lib/Support/JSONSerialization.cpp @@ -1,98 +1,26 @@ #include "nixd/Support/JSONSerialization.h" -#include "lspserver/Logger.h" +#include "lspserver/Protocol.h" #include -namespace nixd { - -using namespace llvm::json; - -namespace configuration { - -bool fromJSON(const Value &Params, TopLevel::Eval &R, Path P) { - ObjectMapper O(Params, P); - return O && O.mapOptional("depth", R.depth) && - O.mapOptional("target", R.target) && - O.mapOptional("workers", R.workers); -} - -bool fromJSON(const Value &Params, TopLevel::Formatting &R, Path P) { - ObjectMapper O(Params, P); - return O && O.mapOptional("command", R.command); -} - -bool fromJSON(const Value &Params, TopLevel::Options &R, Path P) { - ObjectMapper O(Params, P); - return O && O.mapOptional("enable", R.enable) && - O.mapOptional("target", R.target); -} - -bool fromJSON(const Value &Params, TopLevel &R, Path P) { - Value X = Params; - if (Params.kind() == Value::Array) { - const auto *PA = Params.getAsArray(); - X = PA->front(); - } - ObjectMapper O(X, P); - - return O && O.mapOptional("eval", R.eval) && - O.mapOptional("formatting", R.formatting) && - O.mapOptional("options", R.options); -} - -bool fromJSON(const Value &Params, std::list &R, Path P) { - std::vector RVec{std::begin(R), std::end(R)}; - return fromJSON(Params, RVec, P); -} - -bool fromJSON(const Value &Params, InstallableConfigurationItem &R, Path P) { - ObjectMapper O(Params, P); - return O && O.mapOptional("args", R.args) && - O.mapOptional("installable", R.installable); -} - -} // namespace configuration - -namespace ipc { -bool fromJSON(const Value &Params, WorkerMessage &R, Path P) { - ObjectMapper O(Params, P); - return O && O.map("WorkspaceVersion", R.WorkspaceVersion); -} - -Value toJSON(const WorkerMessage &R) { - return Object{{"WorkspaceVersion", R.WorkspaceVersion}}; -} - -bool fromJSON(const Value &Params, Diagnostics &R, Path P) { - WorkerMessage &Base = R; - ObjectMapper O(Params, P); - return fromJSON(Params, Base, P) && O.map("Params", R.Params); -} - -Value toJSON(const Diagnostics &R) { - Value Base = toJSON(WorkerMessage(R)); - Base.getAsObject()->insert({"Params", R.Params}); - return Base; -} - -bool fromJSON(const Value &Params, AttrPathParams &R, Path P) { - ObjectMapper O(Params, P); - return O && O.map("Path", R.Path); -} - -Value toJSON(const AttrPathParams &R) { return Object{{"Path", R.Path}}; } - -} // namespace ipc - -} // namespace nixd - namespace lspserver { using llvm::json::Object; using llvm::json::ObjectMapper; using llvm::json::Value; +Value toJSON(const TextDocumentItem &R) { + return Object{{"text", R.text}, + {"version", R.version}, + {"uri", R.uri}, + {"languageId", R.languageId}}; +} + +Value toJSON(const DidOpenTextDocumentParams &R) { + return Object{{"textDocument", R.textDocument}}; +} + bool fromJSON(const Value &Params, PublishDiagnosticsParams &R, llvm::json::Path P) { ObjectMapper O(Params, P); diff --git a/nixd/tools/nixd/nixd.cpp b/nixd/tools/nixd/nixd.cpp index 819d1faad..245ff9d6d 100644 --- a/nixd/tools/nixd/nixd.cpp +++ b/nixd/tools/nixd/nixd.cpp @@ -2,6 +2,7 @@ #include "nixd/Server/Controller.h" #include "nixd/Server/EvalWorker.h" +#include "nixd/Server/Role.h" #include "lspserver/Connection.h" #include "lspserver/LSPServer.h" @@ -107,7 +108,7 @@ opt WaitWorker{"wait-worker", "any timeout logic"), init(false), cat(Debug)}; -using NSS = nixd::Controller::ServerRole; +using NSS = nixd::ServerRole; opt Role{"role", desc("The role of this process, worker, controller, ..."), values(clEnumValN(NSS::Controller, "controller", "Controller"), @@ -142,14 +143,14 @@ int main(int argc, char *argv[]) { lspserver::log("nixd {0} started", NIXD_VERSION); #endif switch (static_cast(Role)) { - case nixd::Controller::ServerRole::Controller: { + case NSS::Controller: { nixd::Controller Controller{ std::make_unique(STDIN_FILENO, InputStyle), std::make_unique(PrettyPrint), WaitWorker}; Controller.run(); break; } - case nixd::Controller::ServerRole::Evaluator: { + case NSS::Evaluator: { nixd::EvalWorker Worker{ std::make_unique( STDIN_FILENO, lspserver::JSONStreamStyle::Standard), @@ -157,7 +158,7 @@ int main(int argc, char *argv[]) { Worker.run(); break; } - case nixd::Controller::ServerRole::OptionProvider: + case NSS::OptionProvider: break; } return 0; From 9d4576cca5f406fb1486281bedb572520e73137b Mon Sep 17 00:00:00 2001 From: Yingchi Long Date: Thu, 27 Jul 2023 18:53:21 +0800 Subject: [PATCH 3/7] nixd/Server: add serialization methods to configuration types --- nixd/include/nixd/Server/ConfigSerialization.h | 8 ++++++++ nixd/lib/Server/ConfigSerialization.cpp | 18 ++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/nixd/include/nixd/Server/ConfigSerialization.h b/nixd/include/nixd/Server/ConfigSerialization.h index 148856857..c49db0638 100644 --- a/nixd/include/nixd/Server/ConfigSerialization.h +++ b/nixd/include/nixd/Server/ConfigSerialization.h @@ -6,6 +6,14 @@ namespace nixd::configuration { +llvm::json::Value toJSON(const TopLevel::Eval &R); + +llvm::json::Value toJSON(const TopLevel::Formatting &R); + +llvm::json::Value toJSON(const TopLevel::Options &R); + +llvm::json::Value toJSON(const InstallableConfigurationItem &R); + bool fromJSON(const llvm::json::Value &Params, TopLevel::Eval &R, llvm::json::Path P); diff --git a/nixd/lib/Server/ConfigSerialization.cpp b/nixd/lib/Server/ConfigSerialization.cpp index b4ac1c346..46514cb91 100644 --- a/nixd/lib/Server/ConfigSerialization.cpp +++ b/nixd/lib/Server/ConfigSerialization.cpp @@ -4,10 +4,28 @@ namespace nixd::configuration { +using llvm::json::Object; using llvm::json::ObjectMapper; using llvm::json::Path; using llvm::json::Value; +Value toJSON(const TopLevel::Eval &R) { + return Object{ + {"depth", R.depth}, {"target", R.target}, {"workers", R.workers}}; +} + +Value toJSON(const TopLevel::Formatting &R) { + return Object{{"command", R.command}}; +} + +Value toJSON(const TopLevel::Options &R) { + return Object{{"enable", R.enable}, {"target", R.target}}; +} + +llvm::json::Value toJSON(const InstallableConfigurationItem &R) { + return Object{{"installable", R.installable}, {"args", R.args}}; +} + bool fromJSON(const Value &Params, TopLevel::Eval &R, Path P) { ObjectMapper O(Params, P); return O && O.mapOptional("depth", R.depth) && From 49a52509022e8c6a5afd448447d54191208fe473 Mon Sep 17 00:00:00 2001 From: Yingchi Long Date: Thu, 27 Jul 2023 20:55:18 +0800 Subject: [PATCH 4/7] nixd/Server: split the option worker --- nixd/include/nixd/Server/Controller.h | 26 ++++----- nixd/include/nixd/Server/OptionWorker.h | 34 +++++++++++ nixd/lib/Server/Controller.cpp | 57 +++++++++++++++---- .../Server/{Option.cpp => OptionWorker.cpp} | 51 ++++++++--------- nixd/lib/Server/meson.build | 2 +- nixd/tools/nixd/nixd.cpp | 6 ++ 6 files changed, 124 insertions(+), 52 deletions(-) create mode 100644 nixd/include/nixd/Server/OptionWorker.h rename nixd/lib/Server/{Option.cpp => OptionWorker.cpp} (79%) diff --git a/nixd/include/nixd/Server/Controller.h b/nixd/include/nixd/Server/Controller.h index 9f13e2829..06410ae01 100644 --- a/nixd/include/nixd/Server/Controller.h +++ b/nixd/include/nixd/Server/Controller.h @@ -112,19 +112,20 @@ class Controller : public lspserver::LSPServer { private: bool WaitWorker = false; + using WorkerContainerLock = std::shared_mutex; using WorkerContainer = std::deque>; using WC = std::tuple; WorkspaceVersionTy WorkspaceVersion = 1; - std::shared_mutex EvalWorkerLock; + WorkerContainerLock EvalWorkerLock; WorkerContainer EvalWorkers; // GUARDED_BY(EvalWorkerLock) // Used for lit tests, ensure that workers have finished their job. std::counting_semaphore<> FinishSmp = std::counting_semaphore(0); - std::shared_mutex OptionWorkerLock; + WorkerContainerLock OptionWorkerLock; WorkerContainer OptionWorkers; // GUARDED_BY(OptionWorkerLock) EvalDraftStore DraftMgr; @@ -159,17 +160,6 @@ class Controller : public lspserver::LSPServer { boost::asio::thread_pool Pool; - //---------------------------------------------------------------------------/ - // Worker members - - // Worker::Option - - /// The AttrSet having options, we use this for any nixpkgs options. - /// nixpkgs basically defined options under "options" attrpath - /// we can use this for completion (to support ALL option system) - nix::Value *OptionAttrSet; - std::unique_ptr OptionIES; - // IPC Utils template @@ -308,6 +298,16 @@ class Controller : public lspserver::LSPServer { std::unique_ptr createEvalWorker(); + std::unique_ptr createOptionWorker(); + + bool createEnqueueEvalWorker(); + + bool createEnqueueOptionWorker(); + + static bool enqueueWorker(WorkerContainerLock &Lock, + WorkerContainer &Container, + std::unique_ptr Worker, size_t Size); + void onEvalDiagnostic(const ipc::Diagnostics &); void onFinished(const ipc::WorkerMessage &); diff --git a/nixd/include/nixd/Server/OptionWorker.h b/nixd/include/nixd/Server/OptionWorker.h new file mode 100644 index 000000000..a7e36f53d --- /dev/null +++ b/nixd/include/nixd/Server/OptionWorker.h @@ -0,0 +1,34 @@ +#pragma once + +#include "nixd/Server/Config.h" +#include "nixd/Server/EvalDraftStore.h" +#include "nixd/Server/IPC.h" + +#include "lspserver/LSPServer.h" + +#include + +namespace nixd { + +class OptionWorker : public lspserver::LSPServer { +private: + /// The AttrSet having options, we use this for any nixpkgs options. + /// nixpkgs basically defined options under "options" attrpath + /// we can use this for completion (to support ALL option system) + nix::Value *OptionAttrSet; + std::unique_ptr OptionIES; + +public: + OptionWorker(std::unique_ptr In, + std::unique_ptr Out); + + void onEvalOptionSet(const configuration::TopLevel::Options &Config); + + void onDeclaration(const ipc::AttrPathParams &, + lspserver::Callback); + + void onCompletion(const ipc::AttrPathParams &, + lspserver::Callback); +}; + +} // namespace nixd diff --git a/nixd/lib/Server/Controller.cpp b/nixd/lib/Server/Controller.cpp index 7dffb9db3..6269f3250 100644 --- a/nixd/lib/Server/Controller.cpp +++ b/nixd/lib/Server/Controller.cpp @@ -6,6 +6,7 @@ #include "nixd/Parser/Parser.h" #include "nixd/Parser/Require.h" #include "nixd/Server/ASTManager.h" +#include "nixd/Server/ConfigSerialization.h" #include "nixd/Server/Controller.h" #include "nixd/Server/EvalDraftStore.h" #include "nixd/Server/IPC.h" @@ -128,6 +129,44 @@ std::unique_ptr Controller::createEvalWorker() { return EvalWorker; } +std::unique_ptr Controller::createOptionWorker() { + auto Args = nix::Strings{"nixd", "-role=option"}; + auto Worker = selfExec(nix::stringsToCharPtrs(Args).data()); + auto AskEval = mkOutNotifiction( + "nixd/ipc/evalOptionSet", Worker->OutPort.get()); + { + std::lock_guard _(ConfigLock); + AskEval(Config.options); + } + return Worker; +} + +bool Controller::createEnqueueEvalWorker() { + size_t Size; + { + std::lock_guard _(ConfigLock); + Size = Config.eval.workers; + } + return enqueueWorker(EvalWorkerLock, EvalWorkers, createEvalWorker(), Size); +} + +bool Controller::createEnqueueOptionWorker() { + return enqueueWorker(OptionWorkerLock, OptionWorkers, createOptionWorker(), + 1); +} + +bool Controller::enqueueWorker(WorkerContainerLock &Lock, + WorkerContainer &Container, + std::unique_ptr Worker, size_t Size) { + std::lock_guard _(Lock); + Container.emplace_back(std::move(Worker)); + if (Container.size() > Size) { + Container.pop_front(); + return true; + } + return false; +} + void Controller::addDocument(lspserver::PathRef File, llvm::StringRef Contents, llvm::StringRef Version) { using namespace lspserver; @@ -142,20 +181,16 @@ void Controller::addDocument(lspserver::PathRef File, llvm::StringRef Contents, DraftMgr.addDraft(File, Version, Contents); ASTMgr.schedParse(Contents.str(), File.str(), IVersion.value_or(0)); - try { - auto EvalWorker = createEvalWorker(); - std::lock_guard Guard1(EvalWorkerLock); - std::lock_guard Guard2(ConfigLock); - EvalWorkers.emplace_back(std::move(EvalWorker)); - if (EvalWorkers.size() > Config.eval.workers) - EvalWorkers.pop_front(); - } catch (...) { - } + createEnqueueEvalWorker(); } void Controller::updateConfig(configuration::TopLevel &&NewConfig) { - Config = std::move(NewConfig); - // forkOptionWorker(); + { + std::lock_guard _(ConfigLock); + Config = std::move(NewConfig); + } + createEnqueueEvalWorker(); + createEnqueueOptionWorker(); } void Controller::fetchConfig() { diff --git a/nixd/lib/Server/Option.cpp b/nixd/lib/Server/OptionWorker.cpp similarity index 79% rename from nixd/lib/Server/Option.cpp rename to nixd/lib/Server/OptionWorker.cpp index 562c2bf1c..617a70fc1 100644 --- a/nixd/lib/Server/Option.cpp +++ b/nixd/lib/Server/OptionWorker.cpp @@ -1,32 +1,32 @@ -#include "nixd/Nix/Option.h" +#include "nixd/Server/OptionWorker.h" #include "nixd/Nix/Init.h" +#include "nixd/Nix/Option.h" #include "nixd/Nix/Value.h" -#include "nixd/Server/Controller.h" +#include "nixd/Server/ConfigSerialization.h" #include "nixd/Support/Diagnostic.h" +#include "nixd/Support/ReplyRAII.h" #include namespace nixd { -void Controller::forkOptionWorker() { - std::lock_guard _(OptionWorkerLock); - forkWorker( - [this]() { - switchToOptionProvider(); - Registry.addMethod("nixd/ipc/textDocument/completion/options", this, - &Controller::onOptionCompletion); - for (auto &W : OptionWorkers) { - W->Pid.release(); - } - }, - OptionWorkers, 1); +OptionWorker::OptionWorker(std::unique_ptr In, + std::unique_ptr Out) + : lspserver::LSPServer(std::move(In), std::move(Out)) { + + initEval(); + + Registry.addNotification("nixd/ipc/evalOptionSet", this, + &OptionWorker::onEvalOptionSet); + Registry.addMethod("nixd/ipc/textDocument/completion/options", this, + &OptionWorker::onCompletion); + Registry.addMethod("nixd/ipc/option/textDocument/declaration", this, + &OptionWorker::onDeclaration); } -void Controller::onOptionDeclaration( +void OptionWorker::onDeclaration( 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) @@ -78,19 +78,17 @@ void Controller::onOptionDeclaration( } } -void Controller::switchToOptionProvider() { - initEval(); - Role = ServerRole::OptionProvider; - - if (!Config.options.enable) +void OptionWorker::onEvalOptionSet( + const configuration::TopLevel::Options &Config) { + if (!Config.enable) return; - if (Config.options.target.empty()) { + if (Config.target.empty()) { lspserver::elog( "enabled options completion, but the target set is unspecified!"); return; } try { - auto I = Config.options.target; + auto I = Config.target; auto SessionOption = std::make_unique(); SessionOption->parseArgs(I.nArgs()); OptionAttrSet = SessionOption->eval(I.installable); @@ -104,9 +102,8 @@ void Controller::switchToOptionProvider() { } } -void Controller::onOptionCompletion( - const ipc::AttrPathParams &Params, - lspserver::Callback Reply) { +void OptionWorker::onCompletion(const ipc::AttrPathParams &Params, + lspserver::Callback Reply) { using namespace lspserver; using namespace nix::nixd; ReplyRAII RR(std::move(Reply)); diff --git a/nixd/lib/Server/meson.build b/nixd/lib/Server/meson.build index 9a005083d..930e57205 100644 --- a/nixd/lib/Server/meson.build +++ b/nixd/lib/Server/meson.build @@ -11,7 +11,7 @@ libnixdServer = library('nixdServer' , 'EvalDraftStore.cpp' , 'EvalWorker.cpp' , 'IPCSerialization.cpp' -# , 'Option.cpp' +, 'OptionWorker.cpp' , include_directories: nixd_inc , dependencies: libnixdServerDeps , install: true diff --git a/nixd/tools/nixd/nixd.cpp b/nixd/tools/nixd/nixd.cpp index 245ff9d6d..4c356ad4e 100644 --- a/nixd/tools/nixd/nixd.cpp +++ b/nixd/tools/nixd/nixd.cpp @@ -2,6 +2,7 @@ #include "nixd/Server/Controller.h" #include "nixd/Server/EvalWorker.h" +#include "nixd/Server/OptionWorker.h" #include "nixd/Server/Role.h" #include "lspserver/Connection.h" @@ -159,6 +160,11 @@ int main(int argc, char *argv[]) { break; } case NSS::OptionProvider: + nixd::OptionWorker Worker{ + std::make_unique( + STDIN_FILENO, lspserver::JSONStreamStyle::Standard), + std::make_unique(/*PrettyPrint=*/false)}; + Worker.run(); break; } return 0; From 0e9063f058e9a7fa8015484dbdb4c893bc2014a2 Mon Sep 17 00:00:00 2001 From: Yingchi Long Date: Thu, 27 Jul 2023 21:21:17 +0800 Subject: [PATCH 5/7] nixd/Server: sync config with the eval worker --- nixd/include/nixd/Server/EvalWorker.h | 4 +--- nixd/include/nixd/Server/IPC.h | 8 +++++--- nixd/include/nixd/Server/IPCSerialization.h | 8 ++++++++ nixd/lib/Server/Controller.cpp | 11 +++++++---- nixd/lib/Server/EvalWorker.cpp | 6 +++--- nixd/lib/Server/IPCSerialization.cpp | 12 ++++++++++++ nixd/lib/Server/OptionWorker.cpp | 1 + 7 files changed, 37 insertions(+), 13 deletions(-) diff --git a/nixd/include/nixd/Server/EvalWorker.h b/nixd/include/nixd/Server/EvalWorker.h index c4a334939..b0aa78784 100644 --- a/nixd/include/nixd/Server/EvalWorker.h +++ b/nixd/include/nixd/Server/EvalWorker.h @@ -16,8 +16,6 @@ class EvalWorker : public lspserver::LSPServer { std::unique_ptr IER; - configuration::TopLevel Config; - EvalDraftStore DraftMgr; template @@ -31,7 +29,7 @@ class EvalWorker : public lspserver::LSPServer { void onDocumentDidOpen(const lspserver::DidOpenTextDocumentParams &Params); - void onEval(const ipc::WorkerMessage &Params); + void onEval(const ipc::EvalParams &Params); void onDefinition(const lspserver::TextDocumentPositionParams &, lspserver::Callback); diff --git a/nixd/include/nixd/Server/IPC.h b/nixd/include/nixd/Server/IPC.h index 1f74bf90f..3fe2a3867 100644 --- a/nixd/include/nixd/Server/IPC.h +++ b/nixd/include/nixd/Server/IPC.h @@ -1,6 +1,7 @@ #pragma once #include "lspserver/Protocol.h" +#include "nixd/Server/Config.h" namespace nixd::ipc { @@ -21,8 +22,9 @@ struct AttrPathParams { std::string Path; }; -bool fromJSON(const llvm::json::Value &, AttrPathParams &, llvm::json::Path); - -llvm::json::Value toJSON(const AttrPathParams &); +struct EvalParams { + WorkspaceVersionTy WorkspaceVersion; + configuration::TopLevel::Eval Eval; +}; } // namespace nixd::ipc diff --git a/nixd/include/nixd/Server/IPCSerialization.h b/nixd/include/nixd/Server/IPCSerialization.h index acd556dc3..a874144ea 100644 --- a/nixd/include/nixd/Server/IPCSerialization.h +++ b/nixd/include/nixd/Server/IPCSerialization.h @@ -8,10 +8,18 @@ namespace nixd::ipc { +llvm::json::Value toJSON(const AttrPathParams &); + +llvm::json::Value toJSON(const EvalParams &); + bool fromJSON(const llvm::json::Value &, WorkerMessage &, llvm::json::Path); llvm::json::Value toJSON(const WorkerMessage &); bool fromJSON(const llvm::json::Value &, Diagnostics &, llvm::json::Path); llvm::json::Value toJSON(const Diagnostics &); +bool fromJSON(const llvm::json::Value &, AttrPathParams &, llvm::json::Path); + +bool fromJSON(const llvm::json::Value &, EvalParams &, llvm::json::Path); + } // namespace nixd::ipc diff --git a/nixd/lib/Server/Controller.cpp b/nixd/lib/Server/Controller.cpp index 6269f3250..ee244cbee 100644 --- a/nixd/lib/Server/Controller.cpp +++ b/nixd/lib/Server/Controller.cpp @@ -9,7 +9,7 @@ #include "nixd/Server/ConfigSerialization.h" #include "nixd/Server/Controller.h" #include "nixd/Server/EvalDraftStore.h" -#include "nixd/Server/IPC.h" +#include "nixd/Server/IPCSerialization.h" #include "nixd/Support/Diagnostic.h" #include "nixd/Support/Support.h" @@ -123,9 +123,12 @@ std::unique_ptr Controller::createEvalWorker() { auto Args = nix::Strings{"nixd", "-role=evaluator"}; auto EvalWorker = selfExec(nix::stringsToCharPtrs(Args).data()); syncDrafts(*EvalWorker); - auto AskEval = mkOutNotifiction( - "nixd/ipc/eval", EvalWorker->OutPort.get()); - AskEval(ipc::WorkerMessage{WorkspaceVersion}); + auto AskEval = mkOutNotifiction("nixd/ipc/eval", + EvalWorker->OutPort.get()); + { + std::lock_guard _(ConfigLock); + AskEval(ipc::EvalParams{WorkspaceVersion, Config.eval}); + } return EvalWorker; } diff --git a/nixd/lib/Server/EvalWorker.cpp b/nixd/lib/Server/EvalWorker.cpp index 0aefb35e8..04e82ab85 100644 --- a/nixd/lib/Server/EvalWorker.cpp +++ b/nixd/lib/Server/EvalWorker.cpp @@ -38,11 +38,11 @@ void EvalWorker::onDocumentDidOpen( DraftMgr.addDraft(File, Version, Contents); } -void EvalWorker::onEval(const ipc::WorkerMessage &Params) { +void EvalWorker::onEval(const ipc::EvalParams &Params) { auto Session = std::make_unique(); - auto I = Config.eval.target; - auto Depth = Config.eval.depth; + auto I = Params.Eval.target; + auto Depth = Params.Eval.depth; if (!I.empty()) Session->parseArgs(I.nArgs()); diff --git a/nixd/lib/Server/IPCSerialization.cpp b/nixd/lib/Server/IPCSerialization.cpp index c6fc86781..af2e58c96 100644 --- a/nixd/lib/Server/IPCSerialization.cpp +++ b/nixd/lib/Server/IPCSerialization.cpp @@ -1,4 +1,6 @@ #include "nixd/Server/IPCSerialization.h" +#include "nixd/Server/ConfigSerialization.h" +#include "nixd/Server/IPC.h" #include "nixd/Support/JSONSerialization.h" namespace nixd::ipc { @@ -33,4 +35,14 @@ bool fromJSON(const Value &Params, AttrPathParams &R, Path P) { Value toJSON(const AttrPathParams &R) { return Object{{"Path", R.Path}}; } +Value toJSON(const EvalParams &R) { + return Object{{"Eval", R.Eval}, {"WorkspaceVersion", R.WorkspaceVersion}}; +} + +bool fromJSON(const Value &Params, EvalParams &R, Path P) { + ObjectMapper O(Params, P); + return O && O.map("Eval", R.Eval) && + O.map("WorkspaceVersion", R.WorkspaceVersion); +} + } // namespace nixd::ipc diff --git a/nixd/lib/Server/OptionWorker.cpp b/nixd/lib/Server/OptionWorker.cpp index 617a70fc1..575f7834c 100644 --- a/nixd/lib/Server/OptionWorker.cpp +++ b/nixd/lib/Server/OptionWorker.cpp @@ -3,6 +3,7 @@ #include "nixd/Nix/Option.h" #include "nixd/Nix/Value.h" #include "nixd/Server/ConfigSerialization.h" +#include "nixd/Server/IPCSerialization.h" #include "nixd/Support/Diagnostic.h" #include "nixd/Support/ReplyRAII.h" From 4bbed66cab381c9371dc4edfca461d6ceb5fc6cf Mon Sep 17 00:00:00 2001 From: Yingchi Long Date: Thu, 27 Jul 2023 21:44:25 +0800 Subject: [PATCH 6/7] nixd: remove the test: goto-definition-updated.md --- .../nixd/test/goto-definition-updated.md | 120 ------------------ 1 file changed, 120 deletions(-) delete mode 100644 nixd/tools/nixd/test/goto-definition-updated.md diff --git a/nixd/tools/nixd/test/goto-definition-updated.md b/nixd/tools/nixd/test/goto-definition-updated.md deleted file mode 100644 index 25be40baf..000000000 --- a/nixd/tools/nixd/test/goto-definition-updated.md +++ /dev/null @@ -1,120 +0,0 @@ -# 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 - -Testing this nix file: - -```nix -let -a = 1; -b = 2; -c = 3; -in -c -``` - -```json -{ - "jsonrpc": "2.0", - "method": "textDocument/didOpen", - "params": { - "textDocument": { - "uri": "file:///let.nix", - "languageId": "nix", - "version": 1, - "text": "let\na = 1;\nb = 2;\nc = 3;\nin\nc" - } - } -} -``` - - -```json -{ - "jsonrpc": "2.0", - "method": "textDocument/didChange", - "params": { - "textDocument": { - "uri": "file:///let.nix", - "version": 2 - }, - "contentChanges": [ - { - "range": { - "start": { - "line": 5, - "character": 0 - }, - "end": { - "line": 5, - "character": 1 - } - }, - "rangeLength": 1, - "text": "a" - } - ] - } -} -``` - - -<-- textDocument/definition(1) - -```json -{ - "jsonrpc":"2.0", - "id":1, - "method":"textDocument/definition", - "params":{ - "textDocument":{ - "uri":"file:///let.nix" - }, - "position":{ - "line":5, - "character":0 - } - } -} -``` - - -``` - CHECK: "id": 1, -CHECK-NEXT: "jsonrpc": "2.0", -CHECK-NEXT: "result": { -CHECK-NEXT: "range": { -CHECK-NEXT: "end": { -CHECK-NEXT: "character": 1, -CHECK-NEXT: "line": 1 -CHECK-NEXT: }, -CHECK-NEXT: "start": { -CHECK-NEXT: "character": 0, -CHECK-NEXT: "line": 1 -CHECK-NEXT: } -CHECK-NEXT: }, -CHECK-NEXT: "uri": "file:///let.nix" -CHECK-NEXT: } -``` - -```json -{"jsonrpc":"2.0","method":"exit"} -``` From e07e862b6435118408f2d9b2b8b9f910ae9e66fe Mon Sep 17 00:00:00 2001 From: Yingchi Long Date: Thu, 27 Jul 2023 21:46:24 +0800 Subject: [PATCH 7/7] nixd/Server: canonicalize headers --- nixd/include/nixd/Server/IPC.h | 3 ++- nixd/lib/Server/Controller.cpp | 7 ++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/nixd/include/nixd/Server/IPC.h b/nixd/include/nixd/Server/IPC.h index 3fe2a3867..8a37aaad0 100644 --- a/nixd/include/nixd/Server/IPC.h +++ b/nixd/include/nixd/Server/IPC.h @@ -1,8 +1,9 @@ #pragma once -#include "lspserver/Protocol.h" #include "nixd/Server/Config.h" +#include "lspserver/Protocol.h" + namespace nixd::ipc { using WorkspaceVersionTy = uint64_t; diff --git a/nixd/lib/Server/Controller.cpp b/nixd/lib/Server/Controller.cpp index ee244cbee..6a8c4a832 100644 --- a/nixd/lib/Server/Controller.cpp +++ b/nixd/lib/Server/Controller.cpp @@ -1,4 +1,3 @@ -#include "lspserver/DraftStore.h" #include "nixd-config.h" #include "nixd/AST/ParseAST.h" @@ -14,14 +13,15 @@ #include "nixd/Support/Support.h" #include "lspserver/Connection.h" +#include "lspserver/DraftStore.h" #include "lspserver/Logger.h" #include "lspserver/Path.h" #include "lspserver/Protocol.h" #include "lspserver/SourceCode.h" #include "lspserver/URI.h" -#include "util.hh" -#include +#include + #include #include #include @@ -32,6 +32,7 @@ #include #include +#include #include #include #include