From e18e4e7e6f9aab0fd48851dbbdb00fa83f517b7a Mon Sep 17 00:00:00 2001 From: ykiko Date: Sun, 8 Sep 2024 17:10:57 +0800 Subject: [PATCH] implement basic communication between the server and the client. --- include/Protocol/Basic.h | 4 + include/Protocol/Language/SemanticToken.h | 22 ++-- include/Protocol/Lifecycle/Initialize.h | 18 +++- include/Server/Server.h | 2 + include/Server/Transport .h | 8 ++ include/Support/JSON.h | 10 +- src/Server/Server.cpp | 125 +++++++++++++++++++++- 7 files changed, 167 insertions(+), 22 deletions(-) create mode 100644 include/Server/Transport .h diff --git a/include/Protocol/Basic.h b/include/Protocol/Basic.h index 642e22df..cc6171ff 100644 --- a/include/Protocol/Basic.h +++ b/include/Protocol/Basic.h @@ -43,4 +43,8 @@ struct TextDocumentItem { string text; }; +struct TextDocumentIdentifier { + DocumentUri uri; +}; + } // namespace clice::protocol diff --git a/include/Protocol/Language/SemanticToken.h b/include/Protocol/Language/SemanticToken.h index 0bc2307f..e3cef273 100644 --- a/include/Protocol/Language/SemanticToken.h +++ b/include/Protocol/Language/SemanticToken.h @@ -12,7 +12,7 @@ enum class SemanticTokenType : uint8_t { /// Represents a character literal. Char, /// Represents a string literal. - String, + string, /// Represents a C/C++ keyword (e.g., `int`, `class`, `struct`). Keyword, /// Represents a compiler built-in macro, function, or keyword (e.g., `__stdcall`, @@ -126,10 +126,10 @@ struct SemanticTokensClientCapabilities { }; /// The token types that the client supports. - std::vector tokenTypes; + std::vector tokenTypes; /// The token modifiers that the client supports. - std::vector tokenModifiers; + std::vector tokenModifiers; /// The formats the client supports. /// formats: TokenFormat[]; @@ -149,10 +149,10 @@ struct SemanticTokensClientCapabilities { struct SemanticTokensLegend { /// The token types a server uses. - std::vector tokenTypes; + std::vector tokenTypes; /// The token modifiers a server uses. - std::vector tokenModifiers; + std::vector tokenModifiers; }; /// Server Capability: @@ -172,24 +172,20 @@ struct SemanticTokensOptions { /// Request: /// - method: `textDocument/semanticTokens/full` /// - params: `SemanticTokensParams` defined as follows: -struct SemanticTokensParamsBody { + +struct SemanticTokensParams { /// The text document. TextDocumentIdentifier textDocument; }; -using SemanticTokensParams = Combine< - // WorkDoneProgressParams, - // PartialResultParams, - SemanticTokensParamsBody>; - /// Response: /// - result: `SemanticTokens` defined as follows: struct SemanticTokens { /// An optional result id. - String resultId; + string resultId; /// The actual tokens. - std::vector data; + std::vector data; }; } // namespace clice::protocol diff --git a/include/Protocol/Lifecycle/Initialize.h b/include/Protocol/Lifecycle/Initialize.h index f82b619d..041eec2c 100644 --- a/include/Protocol/Lifecycle/Initialize.h +++ b/include/Protocol/Lifecycle/Initialize.h @@ -1,7 +1,19 @@ -#include "../Basic.h" +#include "../Language/SemanticToken.h" namespace clice::protocol { +struct ServerCapabilities { + std::string_view positionEncoding = "utf-16"; + SemanticTokensOptions semanticTokensProvider; +}; - -} \ No newline at end of file +struct InitializeResult { + ServerCapabilities capabilities; + + struct { + std::string_view name = "clice"; + std::string_view version = "0.0.1"; + } serverInfo; +}; + +} // namespace clice::protocol diff --git a/include/Server/Server.h b/include/Server/Server.h index c1ea2461..58197faa 100644 --- a/include/Server/Server.h +++ b/include/Server/Server.h @@ -5,6 +5,8 @@ namespace clice { class Server { public: int run(int argc, const char** argv); + + void handleMessage(std::string_view message); }; namespace global { diff --git a/include/Server/Transport .h b/include/Server/Transport .h new file mode 100644 index 00000000..79e09427 --- /dev/null +++ b/include/Server/Transport .h @@ -0,0 +1,8 @@ +#pragma once + +namespace clice { + + + + +} \ No newline at end of file diff --git a/include/Support/JSON.h b/include/Support/JSON.h index 1e6a806a..2ab59ad5 100644 --- a/include/Support/JSON.h +++ b/include/Support/JSON.h @@ -19,17 +19,17 @@ constexpr inline bool is_integral_v = template Object serialize(const T& object) { Object result; - for_each(object, [&](std::string_view name, Value& value) { + for_each(object, [&](llvm::StringRef name, Value& value) { if constexpr(is_array_v) { Array array; for(const auto& element: value) { array.push_back(serialize(element)); } - result.try_emplace(llvm::StringRef(name), std::move(array)); + result.try_emplace(name, std::move(array)); } else if constexpr(std::is_constructible_v) { - result.try_emplace(llvm::StringRef(name), value); + result.try_emplace(name, value); } else { - result.try_emplace(llvm::StringRef(name), serialize(value)); + result.try_emplace(name, serialize(value)); } }); return result; @@ -38,7 +38,7 @@ Object serialize(const T& object) { template T deserialize(const Object& object) { T result; - for_each(result, [&](std::string_view name, Value& value) { + for_each(result, [&](llvm::StringRef name, Value& value) { if constexpr(is_array_v) { if(const auto* array = object.getArray(name)) { for(std::size_t i = 0; i < array->size(); ++i) { diff --git a/src/Server/Server.cpp b/src/Server/Server.cpp index 904cd7ac..46a51f08 100644 --- a/src/Server/Server.cpp +++ b/src/Server/Server.cpp @@ -1,12 +1,135 @@ +#include #include +#include +#include +#include +#include +#include +#include +#include namespace clice { +static uv_loop_t* loop; +static uv_pipe_t stdin_pipe; +static uv_pipe_t stdout_pipe; + +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; + } + } +}; + +void alloc_buffer(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) { + static llvm::SmallString<4096> buffer; + buffer.resize(suggested_size); + buf->base = buffer.data(); + buf->len = buffer.size(); +} + +Buffer buffer; + +void read_stdin(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()) { + global::server.handleMessage(message); + } + } else if(nread < 0) { + if(nread != UV_EOF) { + spdlog::error("Read error: {}", uv_err_name(nread)); + } + uv_close((uv_handle_t*)stream, NULL); + } +} + int Server::run(int argc, const char** argv) { - // TODO: + // set logger + llvm::SmallString<128> temp; + temp.append(path::parent_path((path::parent_path(argv[0])))); + path::append(temp, "logs"); + auto error = llvm::sys::fs::make_absolute(temp); + path::append(temp, "clice.log"); + auto logger = spdlog::basic_logger_mt("clice", std::string(temp.str())); + logger->flush_on(spdlog::level::trace); + spdlog::set_default_logger(logger); + + loop = uv_default_loop(); + uv_pipe_init(loop, &stdin_pipe, 0); + uv_pipe_open(&stdin_pipe, 0); + + uv_pipe_init(loop, &stdout_pipe, 0); + uv_pipe_open(&stdout_pipe, 1); + + uv_read_start((uv_stream_t*)&stdin_pipe, alloc_buffer, read_stdin); + + uv_run(loop, UV_RUN_DEFAULT); + + uv_loop_close(loop); + return 0; } +void Server::handleMessage(std::string_view message) { + auto result = json::parse(message); + if(!result) { + spdlog::error("Error parsing JSON: {}", llvm::toString(result.takeError())); + } + + spdlog::info("Received message: {}", message); + auto input = result->getAsObject(); + auto id = input->get("id"); + auto method = input->get("method"); + auto params = input->get("params"); + + json::Object response; + response.try_emplace("jsonrpc", "2.0"); + response.try_emplace("id", *id); + response.try_emplace("result", json::serialize(protocol::InitializeResult{})); + + llvm::json::Value responseValue = std::move(response); + + std::string s; + llvm::raw_string_ostream stream(s); + stream << responseValue; + stream.flush(); + + s = "Content-Length: " + std::to_string(s.size()) + "\r\n\r\n" + s; + + uv_buf_t buf = uv_buf_init(s.data(), s.size()); + uv_write_t req; + auto state = uv_write(&req, (uv_stream_t*)&stdout_pipe, &buf, 1, NULL); + if(state < 0) { + spdlog::error("Error writing to stdout: {}", uv_strerror(state)); + } + spdlog::info("Sent message: {}", s); +} + namespace global { Server server; }