From 948decc409bac1e3671afc5d0212b4b35c669b4a Mon Sep 17 00:00:00 2001 From: pommicket Date: Thu, 29 Dec 2022 11:36:33 -0500 Subject: more hover --- autocomplete.c | 2 ++ hover.c | 79 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ lsp-parse.c | 57 +++++++++++++++++++++++++++++++++++++++++- lsp-write.c | 9 +++++++ lsp.c | 2 +- lsp.h | 8 ++++++ main.c | 20 +++++++++++++-- ted.h | 12 ++++++++- 8 files changed, 184 insertions(+), 5 deletions(-) create mode 100644 hover.c diff --git a/autocomplete.c b/autocomplete.c index 9166ea1..1271fba 100644 --- a/autocomplete.c +++ b/autocomplete.c @@ -288,6 +288,8 @@ static char symbol_kind_icon(SymbolKind k) { 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); diff --git a/hover.c b/hover.c new file mode 100644 index 0000000..8b4794a --- /dev/null +++ b/hover.c @@ -0,0 +1,79 @@ +// LSP hover information (textDocument/hover request) + +void hover_close(Ted *ted) { + Hover *hover = &ted->hover; + hover->time = 0.0; + hover->open = false; + free(hover->text); + hover->text = NULL; +} + +void hover_send_request(Ted *ted) { + // find the buffer where the mouse is + for (int i = 0; i < TED_MAX_BUFFERS; ++i) { + TextBuffer *buffer = &ted->buffers[i]; + if (!buffer->filename) continue; + LSP *lsp = buffer_lsp(buffer); + if (!lsp) continue; + BufferPos mouse_pos = {0}; + if (buffer_pixels_to_pos(buffer, ted->mouse_pos, &mouse_pos)) { + // send the request + LSPRequest request = {.type = LSP_REQUEST_HOVER}; + LSPRequestHover *h = &request.data.hover; + h->position = buffer_pos_to_lsp_document_position(buffer, mouse_pos); + lsp_send_request(lsp, &request); + break; + } + } +} + +void hover_process_lsp_response(Ted *ted, LSPResponse *response) { + if (!response) return; + if (response->request.type != LSP_REQUEST_HOVER) return; + + Hover *hover = &ted->hover; + LSPResponseHover *hover_response = &response->data.hover; + free(hover->text); + hover->text = NULL; + + const char *contents = lsp_response_string(response, hover_response->contents); + if (*contents) { + hover->text = str_dup(contents); + char *p = hover->text + strlen(hover->text) - 1; + // remove trailing whitespace + // (rust-analyzer gives us trailing newlines for local variables) + for (; p > hover->text && isspace(*p); --p) + *p = '\0'; + } +} + +void hover_frame(Ted *ted, double dt) { + Hover *hover = &ted->hover; + + if (ted->autocomplete.open) { + hover_close(ted); + } + + if (!hover->open) { + hover->time += dt; + if (hover->time > 1.0) { + hover_send_request(ted); + hover->open = true; + } + return; + } + + if (!hover->text) + return; + + const char *text = hover->text; + u16 lines = 0; // number of lines of text + for (int i = 0; text[i]; ++i) + if (text[i] == '\n') + ++lines; + + //Font *font = ted->font; + //float width = 200, height = lines * font->char_height; + + +} diff --git a/lsp-parse.c b/lsp-parse.c index 555a62a..7141485 100644 --- a/lsp-parse.c +++ b/lsp-parse.c @@ -73,8 +73,11 @@ static bool parse_range(LSP *lsp, const JSON *json, JSONValue range_value, LSPRa JSONObject range_object = range_value.val.object; JSONValue start = json_object_get(json, range_object, "start"); JSONValue end = json_object_get(json, range_object, "end"); - return parse_position(lsp, json, start, &range->start) + bool success = parse_position(lsp, json, start, &range->start) && parse_position(lsp, json, end, &range->end); + if (!success) + memset(range, 0, sizeof *range); + return success; } @@ -403,6 +406,55 @@ static bool parse_signature_help(LSP *lsp, const JSON *json, LSPResponse *respon return true; } +static bool parse_hover(LSP *lsp, const JSON *json, LSPResponse *response) { + LSPResponseHover *hover = &response->data.hover; + JSONObject result = json_force_object(json_get(json, "result")); + + JSONValue range = json_object_get(json, result, "range"); + parse_range(lsp, json, range, &hover->range); + + JSONValue contents = json_object_get(json, result, "contents"); + + switch (contents.type) { + case JSON_OBJECT: + case JSON_STRING: + // all good + break; + case JSON_ARRAY: { + JSONArray contents_array = contents.val.array; + if (contents_array.len == 0) { + // the server probably should have just returned result: null. + // but the spec doesn't seem to forbid this, so we'll handle it. + return true; + } + // it's giving us multiple strings, but we'll just show the first one + contents = json_array_get(json, contents_array, 0); + } break; + default: + lsp_set_error(lsp, "Bad contents field on textDocument/hover response."); + return false; + } + + // contents should either be a MarkupContent or a MarkedString + // i.e. it is either a string or has a member `value: string` + JSONString contents_string = {0}; + if (contents.type == JSON_STRING) { + contents_string = contents.val.string; + } else { + JSONObject contents_object = json_force_object(contents); + JSONValue value = json_object_get(json, contents_object, "value"); + if (value.type == JSON_STRING) { + contents_string = value.val.string; + } else { + lsp_set_error(lsp, "Bad contents field on textDocument/hover response."); + return false; + } + } + + hover->contents = lsp_response_add_json_string(response, json, contents_string); + return true; +} + // fills request->id/id_string appropriately given the request's json // returns true on success static WarnUnusedResult bool parse_id(JSON *json, LSPRequest *request) { @@ -539,6 +591,9 @@ static void process_message(LSP *lsp, JSON *json) { case LSP_REQUEST_SIGNATURE_HELP: add_to_messages = parse_signature_help(lsp, json, &response); break; + case LSP_REQUEST_HOVER: + add_to_messages = parse_hover(lsp, json, &response); + break; case LSP_REQUEST_INITIALIZE: { // it's the response to our initialize request! if (result.type == JSON_OBJECT) { diff --git a/lsp-write.c b/lsp-write.c index dce4c22..7db0f66 100644 --- a/lsp-write.c +++ b/lsp-write.c @@ -408,6 +408,8 @@ static void write_request(LSP *lsp, LSPRequest *request) { write_obj_end(o); write_key_bool(o, "contextSupport", true); write_obj_end(o); + + // signature help capabilities write_key_obj_start(o, "signatureHelp"); write_key_obj_start(o, "signatureInformation"); write_key_obj_start(o, "parameterInformation"); @@ -418,6 +420,13 @@ static void write_request(LSP *lsp, LSPRequest *request) { // we don't have context support because sending the activeSignatureHelp member is annoying //write_key_bool(o, "contextSupport", true); write_obj_end(o); + + // hover capabilities + write_key_obj_start(o, "hover"); + write_key_arr_start(o, "contentFormat"); + write_arr_elem_string(o, "plaintext"); + write_arr_end(o); + write_obj_end(o); write_obj_end(o); write_key_obj_start(o, "workspace"); write_key_bool(o, "workspaceFolders", true); diff --git a/lsp.c b/lsp.c index 73a7045..bc14c60 100644 --- a/lsp.c +++ b/lsp.c @@ -1,5 +1,5 @@ // print server-to-client communication -#define LSP_SHOW_S2C 1 +#define LSP_SHOW_S2C 0 // print client-to-server communication #define LSP_SHOW_C2S 0 diff --git a/lsp.h b/lsp.h index 9bbdd77..0b25091 100644 --- a/lsp.h +++ b/lsp.h @@ -272,6 +272,13 @@ typedef struct { LSPSignatureInformation *signatures; } LSPResponseSignatureHelp; +typedef struct { + // the range of text to highlight + LSPRange range; + // little tool tip to show + LSPString contents; +} LSPResponseHover; + typedef LSPRequestType LSPResponseType; typedef struct { @@ -284,6 +291,7 @@ typedef struct { union { LSPResponseCompletion completion; LSPResponseSignatureHelp signature_help; + LSPResponseHover hover; } data; } LSPResponse; diff --git a/main.c b/main.c index b59079e..ba759f8 100644 --- a/main.c +++ b/main.c @@ -4,6 +4,9 @@ - hover - go to definition using LSP - find usages +- highlight hover range +- check mouse position in hover_process_lsp_response +- hover-enabled, hover-time settings - check if there are any other non-optional/nice-to-have-support-for server-to-client requests - better non-error window/showMessage(Request) - document lsp.h and lsp.c. @@ -140,6 +143,7 @@ bool tag_goto(Ted *ted, char const *tag); #include "menu.c" #include "autocomplete.c" #include "signature-help.c" +#include "hover.c" #include "command.c" #include "config.c" #include "session.c" @@ -656,6 +660,8 @@ int main(int argc, char **argv) { switch (event.type) { case SDL_QUIT: + hover_close(ted); + command_execute(ted, CMD_QUIT, 1); break; case SDL_MOUSEWHEEL: { @@ -670,6 +676,8 @@ int main(int argc, char **argv) { } } break; case SDL_MOUSEBUTTONDOWN: { + hover_close(ted); + Uint32 button = event.button.button; u8 times = event.button.clicks; // number of clicks float x = (float)event.button.x, y = (float)event.button.y; @@ -736,6 +744,8 @@ int main(int argc, char **argv) { } } break; case SDL_MOUSEMOTION: { + hover_close(ted); + float x = (float)event.motion.x, y = (float)event.motion.y; if (ted->drag_buffer != ted->active_buffer) ted->drag_buffer = NULL; @@ -748,11 +758,15 @@ int main(int argc, char **argv) { } } break; case SDL_KEYDOWN: { + hover_close(ted); + SDL_Scancode scancode = event.key.keysym.scancode; SDL_Keymod modifier = event.key.keysym.mod; ted_press_key(ted, scancode, modifier); } break; case SDL_TEXTINPUT: { + hover_close(ted); + char *text = event.text.text; if (buffer // unfortunately, some key combinations like ctrl+minus still register as a "-" text input event @@ -887,6 +901,7 @@ int main(int argc, char **argv) { LSPResponse *r = &message.u.response; autocomplete_process_lsp_response(ted, r); signature_help_process_lsp_response(ted, r); + hover_process_lsp_response(ted, r); } break; } lsp_message_free(&message); @@ -991,9 +1006,9 @@ int main(int argc, char **argv) { if (ted->nodes_used[0]) { float y1 = padding; node_frame(ted, node, rect4(x1, y1, x2, y)); - if (ted->autocomplete.open) - autocomplete_frame(ted); + autocomplete_frame(ted); signature_help_frame(ted); + hover_frame(ted, frame_dt); } else { autocomplete_close(ted); text_utf8_anchored(font, "Press Ctrl+O to open a file or Ctrl+N to create a new one.", @@ -1126,6 +1141,7 @@ int main(int argc, char **argv) { build_stop(ted); if (ted->menu) menu_close(ted); + hover_close(ted); autocomplete_close(ted); session_write(ted); diff --git a/ted.h b/ted.h index 53d9697..f1748b0 100644 --- a/ted.h +++ b/ted.h @@ -447,6 +447,15 @@ typedef struct { Signature signatures[SIGNATURE_HELP_MAX]; } SignatureHelp; +typedef struct { + // is some hover info being displayed? + bool open; + // amount of time user has been hovering cursor for + double time; + // text to display + char *text; +} Hover; + typedef struct Ted { struct LSP *lsps[TED_LSP_MAX + 1]; @@ -493,7 +502,6 @@ typedef struct Ted { bool find; // is the find or find+replace menu open? bool replace; // is the find+replace menu open? bool find_regex, find_case_sensitive; // find options - SignatureHelp signature_help; u32 find_flags; // flags used last time search term was compiled pcre2_code *find_code; pcre2_match_data *find_match_data; @@ -503,6 +511,8 @@ typedef struct Ted { bool build_shown; // are we showing the build output? bool building; // is the build process running? Autocomplete autocomplete; + SignatureHelp signature_help; + Hover hover; FILE *log; -- cgit v1.2.3