From 69872ab8348b23ea348b4967e0bb6a60a9bb5617 Mon Sep 17 00:00:00 2001 From: ykiko Date: Sun, 7 Jul 2024 20:22:36 +0800 Subject: [PATCH] soem fix. --- include/LSP/MessageBuffer.h | 39 ---------------- include/LSP/Server.h | 9 ++-- include/LSP/Transport.h | 44 ++++++++++++++++++ include/Support/Async.h | 1 + src/LSP/Server.cpp | 49 ++------------------ src/LSP/Transport.cpp | 87 +++++++++++++++++++++++++++++++++++ src/main.cpp | 13 ++++-- vscode-clice/src/extension.ts | 2 +- 8 files changed, 150 insertions(+), 94 deletions(-) delete mode 100644 include/LSP/MessageBuffer.h create mode 100644 include/LSP/Transport.h create mode 100644 include/Support/Async.h create mode 100644 src/LSP/Transport.cpp diff --git a/include/LSP/MessageBuffer.h b/include/LSP/MessageBuffer.h deleted file mode 100644 index 388067d4..00000000 --- a/include/LSP/MessageBuffer.h +++ /dev/null @@ -1,39 +0,0 @@ -#pragma once - -#include -#include - -namespace clice { - -class MessageBuffer { - std::vector buffer; - std::size_t max = 0; - -public: - void write(std::string_view message) { buffer.insert(buffer.end(), message.begin(), message.end()); } - - std::string_view read() { - std::string_view view = std::string_view(buffer.data(), buffer.size()); - auto start = view.find("Content-Length: ") + 16; - auto end = view.find("\r\n\r\n"); - - if(start != std::string_view::npos || end != std::string_view::npos) { - std::size_t length = std::stoul(std::string(view.substr(start, end - start))); - if(view.size() >= length + end + 4) { - this->max = length + end + 4; - return view.substr(end + 4, length); - } - } - - return {}; - } - - void clear() { - if(max != 0) { - buffer.erase(buffer.begin(), buffer.begin() + max); - max = 0; - } - } -}; - -} // namespace clice diff --git a/include/LSP/Server.h b/include/LSP/Server.h index 270d04ec..4dae3107 100644 --- a/include/LSP/Server.h +++ b/include/LSP/Server.h @@ -1,9 +1,7 @@ #pragma once -#include -#include - #include "Protocol.h" +#include "Transport.h" namespace clice { @@ -13,11 +11,10 @@ extern class Server server; /// core class responsible for starting the server class Server { uv_loop_t* loop; - uv_pipe_t stdin_pipe; - uv_pipe_t stdout_pipe; + std::unique_ptr transport; public: - int start(); + int run(); int exit(); void handle_message(std::string_view message); diff --git a/include/LSP/Transport.h b/include/LSP/Transport.h new file mode 100644 index 00000000..7dd1c77f --- /dev/null +++ b/include/LSP/Transport.h @@ -0,0 +1,44 @@ +#pragma once + +#include + +namespace clice { + +struct Transport { + virtual void send(std::string_view) = 0; + virtual ~Transport() = default; +}; + +class Pipe : public Transport { +private: + uv_pipe_t stdin_pipe; + uv_pipe_t stdout_pipe; + +private: + static void alloc_buffer(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf); + static void on_read(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf); + +public: + Pipe() { + // initialize the pipe + uv_loop_t* loop = uv_default_loop(); + uv_pipe_init(loop, &stdin_pipe, 0); + uv_pipe_init(loop, &stdout_pipe, 0); + + // bind to stdin and stdout + uv_pipe_open(&stdin_pipe, 0); + uv_pipe_open(&stdout_pipe, 1); + + // set callback for reading from stdin + uv_read_start((uv_stream_t*)&stdin_pipe, alloc_buffer, on_read); + } + + void send(std::string_view message) override; + + ~Pipe() override { + uv_close((uv_handle_t*)&stdin_pipe, nullptr); + uv_close((uv_handle_t*)&stdout_pipe, nullptr); + } +}; + +} // namespace clice diff --git a/include/Support/Async.h b/include/Support/Async.h new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/include/Support/Async.h @@ -0,0 +1 @@ + diff --git a/src/LSP/Server.cpp b/src/LSP/Server.cpp index dfdd7548..21e09a91 100644 --- a/src/LSP/Server.cpp +++ b/src/LSP/Server.cpp @@ -1,6 +1,6 @@ #include #include -#include +#include #include #include @@ -8,47 +8,17 @@ namespace clice { Server server; -static MessageBuffer buffer; -void on_read(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) { - if(nread > 0) { - buffer.write(std::string_view(buf->base, nread)); - if(auto message = buffer.read(); !message.empty()) { - server.handle_message(message); - buffer.clear(); - } - } else if(nread == UV_EOF) { - uv_read_stop(stream); - } else if(nread < 0) { - uv_read_stop(stream); - } - free(buf->base); -} - -int Server::start() { +int Server::run() { loop = uv_default_loop(); - // initialize pipe and bind to stdin - uv_pipe_init(loop, &stdin_pipe, 0); - uv_pipe_init(loop, &stdout_pipe, 0); - uv_pipe_open(&stdin_pipe, 0); - uv_pipe_open(&stdout_pipe, 1); - - auto alloc_buffer = [](uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) { - buf->base = (char*)std::malloc(suggested_size); - buf->len = suggested_size; - }; - - uv_read_start((uv_stream_t*)&stdin_pipe, alloc_buffer, on_read); + transport = std::make_unique(); // start the event loop return uv_run(loop, UV_RUN_DEFAULT); } int Server::exit() { - // uv_pipe_end(&stdin_pipe); - // uv_pipe_end(&stdout_pipe); - // stop the event loop uv_stop(loop); return 0; @@ -57,24 +27,15 @@ int Server::exit() { void Server::handle_message(std::string_view message) { try { auto input = json::parse(message); - int id = input["id"].get(); std::string_view method = input["method"].get(); - logger::info("method: {}", method); - if(method == "initialize") { + int id = input["id"].get(); // initialize(); json json = { {"id", id }, {"result", clice::serialize(InitializeResult{})} }; - - auto stream = json.dump(); - std::string header = "Content-Length: " + std::to_string(stream.size()) + "\r\n\r\n"; - header += stream; - - uv_buf_t buf = uv_buf_init(header.data(), header.size()); - uv_write_t* req = (uv_write_t*)malloc(sizeof(uv_write_t)); - uv_write(req, (uv_stream_t*)&stdout_pipe, &buf, 1, NULL); + transport->send(json.dump()); } } catch(std::exception& e) { logger::error("failed to parse JSON: {}", e.what()); diff --git a/src/LSP/Transport.cpp b/src/LSP/Transport.cpp new file mode 100644 index 00000000..e2dd67d8 --- /dev/null +++ b/src/LSP/Transport.cpp @@ -0,0 +1,87 @@ +#include +#include +#include + +namespace { + +/// a helper class used to read and write messages for LSP +class Buffer { + std::vector buffer; + std::size_t max = 0; + +public: + void write(std::string_view message) { buffer.insert(buffer.end(), message.begin(), message.end()); } + + std::string_view read() { + std::string_view view = std::string_view(buffer.data(), buffer.size()); + auto start = view.find("Content-Length: ") + 16; + auto end = view.find("\r\n\r\n"); + + if(start != std::string_view::npos && end != std::string_view::npos) { + std::size_t length = std::stoul(std::string(view.substr(start, end - start))); + if(view.size() >= length + end + 4) { + this->max = length + end + 4; + return view.substr(end + 4, length); + } + } + + return {}; + } + + void clear() { + if(max != 0) { + buffer.erase(buffer.begin(), buffer.begin() + max); + max = 0; + } + } +}; + +} // namespace + +namespace clice { + +void Pipe::alloc_buffer(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) { + buf->base = (char*)std::malloc(suggested_size); + buf->len = suggested_size; +} + +void Pipe::on_read(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) { + static Buffer buffer = {}; + if(nread > 0) { + /// NOTICE: on_read function is always running in the main thread + /// so there is no need to worry about thread safety + buffer.write(std::string_view(buf->base, nread)); + + /// read the message from the buffer + if(std::string_view message = buffer.read(); !message.empty()) { + /// if the message is not empty, handle it + logger::info("read message: {}", message); + server.handle_message(message); + buffer.clear(); + } + } else if(nread == UV_EOF) { + uv_read_stop(stream); + } else if(nread < 0) { + logger::error("error reading from stream: {}", uv_strerror(nread)); + uv_read_stop(stream); + } + + /// free the buffer + free(buf->base); +} + +void Pipe::send(std::string_view message) { + std::string header = "Content-Length: " + std::to_string(message.size()) + "\r\n\r\n"; + header += message; + uv_buf_t buf = uv_buf_init(header.data(), header.size()); + + uv_write_t* req = (uv_write_t*)std::malloc(sizeof(uv_write_t)); + uv_write(req, (uv_stream_t*)&stdout_pipe, &buf, 1, [](uv_write_t* req, int status) { + if(status < 0) { + logger::error("error writing to stream: {}", uv_strerror(status)); + } + std::free(req); + }); +} + +} // namespace clice diff --git a/src/main.cpp b/src/main.cpp index 3e051171..57176459 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4,9 +4,14 @@ #include int main(int argc, char** argv) { - clice::logger::init(argv[0]); - auto& server = clice::server; - clice::logger::info("Starting server..."); - server.start(); + try { + clice::logger::init(argv[0]); + auto& server = clice::server; + clice::logger::info("Starting server..."); + server.run(); + } catch(std::exception& e) { + clice::logger::error("Failed to start server: {}", e.what()); + return 1; + } return 0; } diff --git a/vscode-clice/src/extension.ts b/vscode-clice/src/extension.ts index 31ffc0a0..0f07970a 100644 --- a/vscode-clice/src/extension.ts +++ b/vscode-clice/src/extension.ts @@ -30,7 +30,7 @@ export async function activate(context: ExtensionContext) { client = new LanguageClient( 'clice-client', - 'client for clice language server', + 'clice-client', serverOptions, clientOptions );