diff options
author | pommicket <pommicket@gmail.com> | 2023-09-09 21:19:26 -0400 |
---|---|---|
committer | pommicket <pommicket@gmail.com> | 2023-09-09 21:19:26 -0400 |
commit | 16fbe87451b0ca3e8fa35fd04e0afbfab368ea65 (patch) | |
tree | dcd4c1e37b4a89173e23047f644df94a9ca92399 | |
parent | 0dcfd5a4f1fd865c24c01b17b214e1f72e4c06fe (diff) |
fix handling of TextEdit[]
i hate microsoft so much
-rw-r--r-- | buffer.c | 54 | ||||
-rw-r--r-- | ide-format.c | 7 | ||||
-rw-r--r-- | ide-rename-symbol.c | 4 | ||||
-rw-r--r-- | lsp-parse.c | 20 | ||||
-rw-r--r-- | lsp.c | 10 | ||||
-rw-r--r-- | lsp.h | 13 | ||||
-rw-r--r-- | main.c | 1 | ||||
-rw-r--r-- | ted-internal.h | 4 | ||||
-rw-r--r-- | unicode.h | 16 | ||||
-rw-r--r-- | util.c | 15 | ||||
-rw-r--r-- | util.h | 2 |
11 files changed, 118 insertions, 28 deletions
@@ -1850,11 +1850,55 @@ LSPRange buffer_selection_as_lsp_range(TextBuffer *buffer) { } } -void buffer_apply_lsp_text_edit(TextBuffer *buffer, const LSPResponse *response, const LSPTextEdit *edit) { - BufferPos start = buffer_pos_from_lsp(buffer, edit->range.start); - BufferPos end = buffer_pos_from_lsp(buffer, edit->range.end); - buffer_delete_chars_between(buffer, start, end); - buffer_insert_utf8_at_pos(buffer, start, lsp_response_string(response, edit->new_text)); +void buffer_apply_lsp_text_edits(TextBuffer *buffer, const LSPResponse *response, const LSPTextEdit *lsp_edits, size_t n_edits) { + typedef struct { + BufferPos pos; + BufferPos end; + const char *new_text; + } Edit; + Edit *edits = calloc(n_edits, sizeof *edits); + for (size_t i = 0; i < n_edits; ++i) { + Edit *edit = &edits[i]; + edit->pos = buffer_pos_from_lsp(buffer, lsp_edits[i].range.start); + edit->end = buffer_pos_from_lsp(buffer, lsp_edits[i].range.end); + edit->new_text = lsp_response_string(response, lsp_edits[i].new_text); + } + for (size_t i = 0; i < n_edits; ++i) { + Edit *edit = &edits[i]; + buffer_delete_chars_between(buffer, edit->pos, edit->end); + buffer_insert_utf8_at_pos(buffer, edit->pos, edit->new_text); + // a TextEdit[] is annoyingly *not* applied one edit at a time, + // instead all the edits happen "at once" + // (see https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textEditArray) + // so we need to adjust the positions of subsequent edits + size_t inserted_newlines = str_count_char(edit->new_text, '\n'); + i32 line_diff = (i32)inserted_newlines + - (i32)(edit->end.line - edit->pos.line); + i32 index_diff = 0; + const char *last_newline = strrchr(edit->new_text, '\n'); + if (last_newline) { + index_diff = (i32)unicode_utf32_len(last_newline + 1) - (i32)edit->end.index; + } else { + index_diff = (i32)unicode_utf32_len(edit->new_text) + (i32)edit->pos.index - (i32)edit->end.index; + } + // see how nightmarish this is? thanks a lot, microsoft. + for (size_t j = i + 1; j < n_edits; ++j) { + Edit *edit2 = &edits[j]; + BufferPos bounds[2] = {edit2->pos, edit2->end}; + for (u32 k = 0; k < 2; ++k) { + BufferPos *pos = &bounds[k]; + if (buffer_pos_cmp(*pos, edit->pos) < 0) + continue; + + if (pos->line <= edit->end.line) + pos->index += (u32)index_diff; + pos->line += (u32)line_diff; + } + edit2->pos = bounds[0]; + edit2->end = bounds[1]; + } + } + free(edits); } static void buffer_send_lsp_did_change(LSP *lsp, TextBuffer *buffer, BufferPos pos, diff --git a/ide-format.c b/ide-format.c index 70103c7..8cc0059 100644 --- a/ide-format.c +++ b/ide-format.c @@ -58,10 +58,11 @@ void format_process_lsp_response(Ted *ted, const LSPResponse *response) { if (buffer_lsp_document_id(buffer) != request->data.formatting.document) return; // switched document + buffer_deselect(buffer); const LSPResponseFormatting *f = &response->data.formatting; - arr_foreach_ptr(f->edits, const LSPTextEdit, edit) { - buffer_apply_lsp_text_edit(buffer, response, edit); - } + buffer_start_edit_chain(buffer); + buffer_apply_lsp_text_edits(buffer, response, f->edits, arr_len(f->edits)); + buffer_end_edit_chain(buffer); } void format_quit(Ted *ted) { diff --git a/ide-rename-symbol.c b/ide-rename-symbol.c index c32f984..53a6467 100644 --- a/ide-rename-symbol.c +++ b/ide-rename-symbol.c @@ -136,7 +136,7 @@ void rename_symbol_process_lsp_response(Ted *ted, const LSPResponse *response) { arr_foreach_ptr(data->changes, const LSPWorkspaceChange, change) { switch (change->type) { - case LSP_CHANGE_EDIT: { + case LSP_CHANGE_EDITS: { const LSPWorkspaceChangeEdit *change_data = &change->data.edit; const char *path = lsp_document_path(lsp, change_data->document); if (!ted_open_file(ted, path)) goto done; @@ -152,7 +152,7 @@ void rename_symbol_process_lsp_response(Ted *ted, const LSPResponse *response) { goto done; } - buffer_apply_lsp_text_edit(buffer, response, &change_data->edit); + buffer_apply_lsp_text_edits(buffer, response, change_data->edits, arr_len(change_data->edits)); } break; case LSP_CHANGE_RENAME: { diff --git a/lsp-parse.c b/lsp-parse.c index 61e44c4..e941f1a 100644 --- a/lsp-parse.c +++ b/lsp-parse.c @@ -832,12 +832,12 @@ static bool parse_workspace_edit(LSP *lsp, LSPResponse *response, const JSON *js LSPDocumentID document = 0; if (!parse_document_uri(lsp, json, uri, &document)) return false; + LSPWorkspaceChange *change = arr_addp(edit->changes); + change->type = LSP_CHANGE_EDITS; + change->data.edit.document = document; for (u32 e = 0; e < edits.len; ++e) { - LSPWorkspaceChange *change = arr_addp(edit->changes); - change->type = LSP_CHANGE_EDIT; - change->data.edit.document = document; JSONValue text_edit = json_array_get(json, edits, e); - if (!parse_text_edit(lsp, response, json, text_edit, &change->data.edit.edit)) + if (!parse_text_edit(lsp, response, json, text_edit, arr_addp(change->data.edit.edits))) return false; } } @@ -854,12 +854,12 @@ static bool parse_workspace_edit(LSP *lsp, LSPResponse *response, const JSON *js if (!parse_document_uri(lsp, json, json_object_get(json, text_document, "uri"), &document)) return false; JSONArray edits = json_object_get_array(json, change, "edits"); - for (u32 e = 0; e < edits.len; ++e) { - LSPWorkspaceChange *out = arr_addp(edit->changes); - out->type = LSP_CHANGE_EDIT; - out->data.edit.document = document; - JSONValue text_edit = json_array_get(json, edits, e); - if (!parse_text_edit(lsp, response, json, text_edit, &out->data.edit.edit)) + LSPWorkspaceChange *out = arr_addp(edit->changes); + out->type = LSP_CHANGE_EDITS; + out->data.edit.document = document; + for (u32 i = 0; i < edits.len; ++i) { + JSONValue text_edit = json_array_get(json, edits, i); + if (!parse_text_edit(lsp, response, json, text_edit, arr_addp(out->data.edit.edits))) return false; } } else if (kind.type == JSON_STRING) { @@ -159,9 +159,15 @@ void lsp_response_free(LSPResponse *r) { case LSP_REQUEST_WORKSPACE_SYMBOLS: arr_free(r->data.workspace_symbols.symbols); break; - case LSP_REQUEST_RENAME: + case LSP_REQUEST_RENAME: { + LSPResponseRename *rename = &r->data.rename; + arr_foreach_ptr(rename->changes, LSPWorkspaceChange, c) { + if (c->type == LSP_CHANGE_EDITS) { + arr_free(c->data.edit.edits); + } + } arr_free(r->data.rename.changes); - break; + } break; case LSP_REQUEST_HIGHLIGHT: arr_free(r->data.highlight.highlights); break; @@ -482,7 +482,14 @@ typedef struct { } LSPResponseWorkspaceSymbols; typedef enum { - LSP_CHANGE_EDIT = 1, + // yes, we do need to store multiple edits in a single workspace change; + // doing a workspace change with TextEdit[] t1 + // followed by a workspace change with TextEdit[] t2 + // is different from a workspace change with t1+t2 + // (see https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textEditArray, + // https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocumentEdit) + // because microsoft is a bunch of idiots + LSP_CHANGE_EDITS = 1, LSP_CHANGE_CREATE, LSP_CHANGE_RENAME, LSP_CHANGE_DELETE @@ -490,7 +497,7 @@ typedef enum { typedef struct { LSPDocumentID document; - LSPTextEdit edit; + LSPTextEdit *edits; } LSPWorkspaceChangeEdit; typedef struct { @@ -859,7 +866,7 @@ void lsp_write_quit(void); /// print server-to-client communication #define LSP_SHOW_S2C 0 /// print client-to-server communication -#define LSP_SHOW_C2S 0 +#define LSP_SHOW_C2S 1 #endif // LSP_INTERNAL @@ -1,6 +1,5 @@ /* TODO: -- figure out what's wrong with format-selection with clangd - figure out what's wrong with godot language server - automatically restart server FUTURE FEATURES: diff --git a/ted-internal.h b/ted-internal.h index 6868895..498516b 100644 --- a/ted-internal.h +++ b/ted-internal.h @@ -458,8 +458,8 @@ LSPPosition buffer_cursor_pos_as_lsp_position(TextBuffer *buffer); /// /// Returns `(LSPRange){0}` if nothing is selected. LSPRange buffer_selection_as_lsp_range(TextBuffer *buffer); -/// Apply LSP TextEdit from response -void buffer_apply_lsp_text_edit(TextBuffer *buffer, const LSPResponse *response, const LSPTextEdit *edit); +/// Apply LSP TextEdit[] from response +void buffer_apply_lsp_text_edits(TextBuffer *buffer, const LSPResponse *response, const LSPTextEdit *edits, size_t n_edits); /// Get the cursor position as an LSPDocumentPosition. LSPDocumentPosition buffer_cursor_pos_as_lsp_document_position(TextBuffer *buffer); /// highlight an \ref LSPRange in this buffer. @@ -187,6 +187,22 @@ static size_t unicode_utf16_len(const char *str) { return len; } +// get the number of UTF-32 codepoints needed to encode `str`. +/// +// returns `(size_t)-1` on bad UTF-8 +static size_t unicode_utf32_len(const char *str) { + size_t len = 0; + uint32_t c = 0; + while (*str) { + size_t n = unicode_utf8_to_utf32(&c, str, 4); + if (n >= (size_t)-2) + return (size_t)-1; + ++len; + str += n; + } + return len; +} + /// returns the UTF-8 offset from `str` which corresponds to a UTF-16 offset of /// `utf16_offset` (rounds down if `utf16_offset` is in the middle of a codepoint). /// @@ -262,6 +262,21 @@ void str_trim(char *str) { str_trim_start(str); } +size_t str_count_char(const char *s, char c) { + const char *p = s; + size_t count = 0; + while (1) { + p = strchr(p, c); + if (p) { + ++count; + ++p; + } else { + break; + } + } + return count; +} + char *a_sprintf(PRINTF_FORMAT_STRING const char *fmt, ...) ATTRIBUTE_PRINTF(1, 2); char *a_sprintf(const char *fmt, ...) { // idk if you can always just pass NULL to vsnprintf @@ -103,6 +103,8 @@ void str_trim_start(char *str); void str_trim_end(char *str); /// trim whitespace from both sides of a string void str_trim(char *str); +/// count occurences of `c` in `s` +size_t str_count_char(const char *s, char c); /// equivalent to GNU function asprintf (like sprintf, but allocates the string with malloc). char *a_sprintf(const char *fmt, ...); /// convert binary number to string. make sure `s` can hold at least 65 bytes!! |