summaryrefslogtreecommitdiff
path: root/ide-code-action.c
diff options
context:
space:
mode:
Diffstat (limited to 'ide-code-action.c')
-rw-r--r--ide-code-action.c260
1 files changed, 260 insertions, 0 deletions
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);
+ }
+ }
+ }
+}