summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt2
-rw-r--r--README.md3
-rw-r--r--buffer.c20
-rw-r--r--colors.c1
-rw-r--r--colors.h1
-rw-r--r--command.c17
-rw-r--r--command.h3
-rw-r--r--config.c1
-rw-r--r--ide-code-action.c260
-rw-r--r--ide-document-link.c13
-rw-r--r--ide-rename-symbol.c80
-rw-r--r--lib/stb_truetype.h14
-rw-r--r--lsp-json.c2
-rw-r--r--lsp-parse.c87
-rw-r--r--lsp-write.c99
-rw-r--r--lsp.c42
-rw-r--r--lsp.h45
-rw-r--r--main.c13
-rwxr-xr-xpre-commit.sh2
-rw-r--r--syntax.c2
-rw-r--r--ted-internal.h30
-rw-r--r--ted.c90
-rw-r--r--ted.cfg9
-rw-r--r--ted.h16
-rw-r--r--ui.c2
-rw-r--r--windows_installer/ted/ted.vdproj6
26 files changed, 729 insertions, 131 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 99c539e..1896751 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.5...3.25)
project(ted)
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
set(SOURCES buffer.c build.c colors.c command.c config.c find.c gl.c ide-autocomplete.c
- ide-document-link.c ide-definitions.c ide-format.c ide-highlights.c ide-hover.c
+ ide-document-link.c ide-definitions.c ide-format.c ide-highlights.c ide-hover.c ide-code-action.c
ide-signature-help.c ide-usages.c ide-rename-symbol.c lsp.c lsp-json.c lsp-parse.c
lsp-write.c main.c menu.c node.c os.c session.c stb_truetype.c syntax.c
tags.c ted.c text.c ui.c util.c macro.c)
diff --git a/README.md b/README.md
index 60145da..e61b9bc 100644
--- a/README.md
+++ b/README.md
@@ -237,7 +237,7 @@ The Go team's `go-pls` is enabled by default. You can download it
### Java
Eclipse's `jdtls` is enabled by default.
-You can download it [here](download.eclipse.org/jdtls/milestones/?d).
+You can download it [here](https://download.eclipse.org/jdtls/milestones/?d).
### JavaScript/TypeScript
@@ -368,6 +368,7 @@ Then run `make.bat release`.
<tr><td>2.8.2</td> <td>Fix syntax highlighting bug</td> <td>2025 Jun 27</td></tr>
<tr><td>2.8.3</td> <td>Fix annoying auto-indent behaviour</td> <td>2025 Sep 1</td></tr>
<tr><td>2.8.4</td> <td>Keep cursor pos on reload, other small improvements</td> <td>2025 Sep 28</td></tr>
+<tr><td>2.9.0</td> <td>LSP code actions, bug fixes</td> <td>2025 Sep 30</td></tr>
</table>
## License
diff --git a/buffer.c b/buffer.c
index 7ed3649..11b239d 100644
--- a/buffer.c
+++ b/buffer.c
@@ -39,14 +39,6 @@ struct BufferEdit {
double time; // time at start of edit (i.e. the time just before the edit), in seconds since epoch
};
-typedef struct {
- MessageType severity;
- BufferPos pos;
- char *message;
- // may be NULL
- char *url;
-} Diagnostic;
-
struct TextBuffer {
/// NULL if this buffer is untitled or doesn't correspond to a file (e.g. line buffers)
char *path;
@@ -1004,6 +996,7 @@ static void buffer_line_free(Line *line) {
static void diagnostic_free(Diagnostic *diagnostic) {
free(diagnostic->message);
free(diagnostic->url);
+ free(diagnostic->raw);
memset(diagnostic, 0, sizeof *diagnostic);
}
@@ -2667,6 +2660,9 @@ void buffer_delete_chars_at_pos(TextBuffer *buffer, BufferPos pos, i64 nchars_)
// just in case
buffer_pos_validate(buffer, &buffer->cursor_pos);
buffer_pos_validate(buffer, &buffer->selection_pos);
+ if (buffer_pos_eq(buffer->cursor_pos, buffer->selection_pos)) {
+ buffer->selection = false;
+ }
// we need to do this *after* making the change to the buffer
// because of how non-incremental syncing works.
@@ -3536,6 +3532,8 @@ bool buffer_handle_click(Ted *ted, TextBuffer *buffer, vec2 click, u8 times) {
else
autocomplete_close(ted); // close autocomplete menu if user clicks outside of it
}
+ if (code_action_is_open(ted))
+ return false;
if (buffer_pixels_to_pos(buffer, click, &buffer_pos)) {
// user clicked on buffer
if (!menu_is_any_open(ted) || buffer->is_line_buffer) {
@@ -4245,7 +4243,9 @@ void buffer_publish_diagnostics(TextBuffer *buffer, const LSPRequest *request, L
arr_foreach_ptr(diagnostics, const LSPDiagnostic, diagnostic) {
Diagnostic *d = arr_addp(buffer->diagnostics);
d->pos = buffer_pos_from_lsp(buffer, diagnostic->range.start);
+ d->end = buffer_pos_from_lsp(buffer, diagnostic->range.end);
d->severity = diagnostic_severity(diagnostic);
+ d->raw = str_dup(lsp_request_string(request, diagnostic->raw));
char message[280];
const char *code = lsp_request_string(request, diagnostic->code);
if (*code) {
@@ -4287,3 +4287,7 @@ void buffer_set_inotify_modified(TextBuffer *buffer) {
buffer->inotify_modified = true;
}
#endif
+
+const Diagnostic *buffer_diagnostics(TextBuffer *buffer) {
+ return buffer->diagnostics;
+}
diff --git a/colors.c b/colors.c
index 820c0c6..f1aa146 100644
--- a/colors.c
+++ b/colors.c
@@ -10,7 +10,6 @@ typedef struct {
static ColorName color_names[] = {
{COLOR_UNKNOWN, "unknown"},
{COLOR_TEXT, "text"},
- {COLOR_TEXT_SECONDARY, "text-secondary"},
{COLOR_BG, "bg"},
{COLOR_CURSOR, "cursor"},
{COLOR_CURSOR_ERROR, "cursor-error"},
diff --git a/colors.h b/colors.h
index 6535240..af92018 100644
--- a/colors.h
+++ b/colors.h
@@ -12,7 +12,6 @@ typedef enum {
/// main text color
COLOR_TEXT,
- COLOR_TEXT_SECONDARY,
COLOR_BG,
COLOR_CURSOR,
COLOR_CURSOR_ERROR,
diff --git a/command.c b/command.c
index 3b647f4..703f4bd 100644
--- a/command.c
+++ b/command.c
@@ -111,6 +111,8 @@ static CommandName command_names[] = {
{"indent-with-tabs", CMD_INDENT_WITH_TABS},
{"set-tab-width", CMD_SET_TAB_WIDTH},
{"debug-print-undo-history", CMD_DEBUG_PRINT_UNDO_HISTORY},
+ {"code-action", CMD_CODE_ACTION},
+ {"code-action-prev", CMD_CODE_ACTION_PREV},
};
static_assert_if_possible(arr_count(command_names) == CMD_COUNT)
@@ -344,6 +346,8 @@ void command_execute_ex(Ted *ted, Command c, const CommandArgument *full_argumen
buffer_select_all(buffer);
} else if (autocomplete_is_open(ted) || autocomplete_has_phantom(ted)) {
autocomplete_select_completion(ted);
+ } else if (code_action_is_open(ted)) {
+ code_action_select(ted);
} else if (buffer) {
if (buffer_has_selection(buffer))
buffer_indent_selection(buffer);
@@ -377,6 +381,8 @@ void command_execute_ex(Ted *ted, Command c, const CommandArgument *full_argumen
} else if (buffer) {
buffer_newline(buffer);
}
+ } else if (code_action_is_open(ted)) {
+ code_action_select(ted);
} else if (buffer) {
buffer_newline(buffer);
}
@@ -640,6 +646,8 @@ void command_execute_ex(Ted *ted, Command c, const CommandArgument *full_argumen
*ted->message_shown = '\0';
} else if (autocomplete_is_open(ted)) {
autocomplete_close(ted);
+ } else if (code_action_is_open(ted)) {
+ code_action_close(ted);
} else if (menu_is_any_open(ted)) {
menu_escape(ted);
} else {
@@ -740,5 +748,14 @@ void command_execute_ex(Ted *ted, Command c, const CommandArgument *full_argumen
case CMD_DEBUG_PRINT_UNDO_HISTORY:
buffer_print_undo_history(buffer);
break;
+ case CMD_CODE_ACTION:
+ if (code_action_is_open(ted))
+ code_action_next(ted);
+ else
+ code_action_open(ted);
+ break;
+ case CMD_CODE_ACTION_PREV:
+ code_action_prev(ted);
+ break;
}
}
diff --git a/command.h b/command.h
index 400dab1..7a141a7 100644
--- a/command.h
+++ b/command.h
@@ -107,6 +107,9 @@ typedef enum {
CMD_AUTOCOMPLETE_BACK,
CMD_FIND_USAGES,
CMD_RENAME_SYMBOL,
+ CMD_CODE_ACTION,
+ CMD_CODE_ACTION_PREV,
+
/// "go to definition of..." menu
CMD_GOTO_DEFINITION,
CMD_GOTO_DEFINITION_AT_CURSOR,
diff --git a/config.c b/config.c
index 92f3580..284773f 100644
--- a/config.c
+++ b/config.c
@@ -1538,6 +1538,7 @@ void config_read(Ted *ted, const char *path, ConfigFormat format) {
break;
case CONFIG_TED_CFG:
config_read_ted_cfg(ted, source_rc, &include_stack);
+ arr_free(include_stack);
// force recompute default settings
strcpy(ted->default_settings_cwd, "//");
break;
diff --git a/ide-code-action.c b/ide-code-action.c
new file mode 100644
index 0000000..c855843
--- /dev/null
+++ b/ide-code-action.c
@@ -0,0 +1,260 @@
+#include "ted-internal.h"
+
+typedef struct {
+ const char *name;
+ const LSPCodeAction *lsp;
+ int relevancy;
+} Action;
+
+struct CodeAction {
+ LSPServerRequestID last_request;
+ // which buffer code action is open for
+ LSPDocumentID which_buffer;
+ // cursor position when code action was opened
+ BufferPos cursor_pos;
+ LSPResponse response;
+ Action *actions;
+ u32 cursor;
+};
+
+static bool ranges_touch(BufferPos p1, BufferPos p2, BufferPos q1, BufferPos q2) {
+ int cmp21 = buffer_pos_cmp(p2, q1);
+ if (cmp21 < 0) {
+ // p is entirely before q
+ return false;
+ }
+ int cmp12 = buffer_pos_cmp(p1, q2);
+ if (cmp12 > 0) {
+ // p is entirely after q
+ return false;
+ }
+ return true;
+}
+
+void code_action_init(Ted *ted) {
+ ted->code_action = calloc(1, sizeof *ted->code_action);
+}
+
+void code_action_open(Ted *ted) {
+ CodeAction *c = ted->code_action;
+ ted_cancel_lsp_request(ted, &c->last_request);
+ TextBuffer *buffer = ted_active_buffer(ted);
+ if (!buffer) return;
+ LSP *lsp = buffer_lsp(buffer);
+ if (!lsp) return;
+ autocomplete_close(ted);
+ c->cursor = 0;
+ c->which_buffer = buffer_lsp_document_id(buffer);
+ c->cursor_pos = buffer_cursor_pos(buffer);
+ BufferPos range_start = {0}, range_end = {0};
+ LSPRange range = {0};
+ if (buffer_selection_pos(buffer, &range_start)) {
+ range = buffer_selection_as_lsp_range(buffer);
+ range_end = buffer_cursor_pos(buffer);
+ if (buffer_pos_cmp(range_start, range_end) > 0) {
+ BufferPos tmp = range_start;
+ range_start = range_end;
+ range_end = tmp;
+ }
+ } else {
+ range_start = range_end = buffer_cursor_pos(buffer);
+ range.start = range.end = buffer_cursor_pos_as_lsp_position(buffer);
+ }
+ LSPRequest req = {
+ .type = LSP_REQUEST_CODE_ACTION,
+ };
+ LSPRequestCodeAction *code_action_req = &req.data.code_action;
+ code_action_req->document = buffer_lsp_document_id(buffer);
+ code_action_req->range = range;
+ arr_foreach_ptr(buffer_diagnostics(buffer), const Diagnostic, d) {
+ if (ranges_touch(range_start, range_end, d->pos, d->end)) {
+ LSPString raw = lsp_request_add_string(&req, d->raw);
+ arr_add(code_action_req->raw_diagnostics, raw);
+ }
+ }
+ c->last_request = lsp_send_request(lsp, &req);
+}
+
+void code_action_next(Ted *ted) {
+ CodeAction *c = ted->code_action;
+ u32 actions_count = arr_len(c->actions);
+ if (actions_count)
+ c->cursor = (c->cursor + 1) % actions_count;
+}
+
+void code_action_prev(Ted *ted) {
+ CodeAction *c = ted->code_action;
+ u32 actions_count = arr_len(c->actions);
+ if (actions_count)
+ c->cursor = c->cursor ? c->cursor - 1 : actions_count - 1;
+}
+
+bool code_action_is_open(Ted *ted) {
+ CodeAction *c = ted->code_action;
+ return arr_len(c->response.data.code_action.actions) != 0;
+}
+
+void code_action_close(Ted *ted) {
+ CodeAction *c = ted->code_action;
+ lsp_response_free(&c->response);
+ arr_free(c->actions);
+ memset(&c->response, 0, sizeof c->response);
+}
+
+static int action_qsort_cmp_relevancy(const void *av, const void *bv) {
+ const Action *a = av, *b = bv;
+ return b->relevancy - a->relevancy;
+}
+
+bool code_action_process_lsp_response(Ted *ted, const LSPResponse *response) {
+ CodeAction *c = ted->code_action;
+ if (response->request.id != c->last_request.id
+ || response->request.type != LSP_REQUEST_CODE_ACTION) {
+ return false;
+ }
+ if (arr_len(response->data.code_action.actions) == 0) {
+ // no code actions
+ code_action_close(ted);
+ ted_flash_error_cursor(ted);
+ return false;
+ }
+ lsp_response_free(&c->response); // free old response
+ c->response = *response;
+ arr_free(c->actions);
+ // we want to figure out which actions should be "preferred" (ordered first)
+ // currently we:
+ // first, prefer actions with the LSP isPreferred property set to true.
+ // then, prefer 'quickfix' to other kinds of actions.
+ // then, prefer whichever action comes first.
+ arr_foreach_ptr(response->data.code_action.actions, const LSPCodeAction, action) {
+ Action *action_out = arr_addp(c->actions);
+ action_out->lsp = action;
+ action_out->name = lsp_response_string(response, action->name);
+ int score = 0;
+ if (action->is_preferred)
+ score += 10;
+ if (action->kind == LSP_CODE_ACTION_QUICKFIX)
+ score += 1;
+ action_out->relevancy = score;
+ }
+ arr_qsort(c->actions, action_qsort_cmp_relevancy);
+ return true;
+}
+
+void code_action_quit(Ted *ted) {
+ CodeAction *c = ted->code_action;
+ code_action_close(ted);
+ free(c);
+ ted->code_action = NULL;
+}
+
+static void code_action_perform(Ted *ted, const LSPCodeAction *action) {
+ CodeAction *c = ted->code_action;
+ const LSPResponse *response = &c->response;
+ LSPServerRequestID request_id = c->last_request;
+ LSP *lsp = ted_get_lsp_by_id(ted, request_id.lsp);
+ ted_perform_workspace_edit(ted, lsp, response, &action->edit);
+ switch (action->command.kind) {
+ case LSP_COMMAND_NONE: break;
+ case LSP_COMMAND_WORKSPACE_EDIT:
+ ted_perform_workspace_edit(ted, lsp, response, &action->command.data.edit);
+ break;
+ }
+ code_action_close(ted);
+}
+
+void code_action_select(Ted *ted) {
+ CodeAction *c = ted->code_action;
+ if (c->cursor < arr_len(c->actions))
+ code_action_perform(ted, c->actions[c->cursor].lsp);
+}
+
+void code_action_frame(Ted *ted) {
+ CodeAction *c = ted->code_action;
+ if (arr_len(c->actions) == 0)
+ return;
+ TextBuffer *buffer = ted_active_buffer(ted);
+ if (!buffer) {
+ code_action_close(ted);
+ return;
+ }
+ LSP *lsp = buffer_lsp(buffer);
+ if (!lsp || lsp_get_id(lsp) != c->last_request.lsp) {
+ // LSP or active buffer changed
+ code_action_close(ted);
+ return;
+ }
+ if (buffer_lsp_document_id(buffer) != c->which_buffer) {
+ // buffer changed
+ code_action_close(ted);
+ return;
+ }
+ if (!buffer_pos_eq(buffer_cursor_pos(buffer), c->cursor_pos)) {
+ // cursor moved
+ code_action_close(ted);
+ return;
+ }
+ const Settings *settings = ted_active_settings(ted);
+ Font *font = ted->font;
+ float char_height = text_font_char_height(font);
+ float padding = settings->padding;
+ float border_thickness = settings->border_thickness;
+
+ // display code action panel
+ vec2 cursor_pos = buffer_pos_to_pixels(buffer, buffer_cursor_pos(buffer));
+ float x = cursor_pos.x, y = cursor_pos.y;
+ float panel_width = 0, panel_height = (char_height + border_thickness) * (float)arr_len(c->actions);
+ arr_foreach_ptr(c->actions, const Action, action) {
+ float row_width = text_get_size_vec2(font, action->name).x
+ + char_height * 6 + padding * 2;
+ if (row_width > panel_width)
+ panel_width = row_width;
+ }
+ // ensure panel doesn't go offscreen
+ if (x > ted->window_width - panel_width)
+ x = ted->window_width - panel_width;
+ if (y > ted->window_height / 2) {
+ // open panel upwards
+ y -= panel_height;
+ } else {
+ y += char_height;
+ }
+
+ Rect panel_rect = {{x,y},{panel_width,panel_height}};
+ gl_geometry_rect(panel_rect, settings_color(settings, COLOR_BG));
+ gl_geometry_rect_border(panel_rect, border_thickness, settings_color(settings, COLOR_AUTOCOMPLETE_BORDER));
+ const Action *selected_action = NULL;
+ arr_foreach_ptr(c->actions, const Action, action) {
+ Rect entry_rect = {{x, y}, {panel_width, char_height}};
+ if (rect_contains_point(entry_rect, ted->mouse_pos)
+ || c->cursor == (u32)(action - c->actions)) {
+ // hovering/cursoring over this entry
+ ted->cursor = ted->cursor_hand;
+ gl_geometry_rect(entry_rect, settings_color(settings, COLOR_AUTOCOMPLETE_HL));
+ }
+ arr_foreach_ptr(ted->mouse_clicks[SDL_BUTTON_LEFT], const MouseClick, click)
+ if (rect_contains_point(entry_rect, click->pos))
+ selected_action = action; // clicked on this entry
+ if (action != c->actions) {
+ // border between entries
+ Rect border = {{x, y}, {panel_width, border_thickness}};
+ gl_geometry_rect(border, settings_color(settings, COLOR_AUTOCOMPLETE_BORDER));
+ y += border_thickness;
+ };
+ text_utf8(font, action->name, x + padding, y,
+ settings_color(settings, COLOR_TEXT));
+ y += char_height;
+ }
+ gl_geometry_draw();
+ text_render(font);
+ if (selected_action) {
+ code_action_perform(ted, selected_action->lsp);
+ } else {
+ arr_foreach_ptr(ted->mouse_clicks[SDL_BUTTON_LEFT], const MouseClick, click) {
+ if (!rect_contains_point(panel_rect, click->pos)) {
+ // clicked outside of code action panel; close it.
+ code_action_close(ted);
+ }
+ }
+ }
+}
diff --git a/ide-document-link.c b/ide-document-link.c
index 754cecd..7448c83 100644
--- a/ide-document-link.c
+++ b/ide-document-link.c
@@ -81,7 +81,6 @@ void document_link_frame(Ted *ted) {
document_link_clear(ted);
return;
}
-
TextBuffer *buffer = ted->active_buffer;
if (!buffer)
return;
@@ -89,20 +88,24 @@ void document_link_frame(Ted *ted) {
LSP *lsp = buffer_lsp(buffer);
if (!lsp)
return;
-
- if (!dl->last_request.id) {
+ LSPDocumentID document = buffer_lsp_document_id(buffer);
+ if (document != dl->requested_document // don't spam requests
+ && !dl->last_request.id) {
// send the request
LSPRequest request = {.type = LSP_REQUEST_DOCUMENT_LINK};
LSPRequestDocumentLink *lnk = &request.data.document_link;
- lnk->document = buffer_lsp_document_id(buffer);
+ lnk->document = document;
dl->last_request = lsp_send_request(lsp, &request);
dl->requested_document = lnk->document;
}
-
+
arr_foreach_ptr(dl->links, DocumentLink, l) {
Rect r = document_link_get_rect(ted, l);
if (ted_mouse_in_rect(ted, r)) {
ted->cursor = ted->cursor_hand;
+ r.pos.y += r.size.y - 1;
+ r.size.y = 1;
+ gl_geometry_rect(r, settings_color(settings, COLOR_TEXT));
}
}
}
diff --git a/ide-rename-symbol.c b/ide-rename-symbol.c
index dbbed0f..95b4a56 100644
--- a/ide-rename-symbol.c
+++ b/ide-rename-symbol.c
@@ -175,85 +175,7 @@ void rename_symbol_process_lsp_response(Ted *ted, const LSPResponse *response) {
// LSP crashed or something
return;
}
- TextBuffer *const start_buffer = ted_active_buffer(ted);
-
- arr_foreach_ptr(data->changes, const LSPWorkspaceChange, change) {
- if (change->type == LSP_CHANGE_DELETE && change->data.delete.recursive) {
- ted_error(ted, "refusing to perform rename because it involves a recursive deletion\n"
- "I'm too scared to go through with this");
- return;
- }
- }
-
- arr_foreach_ptr(data->changes, const LSPWorkspaceChange, change) {
- switch (change->type) {
- 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;
-
- TextBuffer *buffer = ted_get_buffer_with_file(ted, path);
- // chain all edits together so they can be undone with one ctrl+z
- buffer_start_edit_chain(buffer);
-
- if (!buffer) {
- // this should never happen since we just
- // successfully opened it
- assert(0);
- goto done;
- }
-
- buffer_apply_lsp_text_edits(buffer, response, change_data->edits, arr_len(change_data->edits));
- }
- break;
- case LSP_CHANGE_RENAME: {
- const LSPWorkspaceChangeRename *rename = &change->data.rename;
- const char *old = lsp_document_path(lsp, rename->old);
- const char *new = lsp_document_path(lsp, rename->new);
- FsType new_type = fs_path_type(new);
- if (new_type == FS_DIRECTORY) {
- ted_error(ted, "Aborting rename since it's asking to overwrite a directory.");
- goto done;
- }
-
- if (rename->ignore_if_exists && new_type != FS_NON_EXISTENT) {
- break;
- }
- if (!rename->overwrite && new_type != FS_NON_EXISTENT) {
- ted_error(ted, "Aborting rename since it would overwrite a file.");
- goto done;
- }
- os_rename_overwrite(old, new);
- if (ted_close_buffer_with_file(ted, old))
- ted_open_file(ted, new);
- } break;
- case LSP_CHANGE_DELETE: {
- const LSPWorkspaceChangeDelete *delete = &change->data.delete;
- const char *path = lsp_document_path(lsp, delete->document);
- remove(path);
- ted_close_buffer_with_file(ted, path);
- } break;
- case LSP_CHANGE_CREATE: {
- const LSPWorkspaceChangeCreate *create = &change->data.create;
- const char *path = lsp_document_path(lsp, create->document);
- FILE *fp = fopen(path, create->overwrite ? "wb" : "ab");
- if (fp) fclose(fp);
- ted_open_file(ted, path);
- } break;
- }
- }
- done:
-
- {
- // end all edit chains in all buffers
- // they're almost definitely all created by us
- arr_foreach_ptr(ted->buffers, TextBufferPtr, pbuffer) {
- buffer_end_edit_chain(*pbuffer);
- }
-
- ted_save_all(ted);
- }
- ted_switch_to_buffer(ted, start_buffer);
+ ted_perform_workspace_edit(ted, lsp, response, data);
}
void rename_symbol_init(Ted *ted) {
diff --git a/lib/stb_truetype.h b/lib/stb_truetype.h
index bbf2284..90a5c2e 100644
--- a/lib/stb_truetype.h
+++ b/lib/stb_truetype.h
@@ -54,7 +54,7 @@
// Hou Qiming Derek Vinyard
// Rob Loach Cort Stratton
// Kenney Phillis Jr. Brian Costabile
-// Ken Voskuil (kaesve)
+// Ken Voskuil (kaesve) Yakov Galka
//
// VERSION HISTORY
//
@@ -4604,6 +4604,8 @@ STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float sc
scale_y = -scale_y;
{
+ // distance from singular values (in the same units as the pixel grid)
+ const float eps = 1./1024, eps2 = eps*eps;
int x,y,i,j;
float *precompute;
stbtt_vertex *verts;
@@ -4616,15 +4618,15 @@ STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float sc
float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y;
float x1 = verts[j].x*scale_x, y1 = verts[j].y*scale_y;
float dist = (float) STBTT_sqrt((x1-x0)*(x1-x0) + (y1-y0)*(y1-y0));
- precompute[i] = (dist == 0) ? 0.0f : 1.0f / dist;
+ precompute[i] = (dist < eps) ? 0.0f : 1.0f / dist;
} else if (verts[i].type == STBTT_vcurve) {
float x2 = verts[j].x *scale_x, y2 = verts[j].y *scale_y;
float x1 = verts[i].cx*scale_x, y1 = verts[i].cy*scale_y;
float x0 = verts[i].x *scale_x, y0 = verts[i].y *scale_y;
float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2;
float len2 = bx*bx + by*by;
- if (len2 != 0.0f)
- precompute[i] = 1.0f / (bx*bx + by*by);
+ if (len2 >= eps2)
+ precompute[i] = 1.0f / len2;
else
precompute[i] = 0.0f;
} else
@@ -4689,8 +4691,8 @@ STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float sc
float a = 3*(ax*bx + ay*by);
float b = 2*(ax*ax + ay*ay) + (mx*bx+my*by);
float c = mx*ax+my*ay;
- if (a == 0.0) { // if a is 0, it's linear
- if (b != 0.0) {
+ if (STBTT_fabs(a) < eps2) { // if a is 0, it's linear
+ if (STBTT_fabs(b) >= eps2) {
res[num++] = -c/b;
}
} else {
diff --git a/lsp-json.c b/lsp-json.c
index 4884c8c..ae25267 100644
--- a/lsp-json.c
+++ b/lsp-json.c
@@ -577,7 +577,7 @@ void json_string_get(const JSON *json, JSONString string, char *buf, size_t buf_
hex[0] = text[i++];
hex[1] = text[i++];
hex[2] = text[i++];
- hex[3] = text[i++];
+ hex[3] = text[i];
unsigned code_point=0;
sscanf(hex, "%04x", &code_point);
// technically this won't deal with people writing out UTF-16 surrogate halves
diff --git a/lsp-parse.c b/lsp-parse.c
index 63d5605..5881ec3 100644
--- a/lsp-parse.c
+++ b/lsp-parse.c
@@ -280,6 +280,11 @@ static void parse_capabilities(LSP *lsp, const JSON *json, JSONObject capabiliti
cap->range_formatting_support = true;
}
+ JSONValue code_action_value = json_object_get(json, capabilities, "codeActionProvider");
+ if (code_action_value.type == JSON_OBJECT || code_action_value.type == JSON_TRUE) {
+ cap->code_action_support = true;
+ }
+
JSONObject workspace = json_object_get_object(json, capabilities, "workspace");
// check WorkspaceFoldersServerCapabilities
JSONObject workspace_folders = json_object_get_object(json, workspace, "workspaceFolders");
@@ -795,10 +800,14 @@ static bool parse_publish_diagnostics(LSP *lsp, const JSON *json, LSPRequest *re
return false;
JSONArray diagnostics = json_object_get_array(json, params, "diagnostics");
for (u32 i = 0; i < diagnostics.len; ++i) {
- JSONObject diagnostic_in = json_array_get_object(json, diagnostics, i);
+ JSONValue diagnostic_val = json_array_get(json, diagnostics, i);
+ JSONObject diagnostic_in = json_force_object(diagnostic_val);
LSPDiagnostic *diagnostic_out = arr_addp(pub->diagnostics);
if (!parse_diagnostic(lsp, request, json, diagnostic_in, diagnostic_out))
return false;
+ char *raw = json_reserialize(json, diagnostic_val);
+ diagnostic_out->raw = lsp_request_add_string(request, raw);
+ free(raw);
}
return true;
}
@@ -1081,6 +1090,79 @@ static bool parse_formatting_response(LSP *lsp, const JSON *json, LSPResponse *r
return true;
}
+static bool parse_command(LSP *lsp, LSPResponse *response, const JSON *json, JSONObject command_in, LSPCommand *command_out) {
+ JSONString command_str = json_object_get_string(json, command_in, "command");
+ JSONArray arguments = json_object_get_array(json, command_in, "arguments");
+ char command[64];
+ json_string_get(json, command_str, command, sizeof command);
+ if (strcmp(command, "java.apply.workspaceEdit") == 0) {
+ // why does this exist??
+ if (arguments.len != 1) {
+ lsp_set_error(lsp, "Expected 1 argument to %s; got %" PRIu32, command, arguments.len);
+ return false;
+ }
+ JSONObject edit = json_array_get_object(json, arguments, 0);
+ if (!parse_workspace_edit(lsp, response, json, edit, &command_out->data.edit)) {
+ lsp_workspace_edit_free(&command_out->data.edit);
+ return false;
+ }
+ command_out->kind = LSP_COMMAND_WORKSPACE_EDIT;
+ return true;
+ }
+ eprint("Unrecognized command: %s\n", command);
+ (void)command_out;
+ return false;
+}
+
+static LSPCodeActionKind parse_code_action_kind(const char *kind) {
+ if (streq(kind, "quickfix"))
+ return LSP_CODE_ACTION_QUICKFIX;
+ return LSP_CODE_ACTION_OTHER;
+}
+
+static bool parse_code_action_response(LSP *lsp, const JSON *json, LSPResponse *response) {
+ JSONValue actions_val = json_get(json, "result");
+ if (actions_val.type == JSON_NULL) {
+ // nothing there
+ return true;
+ }
+ if (actions_val.type != JSON_ARRAY) {
+ lsp_set_error(lsp, "Expected array or null for code action response; got %s",
+ json_type_to_str(actions_val.type));
+ return false;
+ }
+ JSONArray actions = json_force_array(actions_val);
+ for (u32 i = 0; i < actions.len; i++) {
+ JSONObject action = json_array_get_object(json, actions, i);
+ JSONValue command_val = json_object_get(json, action, "command");
+ LSPCodeAction action_out = {0};
+ JSONString title_str = json_object_get_string(json, action, "title");
+ action_out.name = lsp_response_add_json_string(response, json, title_str);
+ action_out.is_preferred = json_object_get_bool(json, action, "isPreferred", false);
+ JSONString kind_str = json_object_get_string(json, action, "kind");
+ char kind[32];
+ json_string_get(json, kind_str, kind, sizeof kind);
+ action_out.kind = parse_code_action_kind(kind);
+ bool understood = true;
+ if (command_val.type == JSON_STRING) {
+ // this action is a Command
+ understood &= parse_command(lsp, response, json, action, &action_out.command);
+ } else {
+ // this action is a CodeAction
+ JSONValue edit = json_object_get(json, action, "edit");
+ if (edit.type == JSON_OBJECT) {
+ understood &= parse_workspace_edit(lsp, response, json, edit.val.object, &action_out.edit);
+ }
+ if (command_val.type == JSON_OBJECT) {
+ understood &= parse_command(lsp, response, json, command_val.val.object, &action_out.command);
+ }
+ }
+ if (understood)
+ arr_add(response->data.code_action.actions, action_out);
+ }
+ return true;
+}
+
void process_message(LSP *lsp, JSON *json) {
#if 0
@@ -1164,6 +1246,9 @@ void process_message(LSP *lsp, JSON *json) {
case LSP_REQUEST_DOCUMENT_LINK:
add_to_messages = parse_document_link_response(lsp, json, &response);
break;
+ case LSP_REQUEST_CODE_ACTION:
+ add_to_messages = parse_code_action_response(lsp, json, &response);
+ break;
case LSP_REQUEST_INITIALIZE:
if (!lsp->initialized) {
// it's the response to our initialize request!
diff --git a/lsp-write.c b/lsp-write.c
index c1c846a..6c6d5a6 100644
--- a/lsp-write.c
+++ b/lsp-write.c
@@ -250,6 +250,68 @@ static void write_document_position(JSONWriter *o, LSPDocumentPosition pos) {
write_key_position(o, "position", pos.pos);
}
+
+static void json_reserialize_to(const JSON *json, JSONWriter *o, JSONValue val) {
+ switch (val.type) {
+ case JSON_UNDEFINED:
+ assert(false);
+ break;
+ case JSON_TRUE:
+ write_bool(o, true);
+ break;
+ case JSON_FALSE:
+ write_bool(o, false);
+ break;
+ case JSON_NULL:
+ write_null(o);
+ break;
+ case JSON_NUMBER:
+ write_number(o, val.val.number);
+ break;
+ case JSON_STRING: {
+ char *s = json_string_get_alloc(json, val.val.string);
+ write_string(o, s);
+ free(s);
+ } break;
+ case JSON_ARRAY: {
+ JSONArray array = val.val.array;
+ write_arr_start(o);
+ for (u32 i = 0; i < array.len; i++) {
+ JSONValue elem = json_array_get(json, array, i);
+ write_arr_elem(o);
+ json_reserialize_to(json, o, elem);
+ }
+ write_arr_end(o);
+ } break;
+ case JSON_OBJECT: {
+ JSONObject object = val.val.object;
+ write_obj_start(o);
+ JSONValue *keys = &json->values[object.items];
+ JSONValue *values = keys + object.len;
+ for (u32 i = 0; i < object.len; i++) {
+ JSONValue key_val = keys[i];
+ assert(key_val.type == JSON_STRING);
+ JSONString key = key_val.val.string;
+ JSONValue value = values[i];
+ char *key_str = json_string_get_alloc(json, key);
+ write_key(o, key_str);
+ free(key_str);
+ json_reserialize_to(json, o, value);
+ }
+ write_obj_end(o);
+ } break;
+ }
+}
+
+char *json_reserialize(const JSON *json, JSONValue value) {
+ StrBuilder builder = str_builder_new();
+ JSONWriter writer = json_writer_new(NULL, &builder);
+ json_reserialize_to(json, &writer, value);
+ char *str = str_dup(builder.str);
+ str_builder_free(&builder);
+ return str;
+}
+
static const char *lsp_request_method(LSPRequest *request) {
switch (request->type) {
case LSP_REQUEST_NONE: break;
@@ -311,6 +373,8 @@ static const char *lsp_request_method(LSPRequest *request) {
return "textDocument/rangeFormatting";
case LSP_REQUEST_FORMATTING:
return "textDocument/formatting";
+ case LSP_REQUEST_CODE_ACTION:
+ return "textDocument/codeAction";
}
assert(0);
return "$/ignore";
@@ -468,6 +532,24 @@ void write_request(LSP *lsp, LSPRequest *request, StrBuilder *builder) {
write_key_obj_start(o, "publishDiagnostics");
write_key_bool(o, "codeDescriptionSupport", true);
write_obj_end(o);
+
+ write_key_obj_start(o, "codeAction");
+ write_key_bool(o, "isPreferredSupport", true);
+ write_key_obj_start(o, "codeActionLiteralSupport");
+ write_key_obj_start(o, "codeActionKind");
+ write_key_arr_start(o, "valueSet");
+ write_arr_elem_string(o, "quickfix");
+ write_arr_elem_string(o, "refactor");
+ write_arr_elem_string(o, "refactor.extract");
+ write_arr_elem_string(o, "refactor.inline");
+ write_arr_elem_string(o, "refactor.rewrite");
+ write_arr_elem_string(o, "source");
+ write_arr_elem_string(o, "source.organizeImports");
+ write_arr_elem_string(o, "source.fixAll");
+ write_arr_end(o);
+ write_obj_end(o);
+ write_obj_end(o);
+ write_obj_end(o);
write_obj_end(o);
write_key_obj_start(o, "workspace");
write_key_bool(o, "workspaceFolders", true);
@@ -670,6 +752,23 @@ void write_request(LSP *lsp, LSPRequest *request, StrBuilder *builder) {
}
write_obj_end(o);
} break;
+ case LSP_REQUEST_CODE_ACTION: {
+ LSPRequestCodeAction *code_action = &request->data.code_action;
+ write_key_obj_start(o, "params");
+ write_key_obj_start(o, "textDocument");
+ write_key_file_uri(o, "uri", code_action->document);
+ write_obj_end(o);
+ write_key_range(o, "range", code_action->range);
+ write_key_obj_start(o, "context");
+ write_key_arr_start(o, "diagnostics");
+ arr_foreach_ptr(code_action->raw_diagnostics, const LSPString, raw_diagnostic) {
+ write_arr_elem(o);
+ str_builder_append(o->builder, lsp_request_string(request, *raw_diagnostic));
+ }
+ write_arr_end(o);
+ write_obj_end(o);
+ write_obj_end(o);
+ } break;
}
write_obj_end(o);
diff --git a/lsp.c b/lsp.c
index 2004bb7..13e9862 100644
--- a/lsp.c
+++ b/lsp.c
@@ -136,6 +136,10 @@ void lsp_request_free(LSPRequest *r) {
case LSP_REQUEST_PREPARE_RENAME:
case LSP_REQUEST_WORKSPACE_SYMBOLS:
break;
+ case LSP_REQUEST_CODE_ACTION: {
+ LSPRequestCodeAction *c = &r->data.code_action;
+ arr_free(c->raw_diagnostics);
+ } break;
case LSP_REQUEST_DID_CHANGE: {
LSPRequestDidChange *c = &r->data.change;
arr_free(c->changes);
@@ -149,6 +153,15 @@ void lsp_request_free(LSPRequest *r) {
memset(r, 0, sizeof *r);
}
+void lsp_workspace_edit_free(LSPWorkspaceEdit *edit) {
+ arr_foreach_ptr(edit->changes, LSPWorkspaceChange, c) {
+ if (c->type == LSP_CHANGE_EDITS) {
+ arr_free(c->data.edit.edits);
+ }
+ }
+ arr_free(edit->changes);
+}
+
void lsp_response_free(LSPResponse *r) {
lsp_message_base_free(&r->base);
switch (r->request.type) {
@@ -166,12 +179,7 @@ void lsp_response_free(LSPResponse *r) {
break;
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);
+ lsp_workspace_edit_free(rename);
} break;
case LSP_REQUEST_HIGHLIGHT:
arr_free(r->data.highlight.highlights);
@@ -185,6 +193,19 @@ void lsp_response_free(LSPResponse *r) {
case LSP_REQUEST_FORMATTING:
arr_free(r->data.formatting.edits);
break;
+ case LSP_REQUEST_CODE_ACTION: {
+ LSPResponseCodeAction *c = &r->data.code_action;
+ arr_foreach_ptr(c->actions, LSPCodeAction, action) {
+ switch (action->command.kind) {
+ case LSP_COMMAND_WORKSPACE_EDIT:
+ lsp_workspace_edit_free(&action->command.data.edit);
+ break;
+ default: break;
+ }
+ lsp_workspace_edit_free(&action->edit);
+ }
+ arr_free(c->actions);
+ } break;
default:
break;
}
@@ -237,6 +258,8 @@ static bool lsp_supports_request(LSP *lsp, const LSPRequest *request) {
return cap->open_close_support;
case LSP_REQUEST_DID_CHANGE:
return cap->sync_support;
+ case LSP_REQUEST_CODE_ACTION:
+ return cap->code_action_support;
case LSP_REQUEST_INITIALIZE:
case LSP_REQUEST_INITIALIZED:
case LSP_REQUEST_CANCEL:
@@ -320,6 +343,7 @@ static bool request_type_is_notification(LSPRequestType type) {
case LSP_REQUEST_DOCUMENT_LINK:
case LSP_REQUEST_FORMATTING:
case LSP_REQUEST_RANGE_FORMATTING:
+ case LSP_REQUEST_CODE_ACTION:
return false;
}
assert(0);
@@ -447,7 +471,7 @@ static bool lsp_receive(LSP *lsp, size_t max_size) {
arr_hdr_(lsp->received_data)->len = (u32)received_so_far;
lsp->received_data[received_so_far] = '\0';// null terminate
#if LSP_SHOW_S2C
- const int limit = 1000;
+ const int limit = 10000;
debug_println("%s%.*s%s%s",term_italics(stdout),limit,lsp->received_data,
strlen(lsp->received_data) > (size_t)limit ? "..." : "",
term_clear(stdout));
@@ -474,10 +498,6 @@ static bool lsp_receive(LSP *lsp, size_t max_size) {
json_free(&json);
}
size_t leftover_data_len = arr_len(lsp->received_data) - (response_offset + response_size);
-
- //printf("arr_cap = %u response_offset = %u, response_size = %zu, leftover len = %u\n",
- // arr_hdr_(lsp->received_data)->cap,
- // response_offset, response_size, leftover_data_len);
memmove(lsp->received_data, lsp->received_data + response_offset + response_size,
leftover_data_len);
arr_set_len(lsp->received_data, leftover_data_len);
diff --git a/lsp.h b/lsp.h
index c267283..a7fa9b0 100644
--- a/lsp.h
+++ b/lsp.h
@@ -97,6 +97,7 @@ typedef enum {
LSP_REQUEST_LOG_MESSAGE, //< window/logMessage
LSP_REQUEST_WORKSPACE_FOLDERS, //< workspace/workspaceFolders - NOTE: this is handled directly in lsp-parse.c (because it only needs information from the LSP struct)
LSP_REQUEST_PUBLISH_DIAGNOSTICS, //< textDocument/publishDiagnostics
+ LSP_REQUEST_CODE_ACTION, //< textDocument/codeAction
} LSPRequestType;
typedef enum {
@@ -170,6 +171,8 @@ typedef struct {
/// URI to description of code
/// e.g. for Rust's E0621, this would be https://doc.rust-lang.org/error_codes/E0621.html
LSPString code_description_uri;
+ // raw diagnostic object, to be used with textDocument/codeAction
+ LSPString raw;
} LSPDiagnostic;
typedef struct {
@@ -259,6 +262,12 @@ typedef struct {
} LSPRequestFormatting;
typedef struct {
+ LSPDocumentID document;
+ LSPRange range;
+ LSPString *raw_diagnostics;
+} LSPRequestCodeAction;
+
+typedef struct {
LSPMessageType type;
/// LSP requests/responses tend to have a lot of strings.
/// to avoid doing a ton of allocations+frees,
@@ -296,6 +305,7 @@ typedef struct {
LSPRequestPublishDiagnostics publish_diagnostics;
// LSP_REQUEST_FORMATTING and LSP_REQUEST_RANGE_FORMATTING
LSPRequestFormatting formatting;
+ LSPRequestCodeAction code_action;
} data;
} LSPRequest;
@@ -565,6 +575,35 @@ typedef struct {
LSPTextEdit *edits;
} LSPResponseFormatting;
+typedef enum {
+ LSP_COMMAND_NONE,
+ LSP_COMMAND_WORKSPACE_EDIT,
+} LSPCommandKind;
+
+typedef struct {
+ LSPCommandKind kind;
+ union {
+ LSPWorkspaceEdit edit;
+ } data;
+} LSPCommand;
+
+typedef enum {
+ LSP_CODE_ACTION_OTHER,
+ LSP_CODE_ACTION_QUICKFIX,
+} LSPCodeActionKind;
+
+typedef struct {
+ LSPString name;
+ LSPWorkspaceEdit edit;
+ LSPCodeActionKind kind;
+ bool is_preferred;
+ LSPCommand command;
+} LSPCodeAction;
+
+typedef struct {
+ LSPCodeAction *actions;
+} LSPResponseCodeAction;
+
typedef struct {
LSPMessageBase base;
/// the request which this is a response to
@@ -586,6 +625,7 @@ typedef struct {
LSPResponseDocumentLink document_link;
/// `LSP_REQUEST_FORMATTING` or `LSP_REQUEST_RANGE_FORMATTING`
LSPResponseFormatting formatting;
+ LSPResponseCodeAction code_action;
} data;
} LSPResponse;
@@ -629,6 +669,7 @@ typedef struct {
bool document_link_support;
bool formatting_support;
bool range_formatting_support;
+ bool code_action_support;
} LSPCapabilities;
typedef struct LSP LSP;
@@ -669,6 +710,7 @@ void lsp_register_language(u64 id, const char *lsp_identifier);
// if clear = true, the error will be cleared.
// you can set error = NULL, error_size = 0, clear = true to just clear the error
bool lsp_get_error(LSP *lsp, char *error, size_t error_size, bool clear);
+void lsp_response_free(LSPResponse *response);
void lsp_message_free(LSPMessage *message);
u32 lsp_document_id(LSP *lsp, const char *path);
// returned pointer lives as long as lsp.
@@ -935,8 +977,11 @@ size_t json_escape_to(char *out, size_t out_sz, const char *in);
char *json_escape(const char *str);
LSPString lsp_response_add_json_string(LSPResponse *response, const JSON *json, JSONString string);
LSPString lsp_request_add_json_string(LSPRequest *request, const JSON *json, JSONString string);
+void lsp_workspace_edit_free(LSPWorkspaceEdit *edit);
/// free resources used by lsp-write.c
void lsp_write_quit(void);
+// convert JSON value back into string
+char *json_reserialize(const JSON *json, JSONValue value);
/// print server-to-client communication
#define LSP_SHOW_S2C 0
diff --git a/main.c b/main.c
index 0d0732c..718430c 100644
--- a/main.c
+++ b/main.c
@@ -1,6 +1,5 @@
/*
FUTURE FEATURES:
-- LSP "actions"
- wrap-text command
- path-specific extensions
- more tests
@@ -95,6 +94,7 @@ the first character can be interpreted specially if it is one of the following:
#include "menu.c"
#include "ide-autocomplete.c"
#include "ide-signature-help.c"
+#include "ide-code-action.c"
#include "ide-rename-symbol.c"
#include "ide-hover.c"
#include "ide-definitions.c"
@@ -587,6 +587,7 @@ int main(int argc, char **argv) {
find_init(ted);
macros_init(ted);
definitions_init(ted);
+ code_action_init(ted);
autocomplete_init(ted);
format_init(ted);
signature_help_init(ted);
@@ -968,6 +969,7 @@ int main(int argc, char **argv) {
LSP *lsp = ted->lsps[i];
LSPMessage message = {0};
while (lsp_next_message(lsp, &message)) {
+ bool message_moved = false;
switch (message.type) {
case LSP_REQUEST: {
LSPRequest *r = &message.request;
@@ -1005,10 +1007,15 @@ int main(int argc, char **argv) {
highlights_process_lsp_response(ted, r);
usages_process_lsp_response(ted, r);
document_link_process_lsp_response(ted, r);
+ if (code_action_process_lsp_response(ted, r)) {
+ message_moved = true;
+ break;
+ }
rename_symbol_process_lsp_response(ted, r);
} break;
}
- lsp_message_free(&message);
+ if (!message_moved)
+ lsp_message_free(&message);
}
}
@@ -1101,6 +1108,7 @@ int main(int argc, char **argv) {
float y1 = padding;
node_frame(ted, node, rect4(x1, y1, x2, y));
autocomplete_frame(ted);
+ code_action_frame(ted);
signature_help_frame(ted);
hover_frame(ted, frame_dt);
definitions_frame(ted);
@@ -1293,6 +1301,7 @@ int main(int argc, char **argv) {
signature_help_quit(ted);
autocomplete_quit(ted);
format_quit(ted);
+ code_action_quit(ted);
highlights_quit(ted);
usages_quit(ted);
session_write(ted);
diff --git a/pre-commit.sh b/pre-commit.sh
index 3553140..ccf1943 100755
--- a/pre-commit.sh
+++ b/pre-commit.sh
@@ -6,5 +6,5 @@
# Check for trailing whitespace
git grep -In ' $' && { echo 'Fix trailing whitespace!'; exit 1; }
-git grep -In '\w'"$(printf '\t')"'$' && { echo 'Fix trailing whitespace!'; exit 1; }
+git grep -In '\S'"$(printf '\t')" && { echo 'Fix trailing whitespace!'; exit 1; }
exit 0
diff --git a/syntax.c b/syntax.c
index 1803730..0996919 100644
--- a/syntax.c
+++ b/syntax.c
@@ -2139,7 +2139,7 @@ static void syntax_highlight_css(SyntaxState *state_ptr, const char32_t *line, u
dealt_with = true;
if (!in_braces && char_types) {
goto handle_pseudo;
- } else {
+ } else {
--i; // we'll increment i from the for loop
}
break;
diff --git a/ted-internal.h b/ted-internal.h
index a9de122..6875793 100644
--- a/ted-internal.h
+++ b/ted-internal.h
@@ -260,6 +260,9 @@ typedef struct Definitions Definitions;
/// "highlight" information from LSP server
typedef struct Highlights Highlights;
+/// "code action" information from LSP server
+typedef struct CodeAction CodeAction;
+
typedef struct Macro Macro;
typedef struct LoadedFont LoadedFont;
@@ -364,6 +367,7 @@ struct Ted {
Usages *usages;
RenameSymbol *rename_symbol;
Formatting *formatting;
+ CodeAction *code_action;
/// process ID
int pid;
@@ -448,8 +452,25 @@ struct Ted {
u64 edit_notify_id;
EditNotifyInfo *edit_notifys;
+#if HAS_INOTIFY
+ // 16384 = default inotify queue size
+ char inotify_event_buf[16384 * sizeof(struct inotify_event)];
+#endif
};
+typedef struct {
+ MessageType severity;
+ // start position
+ BufferPos pos;
+ // end position
+ BufferPos end;
+ char *message;
+ // may be NULL
+ char *url;
+ // raw JSON object (for textDocument/codeAction)
+ char *raw;
+} Diagnostic;
+
// === buffer.c ===
/// create a new empty buffer with no file name
TextBuffer *buffer_new(Ted *ted);
@@ -508,6 +529,7 @@ int buffer_inotify_watch(TextBuffer *buffer);
/// inform buffer that its watch on the Ted's inotify was modified
void buffer_set_inotify_modified(TextBuffer *buffer);
#endif
+const Diagnostic *buffer_diagnostics(TextBuffer *buffer);
// === build.c ===
void build_frame(Ted *ted, float x1, float y1, float x2, float y2);
@@ -634,6 +656,12 @@ void autocomplete_quit(Ted *ted);
void autocomplete_frame(Ted *ted);
void autocomplete_process_lsp_response(Ted *ted, const LSPResponse *response);
+// === ide-code-action.c ===
+void code_action_init(Ted *ted);
+void code_action_quit(Ted *ted);
+void code_action_frame(Ted *ted);
+bool code_action_process_lsp_response(Ted *ted, const LSPResponse *response);
+
// === ide-definitions.c ===
void definitions_init(Ted *ted);
/// go to the definition of `name`.
@@ -771,5 +799,7 @@ void ted_free_fonts(Ted *ted);
void ted_process_publish_diagnostics(Ted *ted, LSP *lsp, LSPRequest *request);
/// check inotify fd for events
void ted_check_inotify(Ted *ted);
+/// perform LSP WorkspaceEdit
+void ted_perform_workspace_edit(Ted *ted, LSP *lsp, const LSPResponse *response, const LSPWorkspaceEdit *edit);
#endif // TED_INTERNAL_H_
diff --git a/ted.c b/ted.c
index b5403c6..4c96e86 100644
--- a/ted.c
+++ b/ted.c
@@ -982,16 +982,96 @@ void ted_test(Ted *ted) {
printf("all good as far as i know :3\n");
}
+void ted_perform_workspace_edit(Ted *ted, LSP *lsp, const LSPResponse *response, const LSPWorkspaceEdit *edit) {
+ TextBuffer *const start_buffer = ted_active_buffer(ted);
+ arr_foreach_ptr(edit->changes, const LSPWorkspaceChange, change) {
+ if (change->type == LSP_CHANGE_DELETE && change->data.delete.recursive) {
+ ted_error(ted, "refusing to perform edit because it involves a recursive deletion\n"
+ "I'm too scared to go through with this");
+ return;
+ }
+ }
+
+ arr_foreach_ptr(edit->changes, const LSPWorkspaceChange, change) {
+ switch (change->type) {
+ 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;
+
+ TextBuffer *buffer = ted_get_buffer_with_file(ted, path);
+ // chain all edits together so they can be undone with one ctrl+z
+ buffer_start_edit_chain(buffer);
+
+ if (!buffer) {
+ // this should never happen since we just
+ // successfully opened it
+ assert(0);
+ goto done;
+ }
+
+ buffer_apply_lsp_text_edits(buffer, response, change_data->edits, arr_len(change_data->edits));
+ }
+ break;
+ case LSP_CHANGE_RENAME: {
+ const LSPWorkspaceChangeRename *rename = &change->data.rename;
+ const char *old = lsp_document_path(lsp, rename->old);
+ const char *new = lsp_document_path(lsp, rename->new);
+ FsType new_type = fs_path_type(new);
+ if (new_type == FS_DIRECTORY) {
+ ted_error(ted, "Aborting rename since it's asking to overwrite a directory.");
+ goto done;
+ }
+
+ if (rename->ignore_if_exists && new_type != FS_NON_EXISTENT) {
+ break;
+ }
+ if (!rename->overwrite && new_type != FS_NON_EXISTENT) {
+ ted_error(ted, "Aborting rename since it would overwrite a file.");
+ goto done;
+ }
+ os_rename_overwrite(old, new);
+ if (ted_close_buffer_with_file(ted, old))
+ ted_open_file(ted, new);
+ } break;
+ case LSP_CHANGE_DELETE: {
+ const LSPWorkspaceChangeDelete *delete = &change->data.delete;
+ const char *path = lsp_document_path(lsp, delete->document);
+ remove(path);
+ ted_close_buffer_with_file(ted, path);
+ } break;
+ case LSP_CHANGE_CREATE: {
+ const LSPWorkspaceChangeCreate *create = &change->data.create;
+ const char *path = lsp_document_path(lsp, create->document);
+ FILE *fp = fopen(path, create->overwrite ? "wb" : "ab");
+ if (fp) fclose(fp);
+ ted_open_file(ted, path);
+ } break;
+ }
+ }
+ done:
+
+ {
+ // end all edit chains in all buffers
+ // they're almost definitely all created by us
+ arr_foreach_ptr(ted->buffers, TextBufferPtr, pbuffer) {
+ buffer_end_edit_chain(*pbuffer);
+ }
+
+ ted_save_all(ted);
+ }
+ ted_switch_to_buffer(ted, start_buffer);
+}
+
#if HAS_INOTIFY
void ted_check_inotify(Ted *ted) {
// see buffer_externally_changed definition for why this exists
if (ted->inotify_fd == -1) return;
int *watches_modified = NULL;
- char events[16384 * sizeof(struct inotify_event)];
- ssize_t bytes = read(ted->inotify_fd, events, sizeof events);
+ ssize_t bytes = read(ted->inotify_fd, ted->inotify_event_buf, sizeof ted->inotify_event_buf);
for (int i = 0; i + (int)sizeof(struct inotify_event) <= bytes; i += (int)sizeof(struct inotify_event)) {
struct inotify_event event;
- memcpy(&event, &events[i * (int)sizeof event], sizeof event);
+ memcpy(&event, &ted->inotify_event_buf[i], sizeof event);
if (event.mask & INOTIFY_MASK) {
bool new = true;
arr_foreach_ptr(watches_modified, int, w) {
@@ -1002,12 +1082,10 @@ void ted_check_inotify(Ted *ted) {
}
if (new) arr_add(watches_modified, event.wd);
}
- i += (int)event.len; // should always be 0 in theory...
+ i += (int)event.len; // should always be 0 in theory given our event types...
}
// ideally we'd read more events in a loop here but then we'd hang if someone is constantly
// changing the file. probably no good way of dealing with that though.
- // for what it's worth, 16384 is the default max inotify queue size, so we should always
- // read all the events with that one read call.
arr_foreach_ptr(ted->buffers, TextBufferPtr, pbuffer) {
int watch = buffer_inotify_watch(*pbuffer);
arr_foreach_ptr(watches_modified, int, w) {
diff --git a/ted.cfg b/ted.cfg
index 44c4b6a..09c2e0e 100644
--- a/ted.cfg
+++ b/ted.cfg
@@ -321,7 +321,6 @@ Ctrl+Shift+r = :reload-all
# to set the language of the current buffer to Rust, for example. This might be useful if
# one file extension could be multiple different languages.
# Here are the numbers corresponding to each language we have syntax highlighting for:
-# 0 Plain text (no syntax highlighting)
# 1 C
# 2 C++
# 3 Rust
@@ -338,6 +337,10 @@ Ctrl+Shift+r = :reload-all
# 14 JSON
# 15 XML
# 16 GLSL
+# 17 Plain text
+# 18 CSS
+# 19 GdScript (Godot)
+# 20 C#
# -1 Guess from the file extension (default)
# IDE features
@@ -346,6 +349,10 @@ Ctrl+Space = :autocomplete
Ctrl+Shift+Space = :autocomplete-back
Ctrl+u = :find-usages
Ctrl+r = :rename-symbol
+# Show "code action" suggestions (refactoring, quick fixes, etc.) / move code action cursor forwards
+Alt+Space = :code-action
+# Move code action cursor backwards
+Alt+Shift+Space = :code-action-prev
Ctrl+z = :undo
Ctrl+Shift+z = :redo
diff --git a/ted.h b/ted.h
index 68814d9..9fb6aa8 100644
--- a/ted.h
+++ b/ted.h
@@ -22,7 +22,7 @@ extern "C" {
#include "command.h"
/// Version number
-#define TED_VERSION "2.8.4"
+#define TED_VERSION "2.9.0"
/// Maximum path size ted handles.
#define TED_PATH_MAX 1024
/// Config filename
@@ -928,6 +928,20 @@ void autocomplete_prev(Ted *ted);
/// close completion menu
void autocomplete_close(Ted *ted);
+// === ide-code-action.c ==
+/// Show suggested code actions
+void code_action_open(Ted *ted);
+/// Hide suggested code actions
+void code_action_close(Ted *ted);
+/// Are code actions being shown?
+bool code_action_is_open(Ted *ted);
+/// Select current code action
+void code_action_select(Ted *ted);
+/// Move code action cursor forwards
+void code_action_next(Ted *ted);
+/// Move code action cursor backwards
+void code_action_prev(Ted *ted);
+
// === ide-definitions.c ===
/// cancel the last go-to-definition / find symbols request.
void definition_cancel_lookup(Ted *ted);
diff --git a/ui.c b/ui.c
index ce83c35..c53233e 100644
--- a/ui.c
+++ b/ui.c
@@ -648,7 +648,7 @@ void file_selector_render(Ted *ted, FileSelector *fs) {
{
const char *cwd = fs->cwd;
const float text_width = text_get_size_vec2(font, cwd).x;
- TextRenderState state = text_render_state_default;
+ TextRenderState state = text_render_state_default;
state.x = bounds.pos.x;
if (text_width > bounds.size.x) {
// very long cwd
diff --git a/windows_installer/ted/ted.vdproj b/windows_installer/ted/ted.vdproj
index 9bf631c..b9e5a67 100644
--- a/windows_installer/ted/ted.vdproj
+++ b/windows_installer/ted/ted.vdproj
@@ -620,15 +620,15 @@
{
"Name" = "8:Microsoft Visual Studio"
"ProductName" = "8:ted"
- "ProductCode" = "8:{C36CA9E0-6E99-42B9-9F94-8869D5DFD9D9}"
- "PackageCode" = "8:{4EF1D7F9-DEA9-44C7-9CDA-9B26348DE375}"
+ "ProductCode" = "8:{943A950E-7D69-4097-B889-3015A528059E}"
+ "PackageCode" = "8:{F551F5C0-E284-4DC1-B334-88F6AAFE255B}"
"UpgradeCode" = "8:{844F6C2B-DF3B-4A81-9BD5-603401BBA651}"
"AspNetVersion" = "8:2.0.50727.0"
"RestartWWWService" = "11:FALSE"
"RemovePreviousVersions" = "11:TRUE"
"DetectNewerInstalledVersion" = "11:FALSE"
"InstallAllUsers" = "11:FALSE"
- "ProductVersion" = "8:25.09.2800"
+ "ProductVersion" = "8:25.09.3000"
"Manufacturer" = "8:ted"
"ARPHELPTELEPHONE" = "8:"
"ARPHELPLINK" = "8:"