summaryrefslogtreecommitdiff
path: root/autocomplete.c
diff options
context:
space:
mode:
authorpommicket <pommicket@gmail.com>2022-12-29 18:44:37 -0500
committerpommicket <pommicket@gmail.com>2022-12-29 18:44:37 -0500
commitd77782564bf0a090a4e7fa4f4c4bb685383275dc (patch)
treea4bc3c0bf142c663ea7f293b4381927adc896f1c /autocomplete.c
parent5f7af06a9085835a3d1ad3a51374c245859d7d97 (diff)
textDocument/definition for ctrl+click
Diffstat (limited to 'autocomplete.c')
-rw-r--r--autocomplete.c545
1 files changed, 0 insertions, 545 deletions
diff --git a/autocomplete.c b/autocomplete.c
deleted file mode 100644
index f1c3c13..0000000
--- a/autocomplete.c
+++ /dev/null
@@ -1,545 +0,0 @@
-#define TAGS_MAX_COMPLETIONS 200 // max # of tag completions to scroll through
-#define AUTOCOMPLETE_NCOMPLETIONS_VISIBLE 10 // max # of completions to show at once
-
-static void autocomplete_clear_completions(Ted *ted) {
- Autocomplete *ac = &ted->autocomplete;
- arr_foreach_ptr(ac->completions, Autocompletion, completion) {
- free(completion->label);
- free(completion->text);
- free(completion->filter);
- free(completion->detail);
- free(completion->documentation);
- }
- arr_clear(ac->completions);
- arr_clear(ac->suggested);
-}
-
-// do the actual completion
-static void autocomplete_complete(Ted *ted, Autocompletion completion) {
- TextBuffer *buffer = ted->active_buffer;
- buffer_start_edit_chain(buffer); // don't merge with other edits
- if (is_word(buffer_char_before_cursor(buffer)))
- buffer_backspace_words_at_cursor(buffer, 1); // delete whatever text was already typed
- buffer_insert_utf8_at_cursor(buffer, completion.text);
- buffer_end_edit_chain(buffer);
- autocomplete_close(ted);
-}
-
-static void autocomplete_select_cursor_completion(Ted *ted) {
- Autocomplete *ac = &ted->autocomplete;
- if (ac->open) {
- size_t nsuggestions = arr_len(ac->suggested);
- if (nsuggestions) {
- i64 cursor = mod_i64(ac->cursor, (i64)nsuggestions);
- autocomplete_complete(ted, ac->completions[ac->suggested[cursor]]);
- autocomplete_close(ted);
- }
- }
-}
-
-static void autocomplete_correct_scroll(Ted *ted) {
- Autocomplete *ac = &ted->autocomplete;
- i32 scroll = ac->scroll;
- scroll = min_i32(scroll, (i32)arr_len(ac->suggested) - AUTOCOMPLETE_NCOMPLETIONS_VISIBLE);
- scroll = max_i32(scroll, 0);
- ac->scroll = scroll;
-}
-
-void autocomplete_scroll(Ted *ted, i32 by) {
- Autocomplete *ac = &ted->autocomplete;
- ac->scroll += by;
- autocomplete_correct_scroll(ted);
-}
-
-static void autocomplete_move_cursor(Ted *ted, i32 by) {
- Autocomplete *ac = &ted->autocomplete;
- u32 ncompletions = arr_len(ac->suggested);
- if (ncompletions == 0)
- return;
- i32 cursor = ac->cursor;
- cursor += by;
- cursor = (i32)mod_i32(cursor, (i32)ncompletions);
- ac->cursor = cursor;
- ac->scroll = ac->cursor - AUTOCOMPLETE_NCOMPLETIONS_VISIBLE / 2;
- autocomplete_correct_scroll(ted);
-}
-
-static void autocomplete_next(Ted *ted) {
- autocomplete_move_cursor(ted, 1);
-}
-
-static void autocomplete_prev(Ted *ted) {
- autocomplete_move_cursor(ted, -1);
-}
-
-void autocomplete_close(Ted *ted) {
- Autocomplete *ac = &ted->autocomplete;
- if (ac->open) {
- ac->open = false;
- ac->waiting_for_lsp = false;
- autocomplete_clear_completions(ted);
- }
-}
-
-void autocomplete_update_suggested(Ted *ted) {
- Autocomplete *ac = &ted->autocomplete;
- arr_clear(ac->suggested);
- char *word = str32_to_utf8_cstr(
- buffer_word_at_cursor(ted->active_buffer)
- );
- for (u32 i = 0; i < arr_len(ac->completions); ++i) {
- Autocompletion *completion = &ac->completions[i];
- if (str_has_prefix(completion->filter, word))
- arr_add(ac->suggested, i); // suggest this one
- }
- free(word);
-}
-
-static bool autocomplete_using_lsp(Ted *ted) {
- return ted->active_buffer && buffer_lsp(ted->active_buffer) != NULL;
-}
-
-static void autocomplete_no_suggestions(Ted *ted) {
- Autocomplete *ac = &ted->autocomplete;
- if (ac->trigger == TRIGGER_INVOKED)
- ted->cursor_error_time = time_get_seconds();
- autocomplete_close(ted);
-}
-
-static void autocomplete_send_completion_request(Ted *ted, TextBuffer *buffer, BufferPos pos, uint32_t trigger) {
- LSP *lsp = buffer_lsp(buffer);
- Autocomplete *ac = &ted->autocomplete;
-
- LSPRequest request = {
- .type = LSP_REQUEST_COMPLETION
- };
-
- LSPCompletionTriggerKind lsp_trigger = LSP_TRIGGER_CHARACTER;
- switch (trigger) {
- case TRIGGER_INVOKED: lsp_trigger = LSP_TRIGGER_INVOKED; break;
- case TRIGGER_INCOMPLETE: lsp_trigger = LSP_TRIGGER_INCOMPLETE; break;
- }
-
- request.data.completion = (LSPRequestCompletion) {
- .position = {
- .document = lsp_document_id(lsp, buffer->filename),
- .pos = buffer_pos_to_lsp_position(buffer, pos)
- },
- .context = {
- .trigger_kind = lsp_trigger,
- .trigger_character = {0},
- }
- };
- if (trigger < UNICODE_CODE_POINTS)
- unicode_utf32_to_utf8(request.data.completion.context.trigger_character, trigger);
- if (lsp_send_request(lsp, &request)) {
- ac->waiting_for_lsp = true;
- ac->lsp_request_time = ted->frame_time;
- // *technically sepaking* this can mess things up if a complete
- // list arrives only after the user has typed some stuff
- // (in that case we'll send a TriggerKind = incomplete request even though it makes no sense).
- // but i don't think any servers will have a problem with that.
- ac->is_list_complete = false;
- }
-}
-
-static void autocomplete_find_completions(Ted *ted, uint32_t trigger) {
- Autocomplete *ac = &ted->autocomplete;
- TextBuffer *buffer = ted->active_buffer;
- BufferPos pos = buffer->cursor_pos;
- if (buffer_pos_eq(pos, ac->last_pos))
- return; // no need to update completions.
- ac->trigger = trigger;
- ac->last_pos = pos;
-
- LSP *lsp = buffer_lsp(buffer);
- if (lsp) {
- if (ac->is_list_complete && trigger == TRIGGER_INCOMPLETE) {
- // the list of completions we got from the LSP server is complete,
- // so we just need to call autocomplete_update_suggested,
- // we don't need to send a new request.
- } else {
- autocomplete_send_completion_request(ted, buffer, pos, trigger);
- }
- } else {
- // tag completion
- autocomplete_clear_completions(ted);
-
- char *word_at_cursor = str32_to_utf8_cstr(buffer_word_at_cursor(buffer));
- char **completions = calloc(TAGS_MAX_COMPLETIONS, sizeof *completions);
- u32 ncompletions = (u32)tags_beginning_with(ted, word_at_cursor, completions, TAGS_MAX_COMPLETIONS);
- free(word_at_cursor);
-
- arr_set_len(ac->completions, ncompletions);
-
- for (size_t i = 0; i < ncompletions; ++i) {
- ac->completions[i].label = completions[i];
- ac->completions[i].text = str_dup(completions[i]);
- ac->completions[i].filter = str_dup(completions[i]);
- arr_add(ac->suggested, (u32)i);
- }
- free(completions);
-
- // if we got the full list of tags beginning with `word_at_cursor`,
- // then we don't need to call `tags_beginning_with` again.
- ac->is_list_complete = ncompletions == TAGS_MAX_COMPLETIONS;
- }
-
- autocomplete_update_suggested(ted);
-}
-
-static SymbolKind lsp_completion_kind_to_ted(LSPCompletionKind kind) {
- switch (kind) {
- case LSP_COMPLETION_TEXT:
- case LSP_COMPLETION_MODULE:
- case LSP_COMPLETION_UNIT:
- case LSP_COMPLETION_COLOR:
- case LSP_COMPLETION_FILE:
- case LSP_COMPLETION_REFERENCE:
- case LSP_COMPLETION_FOLDER:
- case LSP_COMPLETION_OPERATOR:
- return SYMBOL_OTHER;
-
- case LSP_COMPLETION_METHOD:
- case LSP_COMPLETION_FUNCTION:
- case LSP_COMPLETION_CONSTRUCTOR:
- return SYMBOL_FUNCTION;
-
- case LSP_COMPLETION_FIELD:
- case LSP_COMPLETION_PROPERTY:
- return SYMBOL_FIELD;
-
- case LSP_COMPLETION_VARIABLE:
- return SYMBOL_VARIABLE;
-
- case LSP_COMPLETION_CLASS:
- case LSP_COMPLETION_INTERFACE:
- case LSP_COMPLETION_ENUM:
- case LSP_COMPLETION_STRUCT:
- case LSP_COMPLETION_EVENT:
- case LSP_COMPLETION_TYPEPARAMETER:
- return SYMBOL_TYPE;
-
- case LSP_COMPLETION_VALUE:
- case LSP_COMPLETION_ENUMMEMBER:
- case LSP_COMPLETION_CONSTANT:
- return SYMBOL_CONSTANT;
-
- case LSP_COMPLETION_KEYWORD:
- case LSP_COMPLETION_SNIPPET:
- return SYMBOL_KEYWORD;
- }
- assert(0);
- return SYMBOL_OTHER;
-}
-
-
-static void autocomplete_process_lsp_response(Ted *ted, const LSPResponse *response) {
- const LSPRequest *request = &response->request;
- if (request->type != LSP_REQUEST_COMPLETION)
- return;
-
- Autocomplete *ac = &ted->autocomplete;
- ac->waiting_for_lsp = false;
- if (!ac->open) {
- // user hit escape or down or something before completions arrived.
- return;
- }
-
-
- const LSPResponseCompletion *completion = &response->data.completion;
- size_t ncompletions = arr_len(completion->items);
- arr_set_len(ac->completions, ncompletions);
- for (size_t i = 0; i < ncompletions; ++i) {
- const LSPCompletionItem *lsp_completion = &completion->items[i];
- Autocompletion *ted_completion = &ac->completions[i];
- ted_completion->label = str_dup(lsp_response_string(response, lsp_completion->label));
- ted_completion->filter = str_dup(lsp_response_string(response, lsp_completion->filter_text));
- // NOTE: here we don't deal with snippets.
- // right now we are sending "snippetSupport: false" in the capabilities,
- // so this should be okay.
- ted_completion->text = str_dup(lsp_response_string(response, lsp_completion->text_edit.new_text));
- const char *detail = lsp_response_string(response, lsp_completion->detail);
- ted_completion->detail = *detail ? str_dup(detail) : NULL;
- ted_completion->kind = lsp_completion_kind_to_ted(lsp_completion->kind);
- ted_completion->deprecated = lsp_completion->deprecated;
- const char *documentation = lsp_response_string(response, lsp_completion->documentation);
- ted_completion->documentation = *documentation ? str_dup(documentation) : NULL;
-
- }
- ac->is_list_complete = completion->is_complete;
-
- autocomplete_update_suggested(ted);
- switch (arr_len(ac->suggested)) {
- case 0:
- autocomplete_no_suggestions(ted);
- return;
- case 1:
- // if autocomplete was invoked by Ctrl+Space, and there's only one completion, select it.
- if (ac->trigger == TRIGGER_INVOKED)
- autocomplete_complete(ted, ac->completions[ac->suggested[0]]);
- return;
- }
-}
-
-// open autocomplete
-// trigger should either be a character (e.g. '.') or one of the TRIGGER_* constants.
-static void autocomplete_open(Ted *ted, uint32_t trigger) {
- Autocomplete *ac = &ted->autocomplete;
- if (ac->open) return;
- if (!ted->active_buffer) return;
- TextBuffer *buffer = ted->active_buffer;
- if (!buffer->filename) return;
- if (buffer->view_only) return;
-
- ted->cursor_error_time = 0;
- ac->last_pos = (BufferPos){0,0};
- ac->cursor = 0;
- autocomplete_find_completions(ted, trigger);
-
- switch (arr_len(ac->completions)) {
- case 0:
- if (autocomplete_using_lsp(ted)) {
- ac->open = true;
- } else {
- autocomplete_no_suggestions(ted);
- }
- break;
- case 1:
- autocomplete_complete(ted, ac->completions[0]);
- // (^ this calls autocomplete_close)
- break;
- default:
- // open autocomplete menu
- ac->open = true;
- break;
- }
-}
-
-static char symbol_kind_icon(SymbolKind k) {
- switch (k) {
- case SYMBOL_FUNCTION:
- return 'f';
- case SYMBOL_FIELD:
- return 'm';
- case SYMBOL_TYPE:
- return 't';
- case SYMBOL_CONSTANT:
- return 'c';
- case SYMBOL_VARIABLE:
- return 'v';
- case SYMBOL_KEYWORD:
- case SYMBOL_OTHER:
- return ' ';
- }
- assert(0);
- return ' ';
-}
-
-static void autocomplete_frame(Ted *ted) {
- Autocomplete *ac = &ted->autocomplete;
- if (!ac->open) return;
-
- TextBuffer *buffer = ted->active_buffer;
- Font *font = ted->font;
- float char_height = text_font_char_height(font);
- Settings const *settings = buffer_settings(buffer);
- u32 const *colors = settings->colors;
- float const padding = settings->padding;
-
- autocomplete_find_completions(ted, TRIGGER_INCOMPLETE);
-
- size_t ncompletions = arr_len(ac->suggested);
-
- if (ac->waiting_for_lsp && ncompletions == 0) {
- struct timespec now = ted->frame_time;
- if (timespec_sub(now, ac->lsp_request_time) < 0.2) {
- // don't show "Loading..." unless we've actually been loading for a bit of time
- return;
- }
- }
-
- if (!ac->waiting_for_lsp && ncompletions == 0) {
- // no completions. close menu.
- autocomplete_close(ted);
- return;
- }
-
- ac->cursor = ncompletions ? (i32)mod_i64(ac->cursor, (i64)ncompletions) : 0;
-
- autocomplete_correct_scroll(ted);
- i32 scroll = ac->scroll;
- u32 ncompletions_visible = min_u32((u32)ncompletions, AUTOCOMPLETE_NCOMPLETIONS_VISIBLE);
-
- float menu_width = 400, menu_height = (float)ncompletions_visible * char_height;
-
- if (ac->waiting_for_lsp && ncompletions == 0) {
- menu_height = 200.f;
- }
-
- v2 cursor_pos = buffer_pos_to_pixels(buffer, buffer->cursor_pos);
- bool open_up = cursor_pos.y > 0.5f * (buffer->y1 + buffer->y2); // should the completion menu open upwards?
- bool open_left = cursor_pos.x > 0.5f * (buffer->x1 + buffer->x2);
- float x = cursor_pos.x, start_y = cursor_pos.y;
- if (open_left) x -= menu_width;
- if (open_up)
- start_y -= menu_height;
- else
- start_y += char_height; // put menu below cursor
- {
- Rect menu_rect = rect(V2(x, start_y), V2(menu_width, menu_height));
- gl_geometry_rect(menu_rect, colors[COLOR_AUTOCOMPLETE_BG]);
- gl_geometry_rect_border(menu_rect, 1, colors[COLOR_AUTOCOMPLETE_BORDER]);
- ac->rect = menu_rect;
- }
-
- i32 mouse_entry = scroll + (i32)((ted->mouse_pos.y - start_y) / char_height);
-
- Autocompletion *document = NULL;
- if (ncompletions) {
- assert(ac->cursor >= 0 && ac->cursor < (i32)ncompletions);
- // highlight cursor entry
- Rect r = rect(V2(x, start_y + (float)(ac->cursor - scroll) * char_height), V2(menu_width, char_height));
- if (rect_contains_point(ac->rect, rect_center(r))) {
- gl_geometry_rect(r, colors[COLOR_AUTOCOMPLETE_HL]);
- document = &ac->completions[ac->suggested[ac->cursor]];
- }
- }
- if (mouse_entry >= 0 && mouse_entry < (i32)ncompletions
- && rect_contains_point(ac->rect, ted->mouse_pos)) {
- // highlight moused over entry
- Rect r = rect(V2(x, start_y + (float)(mouse_entry - scroll) * char_height), V2(menu_width, char_height));
- gl_geometry_rect(r, colors[COLOR_AUTOCOMPLETE_HL]);
- ted->cursor = ted->cursor_hand;
- document = &ac->completions[ac->suggested[mouse_entry]];
- }
-
- float border_thickness = settings->border_thickness;
-
-
- if (document && document->documentation) {
- // document that entry!!
-
- // we've got some wacky calculations to figure out the
- // bounding rect for the documentation
- float doc_width = open_left ? ac->rect.pos.x - 2*padding
- : buffer->x2 - (ac->rect.pos.x + ac->rect.size.x + 2*padding);
- if (doc_width > 800) doc_width = 800;
- float doc_height = buffer->y2 - (ac->rect.pos.y + 2*padding);
- if (doc_height > char_height * 20) doc_height = char_height * 20;
-
- // if the rect is too small, there's no point in showing it
- if (doc_width >= 200) {
- float doc_x = open_left ? ac->rect.pos.x - doc_width - padding
- : ac->rect.pos.x + ac->rect.size.x + padding;
- float doc_y = ac->rect.pos.y;
- Rect r = rect(V2(doc_x, doc_y), V2(doc_width, doc_height));
- gl_geometry_rect(r, colors[COLOR_AUTOCOMPLETE_BG]);
- gl_geometry_rect_border(r, border_thickness, colors[COLOR_AUTOCOMPLETE_BORDER]);
-
- // draw the text!
- TextRenderState text_state = text_render_state_default;
- text_state.min_x = doc_x + padding;
- text_state.max_x = doc_x + doc_width - padding;
- text_state.max_y = doc_y + doc_height;
- text_state.x = doc_x + padding;
- text_state.y = doc_y + padding;
- text_state.wrap = true;
- rgba_u32_to_floats(colors[COLOR_TEXT], text_state.color);
- text_utf8_with_state(font, &text_state, document->documentation);
- }
- }
-
-
- for (uint i = 0; i < ted->nmouse_clicks[SDL_BUTTON_LEFT]; ++i) {
- v2 click = ted->mouse_clicks[SDL_BUTTON_LEFT][i];
- if (rect_contains_point(ac->rect, click)) {
- i32 entry = scroll + (i32)((click.y - start_y) / char_height);
- if (entry >= 0 && entry < (i32)ncompletions) {
- // entry was clicked on! use this completion.
- autocomplete_complete(ted, ac->completions[ac->suggested[entry]]);
- return;
- }
- }
- }
-
- float y = start_y;
- TextRenderState state = text_render_state_default;
- state.min_x = x + padding; state.min_y = y; state.max_x = x + menu_width - padding; state.max_y = y + menu_height;
- rgba_u32_to_floats(colors[COLOR_TEXT], state.color);
-
- if (ac->waiting_for_lsp && ncompletions == 0) {
- state.x = x + padding; state.y = y;
- text_utf8_with_state(font, &state, "Loading...");
- } else {
- for (size_t i = 0; i < ncompletions_visible; ++i) {
- const Autocompletion *completion = &ac->completions[ac->suggested[(i32)i + scroll]];
-
- state.x = x; state.y = y;
- if (i != ncompletions_visible-1) {
- gl_geometry_rect(rect(V2(x, y + char_height),
- V2(menu_width, border_thickness)),
- colors[COLOR_AUTOCOMPLETE_BORDER]);
- }
-
- ColorSetting label_color = color_for_symbol_kind(completion->kind);
- if (!settings->syntax_highlighting)
- label_color = COLOR_TEXT;
-
- rgba_u32_to_floats(colors[label_color], state.color);
-
- // draw icon
- char icon_text[2] = {symbol_kind_icon(completion->kind), 0};
- state.x += padding;
- text_utf8_with_state(font, &state, icon_text);
- state.x += padding;
- gl_geometry_rect(rect(V2((float)state.x, (float)state.y), V2(border_thickness, char_height)),
- colors[COLOR_AUTOCOMPLETE_BORDER]);
- state.x += padding;
-
- float label_x = (float)state.x;
- text_utf8_with_state(font, &state, completion->label);
-
- const char *detail = completion->detail;
- if (detail) {
- double label_end_x = state.x;
-
- char show_text[128] = {0};
-
- int amount_detail = 0;
- for (; ; ++amount_detail) {
- if (unicode_is_continuation_byte((u8)detail[amount_detail]))
- continue; // don't cut off text in the middle of a code point.
-
- char text[128] = {0};
- strbuf_printf(text, "%.*s%s", amount_detail, detail,
- (size_t)amount_detail == strlen(detail) ? "" : "...");
- double width = text_get_size_v2(font, text).x;
- if (label_end_x + width + 2 * padding < state.max_x) {
- strbuf_cpy(show_text, text);
- }
- // don't break if not, since we want to use "blabla"
- // even if "blabl..." is too long
-
- if (!detail[amount_detail]) break;
- }
- if (amount_detail >= 3) {
- //rgba_u32_to_floats(colors[COLOR_COMMENT], state.color);
- text_utf8_anchored(font, show_text, state.max_x, state.y,
- colors[COLOR_COMMENT], ANCHOR_TOP_RIGHT);
- }
- }
-
- if (completion->deprecated) {
- gl_geometry_rect(rect(V2(label_x, y + (char_height - border_thickness) * 0.5f),
- V2((float)state.x - label_x, 1)),
- colors[label_color]);
- }
-
- y += char_height;
- }
- }
-
- gl_geometry_draw();
- text_render(font);
-}