From 91ff61cc22c08e2c247b6b689561e6d18cf276e7 Mon Sep 17 00:00:00 2001 From: pommicket Date: Thu, 22 Dec 2022 18:55:15 -0500 Subject: detail text --- autocomplete.c | 37 ++++++++++++++++++++++++++++++++++--- json.c | 43 +++++++++++++++++++++++++++++++++++++++++++ lsp-parse.c | 31 ++++++++++++++++--------------- lsp.h | 2 ++ main.c | 2 +- ted.h | 1 + unicode.h | 3 +++ 7 files changed, 100 insertions(+), 19 deletions(-) diff --git a/autocomplete.c b/autocomplete.c index 1421201..eb5993f 100644 --- a/autocomplete.c +++ b/autocomplete.c @@ -142,6 +142,8 @@ static void autocomplete_process_lsp_response(Ted *ted, const LSPResponse *respo 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)); 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; } } autocomplete_update_suggested(ted); @@ -201,11 +203,11 @@ static void autocomplete_frame(Ted *ted) { autocomplete_find_completions(ted); - char *completions[AUTOCOMPLETE_NCOMPLETIONS_VISIBLE] = {0}; + Autocompletion completions[AUTOCOMPLETE_NCOMPLETIONS_VISIBLE] = {0}; size_t ncompletions = 0; arr_foreach_ptr(ac->suggested, u32, suggestion) { Autocompletion *completion = &ac->completions[*suggestion]; - completions[ncompletions++] = completion->label; + completions[ncompletions++] = *completion; if (ncompletions == AUTOCOMPLETE_NCOMPLETIONS_VISIBLE) break; } @@ -280,7 +282,36 @@ static void autocomplete_frame(Ted *ted) { } else { for (size_t i = 0; i < ncompletions; ++i) { state.x = x + padding; state.y = y; - text_utf8_with_state(font, &state, completions[i]); + text_utf8_with_state(font, &state, completions[i].label); + const char *detail = completions[i].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); + } + } y += char_height; } } diff --git a/json.c b/json.c index c11d907..e07b0ec 100644 --- a/json.c +++ b/json.c @@ -444,6 +444,49 @@ JSONValue json_object_get(const JSON *json, JSONObject object, const char *name) return (JSONValue){0}; } +// returns (JSONString){0} (which is interpreted as an empty string) if `name` does +// not exist or is not a string. +JSONString json_object_get_string(const JSON *json, JSONObject object, const char *name) { + JSONValue value = json_object_get(json, object, name); + if (value.type == JSON_STRING) { + return value.val.string; + } else { + return (JSONString){0}; + } +} + +// returns (JSONObject){0} (which is interpreted as an empty object) if `name` does +// not exist or is not an object. +JSONObject json_object_get_object(const JSON *json, JSONObject object, const char *name) { + JSONValue value = json_object_get(json, object, name); + if (value.type == JSON_OBJECT) { + return value.val.object; + } else { + return (JSONObject){0}; + } +} + +// returns (JSONArray){0} (which is interpreted as an empty array) if `name` does +// not exist or is not an array. +JSONArray json_object_get_array(const JSON *json, JSONObject object, const char *name) { + JSONValue value = json_object_get(json, object, name); + if (value.type == JSON_ARRAY) { + return value.val.array; + } else { + return (JSONArray){0}; + } +} + +// returns NaN if `name` does not exist or is not a number. +double json_object_get_number(const JSON *json, JSONObject object, const char *name) { + JSONValue value = json_object_get(json, object, name); + if (value.type == JSON_NUMBER) { + return value.val.number; + } else { + return NAN; + } +} + JSONValue json_array_get(const JSON *json, JSONArray array, u64 i) { if (i < array.len) { return json->values[array.elements + i]; diff --git a/lsp-parse.c b/lsp-parse.c index 6167cc8..8afca07 100644 --- a/lsp-parse.c +++ b/lsp-parse.c @@ -130,31 +130,32 @@ static bool parse_completion(LSP *lsp, const JSON *json, LSPResponse *response) .new_text = item->label }; - JSONValue sort_text_value = json_object_get(json, item_object, "sortText"); - if (sort_text_value.type == JSON_STRING) { + JSONString sort_text = json_object_get_string(json, item_object, "sortText"); + if (sort_text.pos) { // LSP allows using a different string for sorting. - item->sort_text = lsp_response_add_json_string(response, - json, sort_text_value.val.string); + item->sort_text = lsp_response_add_json_string(response, json, sort_text); } - JSONValue filter_text_value = json_object_get(json, item_object, "filterText"); - if (filter_text_value.type == JSON_STRING) { + JSONString filter_text = json_object_get_string(json, item_object, "filterText"); + if (filter_text.pos) { // LSP allows using a different string for filtering. - item->filter_text = lsp_response_add_json_string(response, - json, filter_text_value.val.string); + item->filter_text = lsp_response_add_json_string(response, json, filter_text); } - JSONValue text_type_value = json_object_get(json, item_object, "insertTextFormat"); - if (text_type_value.type == JSON_NUMBER) { - double type = text_type_value.val.number; - if (type != LSP_TEXT_EDIT_PLAIN && type != LSP_TEXT_EDIT_SNIPPET) { - lsp_set_error(lsp, "Bad InsertTextFormat: %g", type); + double edit_type = json_object_get_number(json, item_object, "insertTextFormat"); + if (!isnan(edit_type)) { + if (edit_type != LSP_TEXT_EDIT_PLAIN && edit_type != LSP_TEXT_EDIT_SNIPPET) { + lsp_set_error(lsp, "Bad InsertTextFormat: %g", edit_type); return false; } - item->text_edit.type = (LSPTextEditType)type; + item->text_edit.type = (LSPTextEditType)edit_type; } - // @TODO: detail + + JSONString detail_text = json_object_get_string(json, item_object, "detail"); + if (detail_text.pos) { + item->detail = lsp_response_add_json_string(response, json, detail_text); + } // @TODO(eventually): additionalTextEdits // (try to find a case where this comes up) diff --git a/lsp.h b/lsp.h index d8d8029..d965cf6 100644 --- a/lsp.h +++ b/lsp.h @@ -136,6 +136,8 @@ typedef struct { LSPString label; // text used to filter completions LSPString filter_text; + // more detail for this item, e.g. the signature of a function + LSPString detail; // the edit to be applied when this completion is selected. LSPTextEdit text_edit; // note: the items are sorted here in this file, diff --git a/main.c b/main.c index 5bd0a47..364e7e3 100644 --- a/main.c +++ b/main.c @@ -1,6 +1,6 @@ /* @TODO: -- show detail and type +- only show "Loading..." if it's taking some time (prevent flash) - LSP setting - scroll through completions - figure out under which circumstances backspace should close completions diff --git a/ted.h b/ted.h index a2c0c98..d56f3ab 100644 --- a/ted.h +++ b/ted.h @@ -356,6 +356,7 @@ typedef struct { char *label; char *filter; char *text; + char *detail; // this can be NULL! } Autocompletion; typedef struct { diff --git a/unicode.h b/unicode.h index fb9810a..fdd5669 100644 --- a/unicode.h +++ b/unicode.h @@ -8,6 +8,9 @@ static bool unicode_is_start_of_code_point(u8 byte) { // continuation bytes are of the form 10xxxxxx return (byte & 0xC0) != 0x80; } +static bool unicode_is_continuation_byte(u8 byte) { + return (byte & 0xC0) == 0x80; +} // A lot like mbrtoc32. Doesn't depend on the locale though, for one thing. // *c will be filled with the next UTF-8 code point in `str`. `bytes` refers to the maximum -- cgit v1.2.3