diff options
author | pommicket <pommicket@gmail.com> | 2022-12-22 18:28:15 -0500 |
---|---|---|
committer | pommicket <pommicket@gmail.com> | 2022-12-22 18:28:15 -0500 |
commit | 2667c71e71d77ecade1142c133ed7181ce38c664 (patch) | |
tree | ef65d210d12f9c80e5a6a6965d315810d33375f8 | |
parent | 6205e83793eff547d404fa5bde747076a1191893 (diff) |
reorganize lsp code
-rw-r--r-- | lsp-parse.c | 329 | ||||
-rw-r--r-- | lsp-write.c (renamed from lsp-write-request.c) | 0 | ||||
-rw-r--r-- | lsp.c | 345 | ||||
-rw-r--r-- | main.c | 1 |
4 files changed, 339 insertions, 336 deletions
diff --git a/lsp-parse.c b/lsp-parse.c new file mode 100644 index 0000000..6167cc8 --- /dev/null +++ b/lsp-parse.c @@ -0,0 +1,329 @@ +static WarnUnusedResult bool lsp_expect_type(LSP *lsp, JSONValue value, JSONValueType type, const char *what) { + if (value.type != type) { + lsp_set_error(lsp, "Expected %s for %s, got %s", + json_type_to_str(type), + what, + json_type_to_str(value.type)); + return false; + } + return true; +} + +static WarnUnusedResult bool lsp_expect_object(LSP *lsp, JSONValue value, const char *what) { + return lsp_expect_type(lsp, value, JSON_OBJECT, what); +} + +static WarnUnusedResult bool lsp_expect_array(LSP *lsp, JSONValue value, const char *what) { + return lsp_expect_type(lsp, value, JSON_ARRAY, what); +} + +static WarnUnusedResult bool lsp_expect_string(LSP *lsp, JSONValue value, const char *what) { + return lsp_expect_type(lsp, value, JSON_STRING, what); +} + +static WarnUnusedResult bool lsp_expect_number(LSP *lsp, JSONValue value, const char *what) { + return lsp_expect_type(lsp, value, JSON_NUMBER, what); +} + +static LSPString lsp_response_add_json_string(LSPResponse *response, const JSON *json, JSONString string) { + u32 offset = arr_len(response->string_data); + arr_set_len(response->string_data, offset + string.len + 1); + json_string_get(json, string, response->string_data + offset, string.len + 1); + return (LSPString){ + .offset = offset + }; +} + +static int completion_qsort_cmp(void *context, const void *av, const void *bv) { + const LSPResponse *response = context; + const LSPCompletionItem *a = av, *b = bv; + const char *a_sort_text = lsp_response_string(response, a->sort_text); + const char *b_sort_text = lsp_response_string(response, b->sort_text); + int sort_text_cmp = strcmp(a_sort_text, b_sort_text); + if (sort_text_cmp != 0) + return sort_text_cmp; + // for some reason, rust-analyzer outputs identical sortTexts + // i have no clue what that means. + // the LSP "specification" is not very specific. + // we'll sort by label in this case. + // this is what VSCode seems to do. + // i hate microsofot. + const char *a_label = lsp_response_string(response, a->label); + const char *b_label = lsp_response_string(response, b->label); + return strcmp(a_label, b_label); +} + +static bool parse_position(LSP *lsp, const JSON *json, JSONValue pos_value, LSPPosition *pos) { + if (!lsp_expect_object(lsp, pos_value, "document position")) + return false; + JSONObject pos_object = pos_value.val.object; + JSONValue line = json_object_get(json, pos_object, "line"); + JSONValue character = json_object_get(json, pos_object, "character"); + if (!lsp_expect_number(lsp, line, "document line number") + || !lsp_expect_number(lsp, character, "document column number")) + return false; + pos->line = (u32)line.val.number; + pos->character = (u32)line.val.number; + return true; +} + +static bool parse_range(LSP *lsp, const JSON *json, JSONValue range_value, LSPRange *range) { + if (!lsp_expect_object(lsp, range_value, "document range")) + return false; + JSONObject range_object = range_value.val.object; + JSONValue start = json_object_get(json, range_object, "start"); + JSONValue end = json_object_get(json, range_object, "end"); + return parse_position(lsp, json, start, &range->start) + && parse_position(lsp, json, end, &range->end); +} + +static bool parse_completion(LSP *lsp, const JSON *json, LSPResponse *response) { + // deal with textDocument/completion response. + // result: CompletionItem[] | CompletionList | null + LSPResponseCompletion *completion = &response->data.completion; + + JSONValue result = json_get(json, "result"); + JSONValue items_value = {0}; + switch (result.type) { + case JSON_NULL: + // no completions + return true; + case JSON_ARRAY: + items_value = result; + break; + case JSON_OBJECT: + items_value = json_object_get(json, result.val.object, "items"); + break; + default: + lsp_set_error(lsp, "Weird result type for textDocument/completion response: %s.", json_type_to_str(result.type)); + break; + } + + if (!lsp_expect_array(lsp, items_value, "completion list")) + return false; + + JSONArray items = items_value.val.array; + + arr_set_len(completion->items, items.len); + + for (u32 i = 0; i < items.len; ++i) { + LSPCompletionItem *item = &completion->items[i]; + + JSONValue item_value = json_array_get(json, items, i); + if (!lsp_expect_object(lsp, item_value, "completion list")) + return false; + JSONObject item_object = item_value.val.object; + + JSONValue label_value = json_object_get(json, item_object, "label"); + if (!lsp_expect_string(lsp, label_value, "completion label")) + return false; + JSONString label = label_value.val.string; + item->label = lsp_response_add_json_string(response, json, label); + + // defaults + item->sort_text = item->label; + item->filter_text = item->label; + item->text_edit = (LSPTextEdit) { + .type = LSP_TEXT_EDIT_PLAIN, + .at_cursor = true, + .range = {0}, + .new_text = item->label + }; + + JSONValue sort_text_value = json_object_get(json, item_object, "sortText"); + if (sort_text_value.type == JSON_STRING) { + // LSP allows using a different string for sorting. + item->sort_text = lsp_response_add_json_string(response, + json, sort_text_value.val.string); + } + + JSONValue filter_text_value = json_object_get(json, item_object, "filterText"); + if (filter_text_value.type == JSON_STRING) { + // LSP allows using a different string for filtering. + item->filter_text = lsp_response_add_json_string(response, + json, filter_text_value.val.string); + } + + JSONValue text_type_value = json_object_get(json, item_object, "insertTextFormat"); + if (text_type_value.type == JSON_NUMBER) { + double type = text_type_value.val.number; + if (type != LSP_TEXT_EDIT_PLAIN && type != LSP_TEXT_EDIT_SNIPPET) { + lsp_set_error(lsp, "Bad InsertTextFormat: %g", type); + return false; + } + item->text_edit.type = (LSPTextEditType)type; + } + + // @TODO: detail + + // @TODO(eventually): additionalTextEdits + // (try to find a case where this comes up) + + // what should happen when this completion is selected? + JSONValue text_edit_value = json_object_get(json, item_object, "textEdit"); + if (text_edit_value.type == JSON_OBJECT) { + JSONObject text_edit = text_edit_value.val.object; + item->text_edit.at_cursor = false; + + JSONValue range = json_object_get(json, text_edit, "range"); + if (!parse_range(lsp, json, range, &item->text_edit.range)) + return false; + + JSONValue new_text_value = json_object_get(json, text_edit, "newText"); + if (!lsp_expect_string(lsp, new_text_value, "completion newText")) + return false; + item->text_edit.new_text = lsp_response_add_json_string(response, + json, new_text_value.val.string); + } else { + // not using textEdit. check insertText. + JSONValue insert_text_value = json_object_get(json, item_object, "insertText"); + if (insert_text_value.type == JSON_STRING) { + // string which will be inserted if this completion is selected + item->text_edit.new_text = lsp_response_add_json_string(response, + json, insert_text_value.val.string); + } + } + + } + + qsort_with_context(completion->items, items.len, sizeof *completion->items, + completion_qsort_cmp, response); + + return true; +} + +static bool parse_server2client_request(LSP *lsp, JSON *json, LSPRequest *request) { + JSONValue method_value = json_get(json, "method"); + if (!lsp_expect_string(lsp, method_value, "request method")) + return false; + + char method[64] = {0}; + json_string_get(json, method_value.val.string, method, sizeof method); + + if (streq(method, "window/showMessage")) { + request->type = LSP_REQUEST_SHOW_MESSAGE; + goto window_message; + } else if (streq(method, "window/logMessage")) { + request->type = LSP_REQUEST_LOG_MESSAGE; + window_message:; + JSONValue type = json_get(json, "params.type"); + JSONValue message = json_get(json, "params.message"); + if (!lsp_expect_number(lsp, type, "MessageType")) + return false; + if (!lsp_expect_string(lsp, message, "message string")) + return false; + + int mtype = (int)type.val.number; + if (mtype < 1 || mtype > 4) { + lsp_set_error(lsp, "Bad MessageType: %g", type.val.number); + return false; + } + + LSPRequestMessage *m = &request->data.message; + m->type = (LSPWindowMessageType)mtype; + m->message = json_string_get_alloc(json, message.val.string); + return true; + } else if (str_has_prefix(method, "$/")) { + // we can safely ignore this + } else { + lsp_set_error(lsp, "Unrecognized request method: %s", method); + } + return false; +} + + +static void process_message(LSP *lsp, JSON *json) { + + #if 0 + printf("\x1b[3m"); + json_debug_print(json); + printf("\x1b[0m\n"); + #endif + JSONValue id_value = json_get(json, "id"); + + // get the request associated with this (if any) + LSPRequest response_to = {0}; + if (id_value.type == JSON_NUMBER) { + u64 id = (u64)id_value.val.number; + arr_foreach_ptr(lsp->requests_sent, LSPRequest, req) { + if (req->id == id) { + response_to = *req; + arr_remove(lsp->requests_sent, (u32)(req - lsp->requests_sent)); + break; + } + } + } + + JSONValue error = json_get(json, "error.message"); + if (error.type == JSON_STRING) { + char err[256] = {0}; + json_string_get(json, error.val.string, err, sizeof err);; + + if (streq(err, "waiting for cargo metadata or cargo check")) { + // fine. be that way. i'll resend the goddamn request. + // i'll keep bombarding you with requests. + // maybe next time you should abide by the standard and only send an initialize response when youre actually ready to handle my requests. fuck you. + if (response_to.type) { + lsp_send_request(lsp, &response_to); + // don't free + memset(&response_to, 0, sizeof response_to); + } + } else { + lsp_set_error(lsp, "%s", err); + } + goto ret; + } + + JSONValue result = json_get(json, "result"); + if (result.type != JSON_UNDEFINED) { + if (response_to.type == LSP_REQUEST_INITIALIZE) { + // it's the response to our initialize request! + // let's send back an "initialized" request (notification) because apparently + // that's something we need to do. + LSPRequest initialized = { + .type = LSP_REQUEST_INITIALIZED, + .data = {0}, + }; + write_request(lsp, &initialized); + // we can now send requests which have nothing to do with initialization + lsp->initialized = true; + } else { + LSPResponse response = {0}; + bool success = false; + response.request = response_to; + switch (response_to.type) { + case LSP_REQUEST_COMPLETION: + success = parse_completion(lsp, json, &response); + break; + default: + // it's some response we don't care about + break; + } + if (success) { + SDL_LockMutex(lsp->messages_mutex); + LSPMessage *message = arr_addp(lsp->messages); + message->type = LSP_RESPONSE; + message->u.response = response; + SDL_UnlockMutex(lsp->messages_mutex); + response_to.type = 0; // don't free + } else { + lsp_response_free(&response); + } + } + } else if (json_has(json, "method")) { + LSPRequest request = {0}; + if (parse_server2client_request(lsp, json, &request)) { + SDL_LockMutex(lsp->messages_mutex); + LSPMessage *message = arr_addp(lsp->messages); + message->type = LSP_REQUEST; + message->u.request = request; + SDL_UnlockMutex(lsp->messages_mutex); + } + } else { + lsp_set_error(lsp, "Bad message from server (no result, no method)."); + } + ret: + lsp_request_free(&response_to); + json_free(json); + +} diff --git a/lsp-write-request.c b/lsp-write.c index 28b19d3..28b19d3 100644 --- a/lsp-write-request.c +++ b/lsp-write.c @@ -1,5 +1,13 @@ static void lsp_request_free(LSPRequest *r); -#include "lsp-write-request.c" +static void lsp_response_free(LSPResponse *r); + +#define lsp_set_error(lsp, ...) do {\ + SDL_LockMutex(lsp->error_mutex);\ + strbuf_printf(lsp->error, __VA_ARGS__);\ + SDL_UnlockMutex(lsp->error_mutex);\ + } while (0) +#include "lsp-write.c" +#include "lsp-parse.c" bool lsp_get_error(LSP *lsp, char *error, size_t error_size, bool clear) { bool has_err = false; @@ -13,12 +21,6 @@ bool lsp_get_error(LSP *lsp, char *error, size_t error_size, bool clear) { return has_err; } -#define lsp_set_error(lsp, ...) do {\ - SDL_LockMutex(lsp->error_mutex);\ - strbuf_printf(lsp->error, __VA_ARGS__);\ - SDL_UnlockMutex(lsp->error_mutex);\ - } while (0) - static void lsp_document_change_event_free(LSPDocumentChangeEvent *event) { free(event->text); @@ -75,32 +77,6 @@ void lsp_message_free(LSPMessage *message) { memset(message, 0, sizeof *message); } -static WarnUnusedResult bool lsp_expect_type(LSP *lsp, JSONValue value, JSONValueType type, const char *what) { - if (value.type != type) { - lsp_set_error(lsp, "Expected %s for %s, got %s", - json_type_to_str(type), - what, - json_type_to_str(value.type)); - return false; - } - return true; -} - -static WarnUnusedResult bool lsp_expect_object(LSP *lsp, JSONValue value, const char *what) { - return lsp_expect_type(lsp, value, JSON_OBJECT, what); -} - -static WarnUnusedResult bool lsp_expect_array(LSP *lsp, JSONValue value, const char *what) { - return lsp_expect_type(lsp, value, JSON_ARRAY, what); -} - -static WarnUnusedResult bool lsp_expect_string(LSP *lsp, JSONValue value, const char *what) { - return lsp_expect_type(lsp, value, JSON_STRING, what); -} - -static WarnUnusedResult bool lsp_expect_number(LSP *lsp, JSONValue value, const char *what) { - return lsp_expect_type(lsp, value, JSON_NUMBER, what); -} // figure out if data begins with a complete LSP response. static bool has_response(const char *data, size_t data_len, u64 *p_offset, u64 *p_size) { @@ -125,314 +101,11 @@ void lsp_send_request(LSP *lsp, const LSPRequest *request) { SDL_UnlockMutex(lsp->requests_mutex); } -static bool parse_server2client_request(LSP *lsp, JSON *json, LSPRequest *request) { - JSONValue method_value = json_get(json, "method"); - if (!lsp_expect_string(lsp, method_value, "request method")) - return false; - - char method[64] = {0}; - json_string_get(json, method_value.val.string, method, sizeof method); - - if (streq(method, "window/showMessage")) { - request->type = LSP_REQUEST_SHOW_MESSAGE; - goto window_message; - } else if (streq(method, "window/logMessage")) { - request->type = LSP_REQUEST_LOG_MESSAGE; - window_message:; - JSONValue type = json_get(json, "params.type"); - JSONValue message = json_get(json, "params.message"); - if (!lsp_expect_number(lsp, type, "MessageType")) - return false; - if (!lsp_expect_string(lsp, message, "message string")) - return false; - - int mtype = (int)type.val.number; - if (mtype < 1 || mtype > 4) { - lsp_set_error(lsp, "Bad MessageType: %g", type.val.number); - return false; - } - - LSPRequestMessage *m = &request->data.message; - m->type = (LSPWindowMessageType)mtype; - m->message = json_string_get_alloc(json, message.val.string); - return true; - } else if (str_has_prefix(method, "$/")) { - // we can safely ignore this - } else { - lsp_set_error(lsp, "Unrecognized request method: %s", method); - } - return false; -} - const char *lsp_response_string(const LSPResponse *response, LSPString string) { assert(string.offset < arr_len(response->string_data)); return &response->string_data[string.offset]; } -static LSPString lsp_response_add_json_string(LSPResponse *response, const JSON *json, JSONString string) { - u32 offset = arr_len(response->string_data); - arr_set_len(response->string_data, offset + string.len + 1); - json_string_get(json, string, response->string_data + offset, string.len + 1); - return (LSPString){ - .offset = offset - }; -} - -static int completion_qsort_cmp(void *context, const void *av, const void *bv) { - const LSPResponse *response = context; - const LSPCompletionItem *a = av, *b = bv; - const char *a_sort_text = lsp_response_string(response, a->sort_text); - const char *b_sort_text = lsp_response_string(response, b->sort_text); - int sort_text_cmp = strcmp(a_sort_text, b_sort_text); - if (sort_text_cmp != 0) - return sort_text_cmp; - // for some reason, rust-analyzer outputs identical sortTexts - // i have no clue what that means. - // the LSP "specification" is not very specific. - // we'll sort by label in this case. - // this is what VSCode seems to do. - // i hate microsofot. - const char *a_label = lsp_response_string(response, a->label); - const char *b_label = lsp_response_string(response, b->label); - return strcmp(a_label, b_label); -} - -static bool parse_position(LSP *lsp, const JSON *json, JSONValue pos_value, LSPPosition *pos) { - if (!lsp_expect_object(lsp, pos_value, "document position")) - return false; - JSONObject pos_object = pos_value.val.object; - JSONValue line = json_object_get(json, pos_object, "line"); - JSONValue character = json_object_get(json, pos_object, "character"); - if (!lsp_expect_number(lsp, line, "document line number") - || !lsp_expect_number(lsp, character, "document column number")) - return false; - pos->line = (u32)line.val.number; - pos->character = (u32)line.val.number; - return true; -} - -static bool parse_range(LSP *lsp, const JSON *json, JSONValue range_value, LSPRange *range) { - if (!lsp_expect_object(lsp, range_value, "document range")) - return false; - JSONObject range_object = range_value.val.object; - JSONValue start = json_object_get(json, range_object, "start"); - JSONValue end = json_object_get(json, range_object, "end"); - return parse_position(lsp, json, start, &range->start) - && parse_position(lsp, json, end, &range->end); -} - -static bool parse_completion(LSP *lsp, const JSON *json, LSPResponse *response) { - // deal with textDocument/completion response. - // result: CompletionItem[] | CompletionList | null - LSPResponseCompletion *completion = &response->data.completion; - - JSONValue result = json_get(json, "result"); - JSONValue items_value = {0}; - switch (result.type) { - case JSON_NULL: - // no completions - return true; - case JSON_ARRAY: - items_value = result; - break; - case JSON_OBJECT: - items_value = json_object_get(json, result.val.object, "items"); - break; - default: - lsp_set_error(lsp, "Weird result type for textDocument/completion response: %s.", json_type_to_str(result.type)); - break; - } - - if (!lsp_expect_array(lsp, items_value, "completion list")) - return false; - - JSONArray items = items_value.val.array; - - arr_set_len(completion->items, items.len); - - for (u32 i = 0; i < items.len; ++i) { - LSPCompletionItem *item = &completion->items[i]; - - JSONValue item_value = json_array_get(json, items, i); - if (!lsp_expect_object(lsp, item_value, "completion list")) - return false; - JSONObject item_object = item_value.val.object; - - JSONValue label_value = json_object_get(json, item_object, "label"); - if (!lsp_expect_string(lsp, label_value, "completion label")) - return false; - JSONString label = label_value.val.string; - item->label = lsp_response_add_json_string(response, json, label); - - // defaults - item->sort_text = item->label; - item->filter_text = item->label; - item->text_edit = (LSPTextEdit) { - .type = LSP_TEXT_EDIT_PLAIN, - .at_cursor = true, - .range = {0}, - .new_text = item->label - }; - - JSONValue sort_text_value = json_object_get(json, item_object, "sortText"); - if (sort_text_value.type == JSON_STRING) { - // LSP allows using a different string for sorting. - item->sort_text = lsp_response_add_json_string(response, - json, sort_text_value.val.string); - } - - JSONValue filter_text_value = json_object_get(json, item_object, "filterText"); - if (filter_text_value.type == JSON_STRING) { - // LSP allows using a different string for filtering. - item->filter_text = lsp_response_add_json_string(response, - json, filter_text_value.val.string); - } - - JSONValue text_type_value = json_object_get(json, item_object, "insertTextFormat"); - if (text_type_value.type == JSON_NUMBER) { - double type = text_type_value.val.number; - if (type != LSP_TEXT_EDIT_PLAIN && type != LSP_TEXT_EDIT_SNIPPET) { - lsp_set_error(lsp, "Bad InsertTextFormat: %g", type); - return false; - } - item->text_edit.type = (LSPTextEditType)type; - } - - // @TODO: detail - - // @TODO(eventually): additionalTextEdits - // (try to find a case where this comes up) - - // what should happen when this completion is selected? - JSONValue text_edit_value = json_object_get(json, item_object, "textEdit"); - if (text_edit_value.type == JSON_OBJECT) { - JSONObject text_edit = text_edit_value.val.object; - item->text_edit.at_cursor = false; - - JSONValue range = json_object_get(json, text_edit, "range"); - if (!parse_range(lsp, json, range, &item->text_edit.range)) - return false; - - JSONValue new_text_value = json_object_get(json, text_edit, "newText"); - if (!lsp_expect_string(lsp, new_text_value, "completion newText")) - return false; - item->text_edit.new_text = lsp_response_add_json_string(response, - json, new_text_value.val.string); - } else { - // not using textEdit. check insertText. - JSONValue insert_text_value = json_object_get(json, item_object, "insertText"); - if (insert_text_value.type == JSON_STRING) { - // string which will be inserted if this completion is selected - item->text_edit.new_text = lsp_response_add_json_string(response, - json, insert_text_value.val.string); - } - } - - } - - qsort_with_context(completion->items, items.len, sizeof *completion->items, - completion_qsort_cmp, response); - - return true; -} - - -static void process_message(LSP *lsp, JSON *json) { - - #if 0 - printf("\x1b[3m"); - json_debug_print(json); - printf("\x1b[0m\n"); - #endif - JSONValue id_value = json_get(json, "id"); - - // get the request associated with this (if any) - LSPRequest response_to = {0}; - if (id_value.type == JSON_NUMBER) { - u64 id = (u64)id_value.val.number; - arr_foreach_ptr(lsp->requests_sent, LSPRequest, req) { - if (req->id == id) { - response_to = *req; - arr_remove(lsp->requests_sent, (u32)(req - lsp->requests_sent)); - break; - } - } - } - - JSONValue error = json_get(json, "error.message"); - if (error.type == JSON_STRING) { - char err[256] = {0}; - json_string_get(json, error.val.string, err, sizeof err);; - - if (streq(err, "waiting for cargo metadata or cargo check")) { - // fine. be that way. i'll resend the goddamn request. - // i'll keep bombarding you with requests. - // maybe next time you should abide by the standard and only send an initialize response when youre actually ready to handle my requests. fuck you. - if (response_to.type) { - lsp_send_request(lsp, &response_to); - // don't free - memset(&response_to, 0, sizeof response_to); - } - } else { - lsp_set_error(lsp, "%s", err); - } - goto ret; - } - - JSONValue result = json_get(json, "result"); - if (result.type != JSON_UNDEFINED) { - if (response_to.type == LSP_REQUEST_INITIALIZE) { - // it's the response to our initialize request! - // let's send back an "initialized" request (notification) because apparently - // that's something we need to do. - LSPRequest initialized = { - .type = LSP_REQUEST_INITIALIZED, - .data = {0}, - }; - write_request(lsp, &initialized); - // we can now send requests which have nothing to do with initialization - lsp->initialized = true; - } else { - LSPResponse response = {0}; - bool success = false; - response.request = response_to; - switch (response_to.type) { - case LSP_REQUEST_COMPLETION: - success = parse_completion(lsp, json, &response); - break; - default: - // it's some response we don't care about - break; - } - if (success) { - SDL_LockMutex(lsp->messages_mutex); - LSPMessage *message = arr_addp(lsp->messages); - message->type = LSP_RESPONSE; - message->u.response = response; - SDL_UnlockMutex(lsp->messages_mutex); - response_to.type = 0; // don't free - } else { - lsp_response_free(&response); - } - } - } else if (json_has(json, "method")) { - LSPRequest request = {0}; - if (parse_server2client_request(lsp, json, &request)) { - SDL_LockMutex(lsp->messages_mutex); - LSPMessage *message = arr_addp(lsp->messages); - message->type = LSP_REQUEST; - message->u.request = request; - SDL_UnlockMutex(lsp->messages_mutex); - } - } else { - lsp_set_error(lsp, "Bad message from server (no result, no method)."); - } - ret: - lsp_request_free(&response_to); - json_free(json); - -} - // receive responses/requests/notifications from LSP, up to max_size bytes. static void lsp_receive(LSP *lsp, size_t max_size) { @@ -4,6 +4,7 @@ - LSP setting - scroll through completions - figure out under which circumstances backspace should close completions + - close completions when a non-word character is typed - rename buffer->filename to buffer->path - make buffer->path NULL for untitled buffers & fix resulting mess - rust-analyzer bug reports: |