From 5cd33cbd7b79011f5a7107646a81b569ae151d85 Mon Sep 17 00:00:00 2001 From: Albert Cervin Date: Thu, 10 Oct 2024 23:26:04 +0200 Subject: [PATCH] More lsp protocol/transport work --- Makefile | 10 ++-- src/dged/bufread.c | 138 +++++++++++++++++++++++++++++++++++++++++++++ src/dged/bufread.h | 13 +++++ src/dged/json.c | 15 ++++- src/dged/json.h | 11 +++- src/dged/jsonrpc.c | 25 ++++++++ src/dged/jsonrpc.h | 23 +++++--- src/dged/lsp.c | 101 +++++++++++++++++++++++++++++++++ src/dged/lsp.h | 43 +++++--------- test/bufread.c | 63 +++++++++++++++++++++ test/main.c | 3 + test/test.h | 1 + 12 files changed, 398 insertions(+), 48 deletions(-) create mode 100644 src/dged/bufread.c create mode 100644 src/dged/bufread.h create mode 100644 src/dged/jsonrpc.c create mode 100644 test/bufread.c diff --git a/Makefile b/Makefile index 5630d10..37c80a8 100644 --- a/Makefile +++ b/Makefile @@ -22,13 +22,13 @@ HEADERS = src/dged/settings.h src/dged/minibuffer.h src/dged/keyboard.h src/dged src/dged/vec.h src/dged/window.h src/dged/hash.h src/dged/undo.h src/dged/lang.h \ src/dged/settings-parse.h src/dged/utf8.h src/main/cmds.h src/main/bindings.h \ src/main/search-replace.h src/dged/location.h src/dged/buffer_view.h src/main/completion.h \ - src/dged/timers.h src/dged/s8.h src/main/version.h src/config.h src/dged/process.h + src/dged/timers.h src/dged/s8.h src/main/version.h src/config.h src/dged/process.h src/dged/bufread.h SOURCES = src/dged/binding.c src/dged/buffer.c src/dged/command.c src/dged/display.c \ src/dged/keyboard.c src/dged/minibuffer.c src/dged/text.c \ src/dged/utf8.c src/dged/buffers.c src/dged/window.c src/dged/allocator.c src/dged/undo.c \ src/dged/settings.c src/dged/lang.c src/dged/settings-parse.c src/dged/location.c \ - src/dged/buffer_view.c src/dged/timers.c src/dged/s8.c src/dged/path.c src/dged/hash.c + src/dged/buffer_view.c src/dged/timers.c src/dged/s8.c src/dged/path.c src/dged/hash.c src/dged/bufread.c MAIN_SOURCES = src/main/main.c src/main/cmds.c src/main/bindings.c src/main/search-replace.c src/main/completion.c @@ -46,7 +46,7 @@ MAIN_SOURCES = src/main/main.c src/main/cmds.c src/main/bindings.c src/main/sear TEST_SOURCES = test/assert.c test/buffer.c test/text.c test/utf8.c test/main.c \ test/command.c test/keyboard.c test/fake-reactor.c test/allocator.c \ - test/minibuffer.c test/undo.c test/settings.c test/container.c + test/minibuffer.c test/undo.c test/settings.c test/container.c test/bufread.c prefix ?= /usr/local DESTDIR ?= $(prefix) @@ -79,8 +79,8 @@ ASAN ?= false .endif .if $(LSP_ENABLE) == true - HEADERS += src/dged/lsp.h src/main/lsp.h src/dged/json.h - SOURCES += src/dged/lsp.c src/dged/json.c + HEADERS += src/dged/lsp.h src/main/lsp.h src/dged/json.h src/dged/jsonrpc.h + SOURCES += src/dged/lsp.c src/dged/json.c src/dged/jsonrpc.c MAIN_SOURCES += src/main/lsp.c TEST_SOURCES += test/json.c CFLAGS += -DLSP_ENABLED diff --git a/src/dged/bufread.c b/src/dged/bufread.c new file mode 100644 index 0000000..5cb59a9 --- /dev/null +++ b/src/dged/bufread.c @@ -0,0 +1,138 @@ +#include "bufread.h" + +#include +#include +#include +#include + +struct bufread { + uint8_t *buf; + size_t capacity; + size_t read_pos; + size_t write_pos; + int fd; + bool empty; +}; + +struct bufread *bufread_create(int fd, size_t capacity) { + struct bufread *br = (struct bufread *)calloc(1, sizeof(struct bufread)); + br->buf = calloc(capacity, 1); + br->capacity = capacity; + br->read_pos = 0; + br->write_pos = 0; + br->empty = true; + br->fd = fd; + + return br; +} + +void bufread_destroy(struct bufread *br) { + free(br->buf); + br->buf = NULL; + br->capacity = 0; + br->read_pos = 0; + br->write_pos = 0; + br->empty = true; + br->fd = -1; + + free(br); +} + +static ssize_t fill(struct bufread *br) { + ssize_t rd = 0, ret = 0; + + // special case for empty ring buffer + // in this case, reset read and write pos to beginning. + if (br->empty) { + if ((ret = read(br->fd, br->buf, br->capacity)) < 0) { + return ret; + } + + rd = ret; + br->read_pos = 0; + br->write_pos = 0; + br->empty = false; + + return rd; + } + + size_t space_after = + br->read_pos < br->write_pos ? br->capacity - br->write_pos : 0; + if (space_after > 0) { + if ((ret = read(br->fd, &br->buf[br->write_pos], space_after)) < 0) { + return ret; + } + } + + br->empty = false; + rd += ret; + + // wrap around to see if there is more space + br->write_pos = 0; + size_t space_before = br->read_pos; + if (space_before > 0) { + if ((ret = read(br->fd, &br->buf[br->write_pos], space_before)) < 0) { + return ret; + } + } + + br->write_pos = br->read_pos; + + return rd + ret; +} + +static void consume(struct bufread *br, size_t amount) { + br->read_pos = (br->read_pos + amount) % br->capacity; + if (br->read_pos == br->write_pos) { + br->empty = true; + } +} + +static size_t available(struct bufread *br) { + if (br->write_pos > br->read_pos) { + return br->write_pos - br->read_pos; + } else if (br->write_pos < br->read_pos) { + return br->write_pos + (br->capacity - br->read_pos); + } + + /* read == write, either empty or full */ + return br->empty ? 0 : br->capacity; +} + +ssize_t bufread_read(struct bufread *br, uint8_t *buf, size_t count) { + // for read request larger than the internal buffer + // and an empty internal buffer, just go to the + // underlying source + if (br->empty && count >= br->capacity) { + return read(br->fd, buf, count); + } + + if (available(br) < count) { + ssize_t fill_res = 0; + if ((fill_res = fill(br)) <= 0) { + return fill_res; + } + } + + // read (at most) to end + size_t to_read = 0, rd = 0; + to_read = (br->read_pos < br->write_pos ? br->write_pos : br->capacity) - + br->read_pos; + to_read = to_read > count ? count : to_read; + + memcpy(buf, &br->buf[br->read_pos], to_read); + rd += to_read; + consume(br, to_read); + + // did we wrap around and have things left to read? + if (br->read_pos == 0 && !br->empty && rd < count) { + to_read = br->write_pos; + to_read = to_read > count ? count : to_read; + + memcpy(buf, br->buf, to_read); + rd += to_read; + consume(br, to_read); + } + + return rd; +} diff --git a/src/dged/bufread.h b/src/dged/bufread.h new file mode 100644 index 0000000..11a18ff --- /dev/null +++ b/src/dged/bufread.h @@ -0,0 +1,13 @@ +#ifndef _BUFREAD_H +#define _BUFREAD_H + +#include +#include +#include + +struct bufread; +struct bufread *bufread_create(int fd, size_t capacity); +void bufread_destroy(struct bufread *br); +ssize_t bufread_read(struct bufread *br, uint8_t *buf, size_t count); + +#endif diff --git a/src/dged/json.c b/src/dged/json.c index a8c3fed..3dafa46 100644 --- a/src/dged/json.c +++ b/src/dged/json.c @@ -322,9 +322,7 @@ uint64_t json_len(struct json_object *obj) { return HASHMAP_SIZE(&obj->members); } -uint64_t json_empty(struct json_object *obj) { - return json_len(obj) == 0; -} +bool json_empty(struct json_object *obj) { return json_len(obj) == 0; } bool json_contains(struct json_object *obj, struct s8 key) { // TODO: get rid of alloc @@ -346,3 +344,14 @@ struct json_value *json_get(struct json_object *obj, struct s8 key) { return result; } + +void json_set(struct json_object *obj, struct s8 key_, struct json_value val) { + // TODO: get rid of alloc + char *k = s8tocstr(key_); + uint32_t hash = 0; + HASHMAP_INSERT(&obj->members, struct json_object_member, k, val, hash); + + (void)hash; + (void)key; + free(k); +} diff --git a/src/dged/json.h b/src/dged/json.h index ced0f73..d686538 100644 --- a/src/dged/json.h +++ b/src/dged/json.h @@ -61,7 +61,7 @@ void json_destroy(struct json_value *value); * * @returns True if @ref obj is empty, false otherwise. */ -void json_empty(struct json_object *obj); +bool json_empty(struct json_object *obj); /** * Return the number of members in a JSON object. @@ -93,6 +93,15 @@ bool json_contains(struct json_object *obj, struct s8 key); */ struct json_value *json_get(struct json_object *obj, struct s8 key); +/** + * Set a value in a JSON object. + * + * @param [in] obj The JSON object to set in. + * @param [in] key The key of the value to set. + * @param [in] value The JSON value to set. + */ +void json_set(struct json_object *obj, struct s8 key, struct json_value val); + /** * Get the length of a JSON array. * diff --git a/src/dged/jsonrpc.c b/src/dged/jsonrpc.c new file mode 100644 index 0000000..864b839 --- /dev/null +++ b/src/dged/jsonrpc.c @@ -0,0 +1,25 @@ +#include "jsonrpc.h" + +#include + +struct jsonrpc_request jsonrpc_request_create(struct json_value id, + struct s8 method, + struct json_object *params) { + return (struct jsonrpc_request){ + .id = id, + .method = method, + .params = params, + }; +} + +struct jsonrpc_response jsonrpc_parse_response(const uint8_t *buf, + uint64_t size) { + (void)buf; + (void)size; + return (struct jsonrpc_response){}; +} + +struct s8 jsonrpc_request_to_string(const struct jsonrpc_request *request) { + (void)request; + return (struct s8){.l = 0, .s = NULL}; +} diff --git a/src/dged/jsonrpc.h b/src/dged/jsonrpc.h index ad2b0cc..835e602 100644 --- a/src/dged/jsonrpc.h +++ b/src/dged/jsonrpc.h @@ -1,6 +1,8 @@ #ifndef _JSONRPC_H #define _JSONRPC_H +#include + #include "json.h" #include "s8.h" @@ -10,6 +12,12 @@ struct jsonrpc_request { struct json_object *params; }; +struct jsonrpc_error { + int code; + struct s8 message; + struct json_value data; +}; + struct jsonrpc_response { struct json_value id; bool ok; @@ -19,14 +27,11 @@ struct jsonrpc_response { } value; }; -struct jsonrpc_error { - int code; - struct s8 message; - struct json_value data; -}; - -struct jsonrpc_request jsonrpc_request_create(struct s8 method, struct json_object *params); -struct jsonrpc_response jsonrpc_parse_response(const uint8_t *buf, uint64_t size); -struct s8 jsonrpc_request_to_string(const struct jsonprc_request *request); +struct jsonrpc_request jsonrpc_request_create(struct json_value id, + struct s8 method, + struct json_object *params); +struct jsonrpc_response jsonrpc_parse_response(const uint8_t *buf, + uint64_t size); +struct s8 jsonrpc_request_to_string(const struct jsonrpc_request *request); #endif diff --git a/src/dged/lsp.c b/src/dged/lsp.c index e73a41f..f6dac83 100644 --- a/src/dged/lsp.c +++ b/src/dged/lsp.c @@ -1,14 +1,31 @@ #include "lsp.h" #include +#include #include #include #include #include "buffer.h" +#include "jsonrpc.h" #include "process.h" #include "reactor.h" +struct pending_write { + char headers[256]; + uint64_t headers_len; + uint64_t request_id; + uint64_t written; + struct s8 payload; +}; + +struct pending_read { + uint64_t request_id; + struct s8 payload; +}; + +typedef VEC(struct pending_write) write_vec; + struct lsp { const char *name; char *const *command; @@ -19,6 +36,11 @@ struct lsp { uint32_t stdin_event; uint32_t stdout_event; uint32_t stderr_event; + + request_id current_id; + + write_vec writes; + VEC(struct pending_read) reads; }; struct lsp *lsp_create(char *const command[], struct reactor *reactor, @@ -65,6 +87,7 @@ struct lsp *lsp_create(char *const command[], struct reactor *reactor, lsp->stdin_event = -1; lsp->stdout_event = -1; lsp->stderr_event = -1; + lsp->current_id = 0; return lsp; } @@ -109,6 +132,56 @@ uint32_t lsp_update(struct lsp *lsp, struct lsp_response **responses, } } + // write pending requests + if (reactor_poll_event(lsp->reactor, lsp->stdin_event)) { + VEC_FOR_EACH(&lsp->writes, struct pending_write * w) { + ssize_t written = 0; + ssize_t to_write = 0; + if (w->written < w->headers_len) { + to_write = w->headers_len - w->written; + written = write(lsp->process->stdin, w->headers + w->written, to_write); + } else { + to_write = w->payload.l + w->headers_len - w->written; + written = write(lsp->process->stdin, w->payload.s, to_write); + + // we have written everything we want, + // "promote" this to a read request. + if (to_write == written) { + VEC_APPEND(&lsp->reads, struct pending_read * r); + r->request_id = w->request_id; + } + } + + // did an error occur + if (written < 0) { + if (written != EAGAIN && written != EWOULDBLOCK) { + // TODO: log error somehow + } + goto cleanup_writes; + } else { + w->written += written; + } + } + } + +cleanup_writes: + /* lsp->writes = filter(&lsp->writes, x: x.written < x.payload.l + + * x.headers_len) */ + write_vec writes = lsp->writes; + VEC_INIT(&lsp->writes, VEC_SIZE(&writes)); + + VEC_FOR_EACH(&writes, struct pending_write * w) { + if (w->written < w->payload.l + w->headers_len) { + // copying 256 bytes, goodbye vaccuum tubes... + VEC_PUSH(&lsp->writes, *w); + } + } + VEC_DESTROY(&writes); + + // process incoming responses + if (reactor_poll_event(lsp->reactor, lsp->stdout_event)) { + } + return 0; } @@ -125,6 +198,12 @@ int lsp_start_server(struct lsp *lsp) { memcpy(lsp->process, &p, sizeof(struct process)); lsp->stderr_event = reactor_register_interest( lsp->reactor, lsp->process->stderr, ReadInterest); + lsp->stdin_event = reactor_register_interest( + lsp->reactor, lsp->process->stdin, WriteInterest); + + if (lsp->stdin_event == (uint32_t)-1) { + return -2; + } return 0; } @@ -161,3 +240,25 @@ uint64_t lsp_server_pid(const struct lsp *lsp) { } const char *lsp_server_name(const struct lsp *lsp) { return lsp->name; } + +request_id lsp_request(struct lsp *lsp, struct lsp_request request) { + struct json_value js_id = { + .type = Json_Number, + .value.number = (double)lsp->current_id, + .parent = NULL, + }; + + struct jsonrpc_request req = + jsonrpc_request_create(js_id, request.method, request.params); + struct s8 payload = jsonrpc_request_to_string(&req); + + VEC_APPEND(&lsp->writes, struct pending_write * w); + w->headers_len = + snprintf(w->headers, 256, "Content-Length: %d\r\n\r\n", payload.l); + w->request_id = lsp->current_id; + w->payload = payload; + + ++lsp->current_id; + + return w->request_id; +} diff --git a/src/dged/lsp.h b/src/dged/lsp.h index 3fd6285..99bf619 100644 --- a/src/dged/lsp.h +++ b/src/dged/lsp.h @@ -1,6 +1,7 @@ #ifndef _LSP_H #define _LSP_H +#include "json.h" #include "location.h" #include "s8.h" @@ -8,17 +9,25 @@ struct buffer; struct lsp; struct reactor; -typedef uint32_t request_id; +typedef uint64_t request_id; + +struct lsp_response_error {}; struct lsp_response { request_id id; + bool ok; union payload_data { - void *result; - struct s8 error; + struct json_value result; + struct lsp_response_error error; } payload; }; +struct lsp_request { + struct s8 method; + struct json_object *params; +}; + struct lsp_notification { int something; }; @@ -27,30 +36,6 @@ struct lsp_client { void (*log_message)(int type, struct s8 msg); }; -struct hover { - struct s8 contents; - - bool has_range; - struct region *range; -}; - -struct text_doc_item { - struct s8 uri; - struct s8 language_id; - uint32_t version; - struct s8 text; -}; - -struct text_doc_position { - struct s8 uri; - struct location pos; -}; - -struct initialize_params { - struct s8 client_name; - struct s8 client_version; -}; - // lifecycle functions struct lsp *lsp_create(char *const command[], struct reactor *reactor, struct buffer *stderr_buffer, @@ -68,8 +53,6 @@ uint64_t lsp_server_pid(const struct lsp *lsp); const char *lsp_server_name(const struct lsp *lsp); // protocol functions -void lsp_initialize(struct lsp *lsp, struct initialize_params); -void lsp_did_open_document(struct lsp *lsp, struct text_doc_item document); -request_id lsp_hover(struct lsp *lsp, struct text_doc_position); +request_id lsp_request(struct lsp *lsp, struct lsp_request request); #endif diff --git a/test/bufread.c b/test/bufread.c new file mode 100644 index 0000000..d477946 --- /dev/null +++ b/test/bufread.c @@ -0,0 +1,63 @@ +#ifdef LINUX +#define _GNU_SOURCE +#endif + +#include "assert.h" +#include "test.h" + +#include "dged/bufread.h" + +#include +#include + +#ifdef LINUX +#include +#endif + +static void test_read(void) { +#ifdef LINUX + int memfd = memfd_create("bufread-test", 0); + ASSERT(memfd >= 0, "Failed to create memfd"); +#endif + for (int i = 0; i < 256; ++i) { + int a = write(memfd, (uint8_t *)&i, 1); + (void)a; + } + lseek(memfd, 0, SEEK_SET); + + struct bufread *br = bufread_create(memfd, 128); + uint8_t buf[32]; + ssize_t read = bufread_read(br, buf, 32); + ASSERT(read > 0, "Expected to be able to read"); + for (int i = 0; i < 32; ++i) { + ASSERT(i == buf[i], "Expected buffer to be monotonically increasing"); + } + bufread_read(br, buf, 32); + bufread_read(br, buf, 32); + bufread_read(br, buf, 32); + + read = bufread_read(br, buf, 32); + ASSERT(read > 0, "Expected to be able to read"); + for (int i = 0; i < 32; ++i) { + ASSERT((i + 128) == buf[i], + "Expected buffer to be monotonically increasing"); + } + bufread_destroy(br); +} + +void test_empty_read(void) { +#ifdef LINUX + int memfd = memfd_create("bufread-test", 0); + ASSERT(memfd >= 0, "Failed to create memfd"); +#endif + struct bufread *br = bufread_create(memfd, 128); + uint8_t buf[32]; + ssize_t read = bufread_read(br, buf, 32); + ASSERT(read == 0, "Expected to not be able to read from empty stream"); + bufread_destroy(br); +} + +void run_bufread_tests(void) { + run_test(test_read); + run_test(test_empty_read); +} diff --git a/test/main.c b/test/main.c index 29e031f..0fb1fd6 100644 --- a/test/main.c +++ b/test/main.c @@ -50,6 +50,9 @@ int main(void) { printf("\n 🎁 \x1b[1;36mRunning container tests...\x1b[0m\n"); run_container_tests(); + printf("\n 🐃 \x1b[1;36mRunning bufread tests...\x1b[0m\n"); + run_bufread_tests(); + #if defined(LSP_ENABLED) printf("\n 📃 \x1b[1;36mRunning JSON tests...\x1b[0m\n"); run_json_tests(); diff --git a/test/test.h b/test/test.h index 5b9cafc..bab1532 100644 --- a/test/test.h +++ b/test/test.h @@ -20,5 +20,6 @@ void run_minibuffer_tests(void); void run_settings_tests(void); void run_container_tests(void); void run_json_tests(void); +void run_bufread_tests(void); #endif