Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion packages/cxx-gen-lsp/src/gen_fwd_h.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,15 @@ import { writeFileSync } from "node:fs";
import { copyrightHeader } from "./copyrightHeader.js";

const fragment = `
[[noreturn]] void lsp_runtime_error(const std::string& msg);

class LSPObject;

using LSPAny = json;
using Pattern = std::string;

[[nodiscard]] auto withUnsafeJson(auto block) { return block(json()); }
[[noreturn]] void lsp_runtime_error(const std::string& msg);

class LSPObject {
public:
LSPObject() = default;
Expand Down
4 changes: 4 additions & 0 deletions src/frontend/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ add_executable(cxx ${SOURCES})

target_link_libraries(cxx cxx-parser cxx-lsp)

target_compile_definitions(cxx PRIVATE
CXX_VERSION="${CMAKE_PROJECT_VERSION}"
)

if(EMSCRIPTEN)
target_link_options(cxx PUBLIC
"SHELL:-s EXIT_RUNTIME=1"
Expand Down
127 changes: 111 additions & 16 deletions src/frontend/cxx/lsp_server.cc
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
#include <format>
#include <iostream>
#include <set>
#include <unordered_map>
#include <vector>

namespace cxx::lsp {
Expand Down Expand Up @@ -106,7 +107,7 @@ auto readHeaders(std::istream& input) -> Headers {
return headers;
}

struct Input {
struct CxxDocument {
struct Diagnostics final : cxx::DiagnosticsClient {
json messages = json::array();
Vector<lsp::Diagnostic> diagnostics{messages};
Expand Down Expand Up @@ -146,7 +147,7 @@ struct Input {
TranslationUnit unit;
std::unique_ptr<Toolchain> toolchain;

Input(const CLI& cli) : cli(cli), unit(&control, &diagnosticsClient) {}
CxxDocument(const CLI& cli) : cli(cli), unit(&control, &diagnosticsClient) {}

void parse(std::string source, std::string fileName) {
configure();
Expand Down Expand Up @@ -273,10 +274,12 @@ struct Input {
};

class Server {
const CLI& cli;
std::istream& input;
std::unordered_map<std::string, std::shared_ptr<CxxDocument>> documents;

public:
Server() : input(std::cin) {}
Server(const CLI& cli) : cli(cli), input(std::cin) {}

auto start() -> int {
while (input.good()) {
Expand Down Expand Up @@ -353,14 +356,66 @@ class Server {

void operator()(const DidOpenTextDocumentNotification& notification) {
std::cerr << std::format("Did receive DidOpenTextDocumentNotification\n");

auto textDocument = notification.params().textDocument();
const auto uri = textDocument.uri();
const auto text = textDocument.text();
const auto version = textDocument.version();

auto doc = std::make_shared<CxxDocument>(cli);
doc->parse(std::move(text), pathFromUri(uri));
documents[uri] = doc;

std::cerr << std::format("Parsed document: {}, reported {} messages\n", uri,
doc->diagnosticsClient.messages.size());
}

void operator()(const DidCloseTextDocumentNotification& notification) {
std::cerr << std::format("Did receive DidCloseTextDocumentNotification\n");

const auto uri = notification.params().textDocument().uri();
documents.erase(uri);
}

void operator()(const DidChangeTextDocumentNotification& notification) {
std::cerr << std::format("Did receive DidChangeTextDocumentNotification\n");

const auto textDocument = notification.params().textDocument();
const auto uri = textDocument.uri();
const auto version = textDocument.version();

if (!documents.contains(uri)) {
std::cerr << std::format("Document not found: {}\n", uri);
return;
}

// update the document
auto contentChanges = notification.params().contentChanges();
const std::size_t contentChangeCount = contentChanges.size();
for (std::size_t i = 0; i < contentChangeCount; ++i) {
auto change = contentChanges.at(i);
if (std::holds_alternative<TextDocumentContentChangeWholeDocument>(
change)) {
auto text =
std::get<TextDocumentContentChangeWholeDocument>(change).text();

// parse the document
auto doc = std::make_shared<CxxDocument>(cli);
doc->parse(std::move(text), pathFromUri(uri));
documents[uri] = doc;

std::cerr << std::format("Parsed document: {}, reported {} messages\n",
uri, doc->diagnosticsClient.messages.size());
}
}
}

[[nodiscard]] auto pathFromUri(const std::string& uri) -> std::string {
if (uri.starts_with("file://")) {
return uri.substr(7);
}

lsp_runtime_error(std::format("Unsupported URI scheme: {}\n", uri));
}

//
Expand All @@ -369,21 +424,57 @@ class Server {

void operator()(const InitializeRequest& request) {
std::cerr << std::format("Did receive InitializeRequest\n");
auto storage = json::object();
InitializeResult result(storage);
result.serverInfo<ServerInfo>().name("cxx-lsp").version("0.0.1");
result.capabilities().textDocumentSync(TextDocumentSyncKind::kFull);

sendToClient(result, request.id());
withUnsafeJson([&](json storage) {
InitializeResult result(storage);
result.serverInfo<ServerInfo>().name("cxx-lsp").version(CXX_VERSION);
result.capabilities().textDocumentSync(TextDocumentSyncKind::kFull);
result.capabilities().diagnosticProvider<DiagnosticOptions>().identifier(
"cxx-lsp");
// .workspaceDiagnostics(true);

sendToClient(result, request.id());
});
}

void operator()(const ShutdownRequest& request) {
std::cerr << std::format("Did receive ShutdownRequest\n");

json storage;
LSPObject result(storage);
withUnsafeJson([&](json storage) {
LSPObject result(storage);
sendToClient(result, request.id());
});
}

void operator()(const DocumentDiagnosticRequest& request) {
std::cerr << std::format("Did receive DocumentDiagnosticRequest\n");

auto textDocument = request.params().textDocument();
auto uri = textDocument.uri();

if (!documents.contains(uri)) {
std::cerr << std::format("Document not found: {}\n", uri);
return;
}

auto doc = documents[uri];

sendToClient(result, request.id());
withUnsafeJson([&](json storage) {
FullDocumentDiagnosticReport report(storage);

auto diagnostics = Vector<Diagnostic>(doc->diagnosticsClient.messages);
report.items(diagnostics);

// TODO: string literals in C++ LSP API
storage["kind"] = "full";

// TODO: responses in C++ LSP API
json response;
response["jsonrpc"] = "2.0";
response["id"] = std::get<long>(*request.id());
response["result"] = report;
sendToClient(response);
});
}

//
Expand All @@ -392,17 +483,21 @@ class Server {
void operator()(const LSPRequest& request) {
std::cerr << "Request: " << request.method() << "\n";

if (request.id().has_value()) {
// send an empty response.
json storage;
if (!request.id().has_value()) {
// nothing to do for notifications
return;
}

// send an empty response.
withUnsafeJson([&](json storage) {
LSPObject result(storage);
sendToClient(result, request.id());
}
});
}
};

int startServer(const CLI& cli) {
Server server;
Server server{cli};
auto exitCode = server.start();
return exitCode;
}
Expand Down
5 changes: 4 additions & 1 deletion src/lsp/cxx/lsp/fwd.h
Original file line number Diff line number Diff line change
Expand Up @@ -623,11 +623,14 @@ class LogTraceNotification;
class CancelNotification;
class ProgressNotification;

[[noreturn]] void lsp_runtime_error(const std::string& msg);
class LSPObject;

using LSPAny = json;
using Pattern = std::string;

[[nodiscard]] auto withUnsafeJson(auto block) { return block(json()); }
[[noreturn]] void lsp_runtime_error(const std::string& msg);

class LSPObject {
public:
LSPObject() = default;
Expand Down
Loading