From 268aba5dde93b67df5f5bd137141eeb177485685 Mon Sep 17 00:00:00 2001 From: pommicket Date: Fri, 6 Jan 2023 11:34:42 -0500 Subject: start phantom completions --- command.c | 4 +- config.c | 1 + ide-autocomplete.c | 144 +++++++++++++++++++++++++++++++++++++++-------------- main.c | 13 +++-- ted.cfg | 2 + ted.h | 12 ++++- 6 files changed, 128 insertions(+), 48 deletions(-) diff --git a/command.c b/command.c index bafb4f6..5f92719 100644 --- a/command.c +++ b/command.c @@ -281,8 +281,8 @@ void command_execute(Ted *ted, Command c, i64 argument) { buffer = &ted->line_buffer; ted_switch_to_buffer(ted, buffer); buffer_select_all(buffer); - } else if (ted->autocomplete.open) { - autocomplete_select_cursor_completion(ted); + } else if (ted->autocomplete.open || ted->autocomplete.phantom) { + autocomplete_select_completion(ted); } else if (buffer) { if (buffer->selection) buffer_indent_selection(buffer); diff --git a/config.c b/config.c index 5d819ad..3fc3183 100644 --- a/config.c +++ b/config.c @@ -82,6 +82,7 @@ static SettingBool const settings_bool[] = { {"indent-with-spaces", &settings_zero.indent_with_spaces, true}, {"trigger-characters", &settings_zero.trigger_characters, true}, {"identifier-trigger-characters", &settings_zero.identifier_trigger_characters, true}, + {"phantom-completions", &settings_zero.phantom_completions, true}, {"signature-help-enabled", &settings_zero.signature_help_enabled, true}, {"lsp-enabled", &settings_zero.lsp_enabled, true}, {"hover-enabled", &settings_zero.hover_enabled, true}, diff --git a/ide-autocomplete.c b/ide-autocomplete.c index 49fcba3..033841f 100644 --- a/ide-autocomplete.c +++ b/ide-autocomplete.c @@ -5,8 +5,7 @@ #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; +static void autocomplete_clear_completions(Autocomplete *ac) { arr_foreach_ptr(ac->completions, Autocompletion, completion) { free(completion->label); free(completion->text); @@ -18,6 +17,11 @@ static void autocomplete_clear_completions(Ted *ted) { arr_clear(ac->suggested); } +static void autocomplete_clear_phantom(Autocomplete *ac) { + free(ac->phantom); + ac->phantom = NULL; +} + // do the actual completion static void autocomplete_complete(Ted *ted, Autocompletion completion) { TextBuffer *buffer = ted->active_buffer; @@ -29,15 +33,19 @@ static void autocomplete_complete(Ted *ted, Autocompletion completion) { autocomplete_close(ted); } -void autocomplete_select_cursor_completion(Ted *ted) { +void autocomplete_select_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); } + } else if (ac->phantom) { + Autocompletion fake_completion = { + .text = ac->phantom + }; + autocomplete_complete(ted, fake_completion); } } @@ -78,12 +86,11 @@ void autocomplete_prev(Ted *ted) { void autocomplete_close(Ted *ted) { Autocomplete *ac = &ted->autocomplete; - if (ac->open) { - ac->open = false; - autocomplete_clear_completions(ted); - ted_cancel_lsp_request(ted, ac->last_request_lsp, ac->last_request_id); - ac->last_request_id = 0; - } + ac->open = false; + autocomplete_clear_phantom(ac); + autocomplete_clear_completions(ac); + ted_cancel_lsp_request(ted, ac->last_request_lsp, ac->last_request_id); + ac->last_request_id = 0; } static void autocomplete_update_suggested(Ted *ted) { @@ -111,7 +118,7 @@ static void autocomplete_no_suggestions(Ted *ted) { autocomplete_close(ted); } -static void autocomplete_send_completion_request(Ted *ted, TextBuffer *buffer, BufferPos pos, uint32_t trigger) { +static void autocomplete_send_completion_request(Ted *ted, TextBuffer *buffer, BufferPos pos, uint32_t trigger, bool phantom) { if (!buffer->path) return; // no can do @@ -146,6 +153,7 @@ static void autocomplete_send_completion_request(Ted *ted, TextBuffer *buffer, B if (ac->last_request_id) { ac->last_request_lsp = lsp->id; ac->last_request_time = ted->frame_time; + ac->last_request_phantom = phantom; // *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). @@ -154,9 +162,11 @@ static void autocomplete_send_completion_request(Ted *ted, TextBuffer *buffer, B } } -static void autocomplete_find_completions(Ted *ted, uint32_t trigger) { +static void autocomplete_find_completions(Ted *ted, uint32_t trigger, bool phantom) { Autocomplete *ac = &ted->autocomplete; TextBuffer *buffer = ted->active_buffer; + if (!buffer) + return; BufferPos pos = buffer->cursor_pos; if (buffer_pos_eq(pos, ac->last_pos)) return; // no need to update completions. @@ -170,30 +180,40 @@ static void autocomplete_find_completions(Ted *ted, uint32_t trigger) { // 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); + autocomplete_send_completion_request(ted, buffer, pos, trigger, phantom); } } else { // tag completion - autocomplete_clear_completions(ted); + autocomplete_clear_completions(ac); 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); + if (phantom) { + char *completion = NULL; + if (tags_beginning_with(ted, word_at_cursor, &completion, 1) == 1) { + // show phantom + ac->phantom = completion; + } else { + free(completion); + } + } else { + char **completions = calloc(TAGS_MAX_COMPLETIONS, sizeof *completions); + u32 ncompletions = (u32)tags_beginning_with(ted, word_at_cursor, completions, TAGS_MAX_COMPLETIONS); + + 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; } - 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; + free(word_at_cursor); } autocomplete_update_suggested(ted); @@ -253,13 +273,23 @@ void autocomplete_process_lsp_response(Ted *ted, const LSPResponse *response) { if (request->id != ac->last_request_id) return; // old request ac->last_request_id = 0; - if (!ac->open) { + if (!ac->open && !ac->last_request_phantom) { // user hit escape or down or something before completions arrived. return; } + if (ac->open && ac->last_request_phantom) { + // i'm not sure if this is possible, but just in case, + return; + } const LSPResponseCompletion *completion = &response->data.completion; size_t ncompletions = arr_len(completion->items); + printf("got %zu\n",ncompletions); + if (ac->last_request_phantom && ncompletions != 1) { + // only show phantom completion if there's exactly 1 completion. + autocomplete_clear_phantom(ac); + return; + } arr_set_len(ac->completions, ncompletions); for (size_t i = 0; i < ncompletions; ++i) { const LSPCompletionItem *lsp_completion = &completion->items[i]; @@ -278,6 +308,13 @@ void autocomplete_process_lsp_response(Ted *ted, const LSPResponse *response) { ted_completion->documentation = *documentation ? str_dup(documentation) : NULL; } + if (ac->last_request_phantom) { + assert(ncompletions == 1); + ac->phantom = str_dup(ac->completions[0].text); + autocomplete_clear_completions(ac); + return; + } + ac->is_list_complete = completion->is_complete; autocomplete_update_suggested(ted); @@ -300,11 +337,12 @@ void autocomplete_open(Ted *ted, uint32_t trigger) { TextBuffer *buffer = ted->active_buffer; if (!buffer->path) return; if (buffer->view_only) return; + autocomplete_clear_phantom(ac); ted->cursor_error_time = 0; - ac->last_pos = (BufferPos){0,0}; + ac->last_pos = (BufferPos){U32_MAX,0}; ac->cursor = 0; - autocomplete_find_completions(ted, trigger); + autocomplete_find_completions(ted, trigger, false); switch (arr_len(ac->completions)) { case 0: @@ -325,6 +363,17 @@ void autocomplete_open(Ted *ted, uint32_t trigger) { } } +static void autocomplete_find_phantom(Ted *ted) { + Autocomplete *ac = &ted->autocomplete; + if (ac->open) return; + if (!ted->active_buffer) return; + TextBuffer *buffer = ted->active_buffer; + if (!buffer->path) return; + if (buffer->view_only) return; + + autocomplete_find_completions(ted, TRIGGER_INVOKED, true); +} + static char symbol_kind_icon(SymbolKind k) { switch (k) { case SYMBOL_FUNCTION: @@ -346,17 +395,38 @@ static char symbol_kind_icon(SymbolKind k) { } void autocomplete_frame(Ted *ted) { - Autocomplete *ac = &ted->autocomplete; - if (!ac->open) return; - TextBuffer *buffer = ted->active_buffer; + if (!buffer) return; Font *font = ted->font; float char_height = text_font_char_height(font); const Settings *settings = buffer_settings(buffer); const u32 *colors = settings->colors; const float padding = settings->padding; + if (settings->phantom_completions) { + autocomplete_find_phantom(ted); + } + + Autocomplete *ac = &ted->autocomplete; + if (!ac->open && ac->phantom) { + // display phantom completion + char *word_at_cursor = buffer_word_at_cursor_utf8(buffer); + if (*word_at_cursor && str_has_prefix(ac->phantom, word_at_cursor)) { + const char *completion = ac->phantom + strlen(word_at_cursor); + vec2 pos = buffer_pos_to_pixels(buffer, buffer->cursor_pos); + text_utf8(font, completion, pos.x, pos.y, + colors[COLOR_TEXT] & 0xffffff7f); + text_render(font); + } else { + // this phantom is no longer relevant + autocomplete_clear_phantom(ac); + } + free(word_at_cursor); + return; + } + if (!ac->open) + return; - autocomplete_find_completions(ted, TRIGGER_INCOMPLETE); + autocomplete_find_completions(ted, TRIGGER_INCOMPLETE, false); size_t ncompletions = arr_len(ac->suggested); bool waiting_for_lsp = ac->last_request_id != 0; diff --git a/main.c b/main.c index f9f2014..18276d2 100644 --- a/main.c +++ b/main.c @@ -1,7 +1,5 @@ /* @TODO: -- LSP configuration in ted.cfg - - disable publishDiagnostics for rust-analyzer (& others)? - phantom completions - debug-lsp option (which logs LSP messages) - check LSP process status (TEST: what happens if LSP server is not installed) @@ -747,11 +745,12 @@ int main(int argc, char **argv) { // NOTE: we are not checking for signature help trigger // characters because currently we ask for signature // help any time a character is inserted. - - if (settings->identifier_trigger_characters - && is32_word(last_char) - && !is32_digit(last_char)) - autocomplete_open(ted, last_char); + if (is32_word(last_char) + && !is32_digit(last_char)) { + if (settings->identifier_trigger_characters) { + autocomplete_open(ted, last_char); + } + } } } diff --git a/ted.cfg b/ted.cfg index 521aaab..7648d12 100644 --- a/ted.cfg +++ b/ted.cfg @@ -33,6 +33,8 @@ restore-session = on trigger-characters = on # should all identifier characters (e.g. a-z) be treated as trigger characters? identifier-trigger-characters = off +# display "phantom completions"? (if there is only one completion, display it next to the cursor) +phantom-completions = yes # enable LSP support (for autocompletion, etc.) # you can also set `lsp = ""` but this is a quick way to disable LSP servers for all langauges lsp-enabled = yes diff --git a/ted.h b/ted.h index c42fd69..a3104e8 100644 --- a/ted.h +++ b/ted.h @@ -123,6 +123,7 @@ typedef struct { bool restore_session; bool regenerate_tags_if_not_found; bool indent_with_spaces; + bool phantom_completions; bool trigger_characters; bool identifier_trigger_characters; bool signature_help_enabled; @@ -386,6 +387,11 @@ typedef struct { i32 cursor; // which completion is currently selected (index into suggested) i32 scroll; + // was the last request for phantom completion? + bool last_request_phantom; + // current phantom completion to be displayed + char *phantom; + Rect rect; // rectangle where the autocomplete menu is (needed to avoid interpreting autocomplete clicks as other clicks) } Autocomplete; @@ -1120,8 +1126,10 @@ GLuint gl_load_texture_from_image(const char *path); // open autocomplete // trigger should either be a character (e.g. '.') or one of the TRIGGER_* constants. void autocomplete_open(Ted *ted, uint32_t trigger); -void autocomplete_process_lsp_response(Ted *ted, const LSPResponse *response); -void autocomplete_select_cursor_completion(Ted *ted); +void autocomplete_process_lsp_response(Ted *ted, const LSPResponse *response); +// select the completion the cursor is on, +// or select the phantom completion if there is one. +void autocomplete_select_completion(Ted *ted); // scroll completion list void autocomplete_scroll(Ted *ted, i32 by); // move cursor to next completion -- cgit v1.2.3