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; }