From 7185635a553d44b537d6fd1264ceedf421e114ef Mon Sep 17 00:00:00 2001 From: pommicket Date: Sat, 9 Sep 2023 23:01:55 -0400 Subject: deal with LSP servers that don't support incremental sync this was the problem with godot --- buffer.c | 61 +++++++++++++++++++++++++++++++++++++++++++++++-------------- lsp-write.c | 3 ++- lsp.c | 17 ++--------------- lsp.h | 5 +++-- main.c | 5 ++++- 5 files changed, 58 insertions(+), 33 deletions(-) diff --git a/buffer.c b/buffer.c index 9fa4ee5..ac2b532 100644 --- a/buffer.c +++ b/buffer.c @@ -1909,7 +1909,38 @@ static void buffer_send_lsp_did_change(LSP *lsp, TextBuffer *buffer, BufferPos p range.start = buffer_pos_to_lsp_position(buffer, pos); BufferPos pos_end = buffer_pos_advance(buffer, pos, nchars_deleted); range.end = buffer_pos_to_lsp_position(buffer, pos_end); - lsp_document_changed(lsp, buffer->path, range, new_text); + const char *document = buffer->path; + + if (lsp->capabilities.incremental_sync_support) { + LSPRequest request = {.type = LSP_REQUEST_DID_CHANGE}; + LSPDocumentChangeEvent change = { + .range = range, + .use_range = true, + .text = lsp_message_add_string32(&request.base, new_text), + }; + LSPRequestDidChange *c = &request.data.change; + c->document = lsp_document_id(lsp, document); + arr_add(c->changes, change); + lsp_send_request(lsp, &request); + } else { + // re-send the whole document + // needed for servers which don't have incremental sync support, + // such as godot (at time of writing) + // this isn't great performance-wise, + // but we can't just send it each frame because then some + // requests might have messed up buffer positions from the server's point of view. + LSPRequest request = {.type = LSP_REQUEST_DID_CHANGE}; + size_t len = buffer_contents_utf8(buffer, NULL); + LSPDocumentChangeEvent change = { + .use_range = false + }; + char *text = lsp_message_alloc_string(&request.base, len, &change.text); + buffer_contents_utf8(buffer, text); + LSPRequestDidChange *c = &request.data.change; + c->document = lsp_document_id(lsp, document); + arr_add(c->changes, change); + lsp_send_request(lsp, &request); + } } BufferPos buffer_insert_text_at_pos(TextBuffer *buffer, BufferPos pos, String32 str) { @@ -1932,9 +1963,7 @@ BufferPos buffer_insert_text_at_pos(TextBuffer *buffer, BufferPos pos, String32 // no text to insert return pos; } - - const u32 insertion_len = (u32)str.len; - + // create a copy of str. we need to do this to remove carriage returns and newlines in the case of line buffers char32_t str_copy[256]; char32_t *str_alloc = NULL; @@ -1968,10 +1997,7 @@ BufferPos buffer_insert_text_at_pos(TextBuffer *buffer, BufferPos pos, String32 autocomplete_close(buffer->ted); } - - LSP *lsp = buffer_lsp(buffer); - if (lsp) - buffer_send_lsp_did_change(lsp, buffer, pos, 0, str); + const String32 str_start = str; // keep this around for later if (buffer->store_undo_events) { BufferEdit *last_edit = arr_lastp(buffer->undo_history); @@ -2054,12 +2080,18 @@ BufferPos buffer_insert_text_at_pos(TextBuffer *buffer, BufferPos pos, String32 BufferPos b = {.line = line_idx, .index = index}; free(str_alloc); + + // we need to do this *after* making the change to the buffer + // because of how non-incremental syncing works. + LSP *lsp = buffer_lsp(buffer); + if (lsp) + buffer_send_lsp_did_change(lsp, buffer, pos, 0, str_start); const EditInfo info = { .pos = pos, .end = b, .chars_deleted = 0, - .chars_inserted = insertion_len, + .chars_inserted = (u32)str_start.len, }; // move diagnostics around as needed arr_foreach_ptr(buffer->diagnostics, Diagnostic, d) { @@ -2465,11 +2497,6 @@ void buffer_delete_chars_at_pos(TextBuffer *buffer, BufferPos pos, i64 nchars_) autocomplete_close(buffer->ted); } - - LSP *lsp = buffer_lsp(buffer); - if (lsp) - buffer_send_lsp_did_change(lsp, buffer, pos, nchars, (String32){0}); - if (buffer->store_undo_events) { // we need to make sure the undo history keeps track of the edit. // we will either combine it with the previous BufferEdit, or create a new @@ -2581,6 +2608,12 @@ void buffer_delete_chars_at_pos(TextBuffer *buffer, BufferPos pos, i64 nchars_) // cursor position could have been invalidated by this edit buffer_validate_cursor(buffer); + // we need to do this *after* making the change to the buffer + // because of how non-incremental syncing works. + LSP *lsp = buffer_lsp(buffer); + if (lsp) + buffer_send_lsp_did_change(lsp, buffer, pos, deletion_len, (String32){0}); + buffer_lines_modified(buffer, line_idx, line_idx); signature_help_retrigger(buffer->ted); diff --git a/lsp-write.c b/lsp-write.c index 050d784..0eed987 100644 --- a/lsp-write.c +++ b/lsp-write.c @@ -547,7 +547,8 @@ void write_request(LSP *lsp, LSPRequest *request) { arr_foreach_ptr(change->changes, LSPDocumentChangeEvent, event) { write_arr_elem(o); write_obj_start(o); - write_key_range(o, "range", event->range); + if (event->use_range) + write_key_range(o, "range", event->range); write_key_string(o, "text", lsp_request_string(request, event->text)); write_obj_end(o); } diff --git a/lsp.c b/lsp.c index 025e2be..c828bf1 100644 --- a/lsp.c +++ b/lsp.c @@ -67,7 +67,7 @@ static LSPString lsp_message_add_json_string(LSPMessageBase *message, const JSON json_string_get(json, string, dest, len + 1); return ret; } -static LSPString lsp_message_add_string32(LSPMessageBase *message, String32 string) { +LSPString lsp_message_add_string32(LSPMessageBase *message, String32 string) { LSPString ret = {0}; size_t len32 = string.len; if (len32 == 0) { @@ -438,7 +438,7 @@ static bool lsp_receive(LSP *lsp, size_t max_size) { // kind of a hack. this is needed because arr_set_len zeroes the data. arr_hdr_(lsp->received_data)->len = (u32)received_so_far; lsp->received_data[received_so_far] = '\0';// null terminate - #if 0 + #if LSP_SHOW_S2C const int limit = 1000; printf("%s%.*s%s%s\n",term_italics(stdout),limit,lsp->received_data, strlen(lsp->received_data) > (size_t)limit ? "..." : "", @@ -768,19 +768,6 @@ void lsp_free(LSP *lsp) { free(lsp); } -void lsp_document_changed(LSP *lsp, const char *document, LSPRange range, String32 new_text) { - // @TODO(optimization, eventually): batch changes (using the contentChanges array) - LSPRequest request = {.type = LSP_REQUEST_DID_CHANGE}; - LSPDocumentChangeEvent change = { - .range = range, - .text = lsp_message_add_string32(&request.base, new_text), - }; - LSPRequestDidChange *c = &request.data.change; - c->document = lsp_document_id(lsp, document); - arr_add(c->changes, change); - lsp_send_request(lsp, &request); -} - int lsp_position_cmp(LSPPosition a, LSPPosition b) { if (a.line < b.line) return -1; diff --git a/lsp.h b/lsp.h index 27f2706..8361f60 100644 --- a/lsp.h +++ b/lsp.h @@ -129,6 +129,8 @@ typedef struct { // see TextDocumentContentChangeEvent in the LSP spec typedef struct { LSPRange range; + /// if `false`, \ref text refers to the whole document contents after the change. + bool use_range; /// new text. LSPString text; } LSPDocumentChangeEvent; @@ -714,6 +716,7 @@ const char *lsp_request_string(const LSPRequest *request, LSPString string); /// sets `*string` to the LSPString, and returns a pointer which you can write the string to. /// the returned pointer will be zeroed up to and including [len]. char *lsp_message_alloc_string(LSPMessageBase *message, size_t len, LSPString *string); +LSPString lsp_message_add_string32(LSPMessageBase *message, String32 string); LSPString lsp_request_add_string(LSPRequest *request, const char *string); LSPString lsp_response_add_string(LSPResponse *response, const char *string); bool lsp_string_is_empty(LSPString string); @@ -728,8 +731,6 @@ LSP *lsp_create(const char *root_dir, const char *command, u16 port, const char // if this fails (i.e. if the LSP does not have workspace support), create a new LSP // with root directory `new_root_dir`. bool lsp_try_add_root_dir(LSP *lsp, const char *new_root_dir); -// report that this document has changed -void lsp_document_changed(LSP *lsp, const char *document, LSPRange range, String32 new_text); // is this path in the LSP's workspace folders? bool lsp_covers_path(LSP *lsp, const char *path); // get next message from server diff --git a/main.c b/main.c index d1569f7..5deabac 100644 --- a/main.c +++ b/main.c @@ -1,6 +1,9 @@ /* TODO: -- figure out what's wrong with godot language server +- figure out how to deal with godot language server being so slow + one comparatively solution is to wait x seconds before sending a batch of requests in the communication thread + (this gives us time to cancel the irrelevant requests before they get sent to the server, + and we can remove stale full-sync didChange requests) FUTURE FEATURES: - autodetect indentation (tabs vs spaces) - custom file/build command associations -- cgit v1.2.3