From e4774c14963bf51840bbf940c1181fe7fc7c3046 Mon Sep 17 00:00:00 2001 From: pommicket Date: Fri, 23 Dec 2022 13:00:51 -0500 Subject: trigger characters --- autocomplete.c | 2 +- json.c | 54 +++++++++++++++++++++++---------------- lsp-parse.c | 80 +++++++++++++++++++++++++++++++++++++++++----------------- lsp.c | 1 + lsp.h | 2 ++ main.c | 31 +++++++++++++++++++++-- 6 files changed, 122 insertions(+), 48 deletions(-) diff --git a/autocomplete.c b/autocomplete.c index e0de9fd..a71e45a 100644 --- a/autocomplete.c +++ b/autocomplete.c @@ -171,7 +171,7 @@ static void autocomplete_process_lsp_response(Ted *ted, const LSPResponse *respo // open autocomplete, or just do the completion if there's only one suggestion static void autocomplete_open(Ted *ted) { Autocomplete *ac = &ted->autocomplete; - + if (ac->open) return; if (!ted->active_buffer) return; TextBuffer *buffer = ted->active_buffer; if (!buffer->filename) return; diff --git a/json.c b/json.c index 61c8103..1174490 100644 --- a/json.c +++ b/json.c @@ -81,6 +81,7 @@ const char *json_type_to_str(JSONValueType type) { } static bool json_parse_value(JSON *json, u32 *p_index, JSONValue *val); +void json_debug_print_value(const JSON *json, JSONValue value); // defining this instead of using isspace seems to be faster // probably because isspace depends on the locale. @@ -88,7 +89,33 @@ static inline bool json_is_space(char c) { return c == ' ' || c == '\n' || c == '\r' || c == '\t'; } -static void json_debug_print_value(const JSON *json, JSONValue value) { +void json_debug_print_array(const JSON *json, JSONArray array) { + printf("["); + for (u32 i = 0; i < array.len; ++i) { + json_debug_print_value(json, json->values[array.elements + i]); + printf(", "); + } + printf("]"); +} + +void json_debug_print_object(const JSON *json, JSONObject obj) { + printf("{"); + for (u32 i = 0; i < obj.len; ++i) { + json_debug_print_value(json, json->values[obj.items + i]); + printf(": "); + json_debug_print_value(json, json->values[obj.items + obj.len + i]); + printf(", "); + } + printf("}"); +} + +void json_debug_print_string(const JSON *json, JSONString string) { + printf("\"%.*s\"", + (int)string.len, + json->text + string.pos); +} + +void json_debug_print_value(const JSON *json, JSONValue value) { switch (value.type) { case JSON_UNDEFINED: printf("undefined"); break; case JSON_NULL: printf("null"); break; @@ -96,30 +123,13 @@ static void json_debug_print_value(const JSON *json, JSONValue value) { case JSON_TRUE: printf("true"); break; case JSON_NUMBER: printf("%g", value.val.number); break; case JSON_STRING: { - JSONString string = value.val.string; - printf("\"%.*s\"", - (int)string.len, - json->text + string.pos); + json_debug_print_string(json, value.val.string); } break; case JSON_ARRAY: { - JSONArray array = value.val.array; - printf("["); - for (u32 i = 0; i < array.len; ++i) { - json_debug_print_value(json, json->values[array.elements + i]); - printf(", "); - } - printf("]"); - } break; + json_debug_print_array(json, value.val.array); + } break; case JSON_OBJECT: { - JSONObject obj = value.val.object; - printf("{"); - for (u32 i = 0; i < obj.len; ++i) { - json_debug_print_value(json, json->values[obj.items + i]); - printf(": "); - json_debug_print_value(json, json->values[obj.items + obj.len + i]); - printf(", "); - } - printf("}"); + json_debug_print_object(json, value.val.object); } break; } } diff --git a/lsp-parse.c b/lsp-parse.c index 05fc898..0c75acf 100644 --- a/lsp-parse.c +++ b/lsp-parse.c @@ -77,6 +77,35 @@ static bool parse_range(LSP *lsp, const JSON *json, JSONValue range_value, LSPRa && parse_position(lsp, json, end, &range->end); } + +static void parse_capabilities(LSP *lsp, const JSON *json, JSONObject capabilities) { + JSONValue completion_value = json_object_get(json, capabilities, "completionProvider"); + if (completion_value.type == JSON_OBJECT) { + lsp->provides_completion = true; + JSONObject completion = completion_value.val.object; + + JSONArray trigger_chars = json_object_get_array(json, completion, "triggerCharacters"); + for (u32 i = 0; i < trigger_chars.len; ++i) { + char character[8] = {0}; + json_string_get(json, + json_array_get_string(json, trigger_chars, i), + character, + sizeof character); + if (*character) { + char32_t c = 0; + unicode_utf8_to_utf32(&c, character, strlen(character)); + // the fact that they're called "trigger characters" makes + // me think multi-character triggers aren't allowed + // even though that would be nice in some languages, + // e.g. "::" + if (c) { + arr_add(lsp->trigger_chars, c); + } + } + } + } +} + static bool parse_completion(LSP *lsp, const JSON *json, LSPResponse *response) { // deal with textDocument/completion response. // result: CompletionItem[] | CompletionList | null @@ -273,7 +302,6 @@ static bool parse_server2client_request(LSP *lsp, JSON *json, LSPRequest *reques return false; } - static void process_message(LSP *lsp, JSON *json) { #if 0 @@ -318,8 +346,22 @@ static void process_message(LSP *lsp, JSON *json) { JSONValue result = json_get(json, "result"); if (result.type != JSON_UNDEFINED) { - if (response_to.type == LSP_REQUEST_INITIALIZE) { + LSPResponse response = {0}; + bool add_to_messages = false; + response.request = response_to; + switch (response_to.type) { + case LSP_REQUEST_COMPLETION: + add_to_messages = parse_completion(lsp, json, &response); + break; + case LSP_REQUEST_INITIALIZE: { // it's the response to our initialize request! + + if (result.type == JSON_OBJECT) { + // read server capabilities + JSONObject capabilities = json_object_get_object(json, result.val.object, "capabilities"); + parse_capabilities(lsp, json, capabilities); + } + // let's send back an "initialized" request (notification) because apparently // that's something we need to do. LSPRequest initialized = { @@ -329,28 +371,20 @@ static void process_message(LSP *lsp, JSON *json) { write_request(lsp, &initialized); // we can now send requests which have nothing to do with initialization lsp->initialized = true; + } break; + default: + // it's some response we don't care about + break; + } + if (add_to_messages) { + SDL_LockMutex(lsp->messages_mutex); + LSPMessage *message = arr_addp(lsp->messages); + message->type = LSP_RESPONSE; + message->u.response = response; + SDL_UnlockMutex(lsp->messages_mutex); + response_to.type = 0; // don't free } else { - LSPResponse response = {0}; - bool success = false; - response.request = response_to; - switch (response_to.type) { - case LSP_REQUEST_COMPLETION: - success = parse_completion(lsp, json, &response); - break; - default: - // it's some response we don't care about - break; - } - if (success) { - SDL_LockMutex(lsp->messages_mutex); - LSPMessage *message = arr_addp(lsp->messages); - message->type = LSP_RESPONSE; - message->u.response = response; - SDL_UnlockMutex(lsp->messages_mutex); - response_to.type = 0; // don't free - } else { - lsp_response_free(&response); - } + lsp_response_free(&response); } } else if (json_has(json, "method")) { LSPRequest request = {0}; diff --git a/lsp.c b/lsp.c index fedc085..087d757 100644 --- a/lsp.c +++ b/lsp.c @@ -315,6 +315,7 @@ void lsp_free(LSP *lsp) { lsp_message_free(message); } arr_free(lsp->messages); + arr_free(lsp->trigger_chars); } void lsp_document_changed(LSP *lsp, const char *document, LSPDocumentChangeEvent change) { diff --git a/lsp.h b/lsp.h index cee1505..1c6076a 100644 --- a/lsp.h +++ b/lsp.h @@ -268,6 +268,8 @@ typedef struct LSP { SDL_Thread *communication_thread; SDL_sem *quit_sem; char *received_data; // dynamic array + bool provides_completion; // can this LSP server handle completion requests? + char32_t *trigger_chars; // dynamic array of "trigger characters" SDL_mutex *error_mutex; char error[256]; } LSP; diff --git a/main.c b/main.c index f9f8605..26195f5 100644 --- a/main.c +++ b/main.c @@ -1,11 +1,16 @@ /* @TODO: -- trigger characters (with setting) +- fix unicode_utf8_to_utf32 to handle bad UTF-8 (i.e. continuation bytes which aren't actually continuation bytes) +- provide completion context? +- dont do completion if provides_completion = false - scroll through completions - only show "Loading..." if it's taking some time (prevent flash) - LSP setting - figure out workspace - make sure "save as" works +- more LSP stuff: + - go to definition using LSP + - find usages - rename buffer->filename to buffer->path - make buffer->path NULL for untitled buffers & fix resulting mess - run everything through valgrind ideally with leak checking @@ -810,8 +815,30 @@ int main(int argc, char **argv) { case SDL_TEXTINPUT: { char *text = event.text.text; if (buffer - && (key_modifier & ~KEY_MODIFIER_SHIFT) == 0) // unfortunately, some key combinations like ctrl+minus still register as a "-" text input event + // unfortunately, some key combinations like ctrl+minus still register as a "-" text input event + && (key_modifier & ~KEY_MODIFIER_SHIFT) == 0) { + // insert the text buffer_insert_utf8_at_cursor(buffer, text); + // check for trigger character + LSP *lsp = buffer_lsp(buffer); + Settings *settings = buffer_settings(buffer); + if (lsp && settings->trigger_characters) { + u32 last_code_point = (u32)strlen(text) - 1; + while (last_code_point > 0 && + unicode_is_continuation_byte((u8)text[last_code_point])) + --last_code_point; + char32_t last_char = 0; + unicode_utf8_to_utf32(&last_char, &text[last_code_point], + strlen(text) - last_code_point); + arr_foreach_ptr(lsp->trigger_chars, char32_t, c) { + if (*c == last_char) { + autocomplete_open(ted); + break; + } + } + } + + } } break; } } -- cgit v1.2.3