Skip to content

Commit

Permalink
nixd: threaded RPC
Browse files Browse the repository at this point in the history
  • Loading branch information
inclyc committed Mar 29, 2024
1 parent 551ad9c commit 139d129
Show file tree
Hide file tree
Showing 7 changed files with 225 additions and 129 deletions.
65 changes: 37 additions & 28 deletions nixd/tools/nixd/src/CodeAction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,46 +6,55 @@
#include "Controller.h"
#include "Convert.h"

#include <boost/asio/post.hpp>

namespace nixd {

using namespace llvm::json;
using namespace lspserver;

void Controller::onCodeAction(const lspserver::CodeActionParams &Params,
Callback<std::vector<CodeAction>> Reply) {
PathRef File = Params.textDocument.uri.file();
std::string File(Params.textDocument.uri.file());
Range Range = Params.range;
const std::vector<nixf::Diagnostic> &Diagnostics = TUs[File].diagnostics();
std::vector<CodeAction> Actions;
Actions.reserve(Diagnostics.size());
for (const nixf::Diagnostic &D : Diagnostics) {
auto DRange = toLSPRange(D.range());
if (!Range.overlap(DRange))
continue;
auto Action = [Reply = std::move(Reply), File, Range, this]() mutable {
std::vector<nixf::Diagnostic> Diagnostics;
{
std::lock_guard TU(TUsLock);
Diagnostics = TUs[File].diagnostics();
}
std::vector<CodeAction> Actions;
Actions.reserve(Diagnostics.size());
for (const nixf::Diagnostic &D : Diagnostics) {
auto DRange = toLSPRange(D.range());
if (!Range.overlap(DRange))
continue;

// Add fixes.
for (const nixf::Fix &F : D.fixes()) {
std::vector<TextEdit> Edits;
Edits.reserve(F.edits().size());
for (const nixf::TextEdit &TE : F.edits()) {
Edits.emplace_back(TextEdit{
.range = toLSPRange(TE.oldRange()),
.newText = std::string(TE.newText()),
// Add fixes.
for (const nixf::Fix &F : D.fixes()) {
std::vector<TextEdit> Edits;
Edits.reserve(F.edits().size());
for (const nixf::TextEdit &TE : F.edits()) {
Edits.emplace_back(TextEdit{
.range = toLSPRange(TE.oldRange()),
.newText = std::string(TE.newText()),
});
}
using Changes = std::map<std::string, std::vector<TextEdit>>;
std::string FileURI = URIForFile::canonicalize(File, File).uri();
WorkspaceEdit WE{.changes = Changes{
{std::move(FileURI), std::move(Edits)},
}};
Actions.emplace_back(CodeAction{
.title = F.message(),
.kind = std::string(CodeAction::QUICKFIX_KIND),
.edit = std::move(WE),
});
}
using Changes = std::map<std::string, std::vector<TextEdit>>;
std::string FileURI = URIForFile::canonicalize(File, File).uri();
WorkspaceEdit WE{.changes = Changes{
{std::move(FileURI), std::move(Edits)},
}};
Actions.emplace_back(CodeAction{
.title = F.message(),
.kind = std::string(CodeAction::QUICKFIX_KIND),
.edit = std::move(WE),
});
}
}
Reply(std::move(Actions));
Reply(std::move(Actions));
};
boost::asio::post(Pool, std::move(Action));
}

} // namespace nixd
12 changes: 12 additions & 0 deletions nixd/tools/nixd/src/Controller.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
#include "lspserver/DraftStore.h"
#include "lspserver/LSPServer.h"

#include <boost/asio/thread_pool.hpp>

namespace nixd {

class Controller : public lspserver::LSPServer {
Expand All @@ -15,8 +17,14 @@ class Controller : public lspserver::LSPServer {
llvm::unique_function<void(const lspserver::PublishDiagnosticsParams &)>
PublishDiagnostic;

std::mutex TUsLock;
llvm::StringMap<NixTU> TUs;

boost::asio::thread_pool Pool;

/// In lit-test mode. Disable some concurrency for better text-testing.
bool LitTest;

/// Action right after a document is added (including updates).
void actOnDocumentAdd(lspserver::PathRef File,
std::optional<int64_t> Version);
Expand Down Expand Up @@ -51,6 +59,10 @@ class Controller : public lspserver::LSPServer {
public:
Controller(std::unique_ptr<lspserver::InboundPort> In,
std::unique_ptr<lspserver::OutboundPort> Out);

~Controller() { Pool.join(); }

void setLitTest(bool LitTest) { this->LitTest = LitTest; }
};

} // namespace nixd
46 changes: 29 additions & 17 deletions nixd/tools/nixd/src/Hover.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,42 @@
#include "Controller.h"
#include "Convert.h"

#include <llvm/Support/Error.h>

#include <boost/asio/post.hpp>

#include <mutex>

namespace nixd {

using namespace llvm::json;
using namespace lspserver;
using namespace rpc;

void Controller::onHover(const TextDocumentPositionParams &Params,
Callback<std::optional<Hover>> Reply) {
PathRef File = Params.textDocument.uri.file();
const nixf::Node *AST = TUs[File].ast().get();
nixf::Position Pos{Params.position.line, Params.position.character};
const nixf::Node *N = AST->descend({Pos, Pos});
if (!N) {
Reply(std::nullopt);
return;
}
std::string Name = N->name();
Reply(Hover{
.contents =
MarkupContent{
.kind = MarkupKind::Markdown,
.value = "`" + Name + "`",
},
.range = toLSPRange(N->range()),
});
auto Action = [Reply = std::move(Reply),
File = std::string(Params.textDocument.uri.file()),
RawPos = Params.position, this]() mutable {
std::lock_guard G(TUsLock);
const std::shared_ptr<nixf::Node> &AST = TUs[File].ast();
nixf::Position Pos{RawPos.line, RawPos.character};
const nixf::Node *N = AST->descend({Pos, Pos});
if (!N) {
Reply(std::nullopt);
return;
}
std::string Name = N->name();
Reply(Hover{
.contents =
MarkupContent{
.kind = MarkupKind::Markdown,
.value = "`" + Name + "`",
},
.range = toLSPRange(N->range()),
});
};
boost::asio::post(Pool, std::move(Action));
}

} // namespace nixd
3 changes: 3 additions & 0 deletions nixd/tools/nixd/src/Main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ int main(int argc, char *argv[]) {
auto Controller =
std::make_unique<nixd::Controller>(std::move(In), std::move(Out));

if (LitTest)
Controller->setLitTest(LitTest);

Controller->run();

return 0;
Expand Down
106 changes: 63 additions & 43 deletions nixd/tools/nixd/src/Support.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
#include "nixf/Bytecode/Write.h"
#include "nixf/Parse/Parser.h"

#include <boost/asio/post.hpp>

#include <mutex>

using namespace lspserver;
using namespace nixd;

Expand All @@ -27,54 +31,70 @@ namespace nixd {

void Controller::actOnDocumentAdd(PathRef File,
std::optional<int64_t> Version) {
auto Draft = Store.getDraft(File);
assert(Draft && "Added document is not in the store?");

std::vector<nixf::Diagnostic> Diagnostics;
std::shared_ptr<nixf::Node> AST = nixf::parse(*Draft->Contents, Diagnostics);
publishDiagnostics(File, Version, Diagnostics);

if (!AST) {
TUs[File] = NixTU(std::move(Diagnostics), std::move(AST), std::nullopt);
return;
}

// Serialize the AST into shared memory. Prepare for evaluation.
std::stringstream OS;
nixf::writeBytecode(OS, *AST);
std::string Buf = OS.str();
if (Buf.empty()) {
lspserver::log("empty AST for {0}", File);
TUs[File] = NixTU(std::move(Diagnostics), std::move(AST), std::nullopt);
return;
}

// Create an mmap()-ed region, and write AST byte code there.
std::string ShmName = getShmName(File);

auto Shm = std::make_unique<util::AutoRemoveShm>(
ShmName, static_cast<bipc::offset_t>(Buf.size()));

auto Region =
std::make_unique<bipc::mapped_region>(Shm->get(), bipc::read_write);

std::memcpy(Region->get_address(), Buf.data(), Buf.size());

lspserver::log("serialized AST {0} to {1}, size: {2}", File, ShmName,
Buf.size());

TUs[File] = NixTU(std::move(Diagnostics), std::move(AST),
util::OwnedRegion{std::move(Shm), std::move(Region)});

if (Eval) {
Eval->RegisterBC(rpc::RegisterBCParams{
ShmName, ".", ".", static_cast<std::int64_t>(Buf.size())});
auto Action = [this, File = std::string(File), Version]() {
auto Draft = Store.getDraft(File);
assert(Draft && "Added document is not in the store?");

std::vector<nixf::Diagnostic> Diagnostics;
std::shared_ptr<nixf::Node> AST =
nixf::parse(*Draft->Contents, Diagnostics);
publishDiagnostics(File, Version, Diagnostics);

if (!AST) {
std::lock_guard G(TUsLock);
TUs[File] = NixTU(std::move(Diagnostics), std::move(AST), std::nullopt);
return;
}

// Serialize the AST into shared memory. Prepare for evaluation.
std::stringstream OS;
nixf::writeBytecode(OS, *AST);
std::string Buf = OS.str();
if (Buf.empty()) {
lspserver::log("empty AST for {0}", File);
std::lock_guard G(TUsLock);
TUs[File] = NixTU(std::move(Diagnostics), std::move(AST), std::nullopt);
return;
}

// Create an mmap()-ed region, and write AST byte code there.
std::string ShmName = getShmName(File);

auto Shm = std::make_unique<util::AutoRemoveShm>(
ShmName, static_cast<bipc::offset_t>(Buf.size()));

auto Region =
std::make_unique<bipc::mapped_region>(Shm->get(), bipc::read_write);

std::memcpy(Region->get_address(), Buf.data(), Buf.size());

lspserver::log("serialized AST {0} to {1}, size: {2}", File, ShmName,
Buf.size());

std::lock_guard G(TUsLock);
TUs[File] = NixTU(std::move(Diagnostics), std::move(AST),
util::OwnedRegion{std::move(Shm), std::move(Region)});

if (Eval) {
Eval->RegisterBC(rpc::RegisterBCParams{
ShmName, ".", ".", static_cast<std::int64_t>(Buf.size())});
}
};
if (LitTest) {
// In lit-testing mode we don't want to having requests processed before
// document updates. (e.g. Hover before parsing)
// So just invoke the action here.
Action();
} else {
// Otherwise we may want to concurently parse & serialize the file, so post
// it to the thread pool.
boost::asio::post(Pool, std::move(Action));
}
}

Controller::Controller(std::unique_ptr<lspserver::InboundPort> In,
std::unique_ptr<lspserver::OutboundPort> Out)
: LSPServer(std::move(In), std::move(Out)) {
: LSPServer(std::move(In), std::move(Out)), LitTest(false) {

// Life Cycle
Registry.addMethod("initialize", this, &Controller::onInitialize);
Expand Down
41 changes: 0 additions & 41 deletions nixd/tools/nixd/test/hover.md → nixd/tools/nixd/test/hover-1.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,47 +76,6 @@ CHECK-NEXT: }
CHECK-NEXT: }
```

<-- textDocument/hover(2)


```json
{
"jsonrpc":"2.0",
"id":2,
"method":"textDocument/hover",
"params":{
"textDocument":{
"uri":"file:///basic.nix"
},
"position":{
"line":0,
"character":4
}
}
}
```

```
CHECK: "id": 2,
CHECK-NEXT: "jsonrpc": "2.0",
CHECK-NEXT: "result": {
CHECK-NEXT: "contents": {
CHECK-NEXT: "kind": "markdown",
CHECK-NEXT: "value": "`Binding`"
CHECK-NEXT: },
CHECK-NEXT: "range": {
CHECK-NEXT: "end": {
CHECK-NEXT: "character": 8,
CHECK-NEXT: "line": 0
CHECK-NEXT: },
CHECK-NEXT: "start": {
CHECK-NEXT: "character": 2,
CHECK-NEXT: "line": 0
CHECK-NEXT: }
CHECK-NEXT: }
CHECK-NEXT: }
```

```json
{"jsonrpc":"2.0","method":"exit"}
```
Loading

0 comments on commit 139d129

Please sign in to comment.