summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--buffer.c54
-rw-r--r--ide-format.c7
-rw-r--r--ide-rename-symbol.c4
-rw-r--r--lsp-parse.c20
-rw-r--r--lsp.c10
-rw-r--r--lsp.h13
-rw-r--r--main.c1
-rw-r--r--ted-internal.h4
-rw-r--r--unicode.h16
-rw-r--r--util.c15
-rw-r--r--util.h2
11 files changed, 118 insertions, 28 deletions
diff --git a/buffer.c b/buffer.c
index 1214fa4..9fa4ee5 100644
--- a/buffer.c
+++ b/buffer.c
@@ -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) {
diff --git a/lsp.c b/lsp.c
index 0ab78a8..e8cb630 100644
--- a/lsp.c
+++ b/lsp.c
@@ -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;
diff --git a/lsp.h b/lsp.h
index 832d3d4..395f5ee 100644
--- a/lsp.h
+++ b/lsp.h
@@ -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
diff --git a/main.c b/main.c
index a0b6e67..7506c08 100644
--- a/main.c
+++ b/main.c
@@ -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.
diff --git a/unicode.h b/unicode.h
index 8765164..cd6a965 100644
--- a/unicode.h
+++ b/unicode.h
@@ -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).
///
diff --git a/util.c b/util.c
index bdeb687..89b3dc2 100644
--- a/util.c
+++ b/util.c
@@ -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
diff --git a/util.h b/util.h
index 97db6fd..edbcbe3 100644
--- a/util.h
+++ b/util.h
@@ -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!!