summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorpommicket <pommicket@gmail.com>2023-09-09 23:01:55 -0400
committerpommicket <pommicket@gmail.com>2023-09-09 23:02:10 -0400
commit7185635a553d44b537d6fd1264ceedf421e114ef (patch)
tree826a9f66fb47947b0288eea92d153c3b165a4859
parentc9c4b74376017b52a024705622c42d7d35c5bda0 (diff)
deal with LSP servers that don't support incremental sync
this was the problem with godot
-rw-r--r--buffer.c61
-rw-r--r--lsp-write.c3
-rw-r--r--lsp.c17
-rw-r--r--lsp.h5
-rw-r--r--main.c5
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