From 18fbb21e6b95139a8890ba4a65f4402df128ac84 Mon Sep 17 00:00:00 2001 From: pommicket Date: Thu, 29 Dec 2022 12:12:36 -0500 Subject: working hover --- autocomplete.c | 48 ++++++++++++++++++++++++++++++++ base.h | 53 +++++++++++++++++++++++++++++++++++ hover.c | 87 +++++++++++++++++++++++++++++++++++++++++++--------------- lsp-parse.c | 11 ++++++-- lsp.c | 49 ++++++--------------------------- lsp.h | 28 +++++++++---------- main.c | 12 +------- ted.h | 58 ++++----------------------------------- text.c | 30 ++++++++++++-------- text.h | 4 +++ 10 files changed, 226 insertions(+), 154 deletions(-) diff --git a/autocomplete.c b/autocomplete.c index 1271fba..1d03c75 100644 --- a/autocomplete.c +++ b/autocomplete.c @@ -54,6 +54,8 @@ void autocomplete_scroll(Ted *ted, i32 by) { 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); @@ -186,6 +188,52 @@ static void autocomplete_find_completions(Ted *ted, uint32_t trigger) { 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) diff --git a/base.h b/base.h index 26e874e..bbb1be5 100644 --- a/base.h +++ b/base.h @@ -175,4 +175,57 @@ static void print(char const *fmt, ...) { #define debug_println(...) #endif +// NOTE: these have to be defined here because lsp.h uses them + +// If you are adding new languages, DO NOT change the constant values +// of the previous languages. It will mess up config files which use :set-language! +typedef enum { + LANG_NONE = 0, + LANG_C = 1, + LANG_CPP = 2, + LANG_RUST = 3, + LANG_PYTHON = 4, + LANG_TEX = 5, + LANG_MARKDOWN = 6, + LANG_HTML = 7, + LANG_CONFIG = 8, // .cfg files + LANG_JAVASCRIPT = 9, + LANG_JAVA = 10, + LANG_GO = 11, + LANG_TED_CFG = 12, // like LANG_CONFIG, but with multiline strings. + LANG_TYPESCRIPT = 13, + LANG_JSON = 14, + LANG_XML = 15, + LANG_GLSL = 16, + LANG_COUNT +} Language; + +typedef struct { + Language lang; + char const *name; +} LanguageName; + +static LanguageName const language_names[] = { + {LANG_NONE, "None"}, + {LANG_C, "C"}, + {LANG_CPP, "C++"}, + {LANG_RUST, "Rust"}, + {LANG_PYTHON, "Python"}, + {LANG_TEX, "Tex"}, + {LANG_MARKDOWN, "Markdown"}, + {LANG_HTML, "HTML"}, + {LANG_CONFIG, "Config"}, + {LANG_JAVASCRIPT, "JavaScript"}, + {LANG_JAVA, "Java"}, + {LANG_GO, "Go"}, + {LANG_TED_CFG, "TedCfg"}, + {LANG_TYPESCRIPT, "TypeScript"}, + {LANG_JSON, "JSON"}, + {LANG_XML, "XML"}, + {LANG_GLSL, "GLSL"}, +}; + +static_assert_if_possible(arr_count(language_names) == LANG_COUNT) + + #endif diff --git a/hover.c b/hover.c index 8b4794a..ebb3d8e 100644 --- a/hover.c +++ b/hover.c @@ -2,29 +2,37 @@ 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) { +static bool get_hover_position(Ted *ted, LSPDocumentPosition *pos, LSP **lsp) { // 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; + LSP *l = buffer_lsp(buffer); + if (!l) 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; + *pos = buffer_pos_to_lsp_document_position(buffer, mouse_pos); + *lsp = l; + return true; } } + return false; +} +void hover_send_request(Ted *ted) { + Hover *hover = &ted->hover; + LSPRequest request = {.type = LSP_REQUEST_HOVER}; + LSPRequestHover *h = &request.data.hover; + LSP *lsp = NULL; + if (get_hover_position(ted, &h->position, &lsp)) { + hover->requested_position = h->position; + hover->requested_lsp = lsp->id; + lsp_send_request(lsp, &request); + } } void hover_process_lsp_response(Ted *ted, LSPResponse *response) { @@ -50,30 +58,65 @@ void hover_process_lsp_response(Ted *ted, LSPResponse *response) { void hover_frame(Ted *ted, double dt) { Hover *hover = &ted->hover; - if (ted->autocomplete.open) { + bool shift_down = SDL_GetKeyboardState(NULL)[SDL_SCANCODE_LSHIFT] + || SDL_GetKeyboardState(NULL)[SDL_SCANCODE_RSHIFT]; + + if (!shift_down) { hover_close(ted); } + (void)dt; if (!hover->open) { - hover->time += dt; - if (hover->time > 1.0) { + if (shift_down) { hover_send_request(ted); hover->open = true; } return; } + { + LSPDocumentPosition pos={0}; + LSP *lsp=0; + if (get_hover_position(ted, &pos, &lsp)) { + if (lsp->id != hover->requested_lsp + || !lsp_document_position_eq(pos, hover->requested_position)) { + // refresh hover + hover_send_request(ted); + } + } else { + hover_close(ted); + return; + } + } + if (!hover->text) return; + const Settings *settings = ted_active_settings(ted); + const u32 *colors = settings->colors; 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; - - + Font *font = ted->font; + float x = ted->mouse_pos.x, y = ted->mouse_pos.y; + TextRenderState state = text_render_state_default; + state.x = state.min_x = x; + state.y = state.min_y = y; + state.render = false; + state.wrap = true; + state.max_x = x + 400; + state.max_y = ted->window_height; + text_utf8_with_state(font, &state, text); + float width = (float)(state.x_largest - x); + float height = (float)(state.y_largest - y) + font->char_height; + if (height > 300) { + height = 300; + } + state.x = x; + state.y = y; + state.render = true; + state.max_y = y + height; + gl_geometry_rect(rect_xywh(x, y, width, height), colors[COLOR_AUTOCOMPLETE_BG]); + rgba_u32_to_floats(colors[COLOR_TEXT], state.color); + text_utf8_with_state(font, &state, text); + gl_geometry_draw(); + text_render(font); } diff --git a/lsp-parse.c b/lsp-parse.c index 7141485..c89a8c4 100644 --- a/lsp-parse.c +++ b/lsp-parse.c @@ -408,7 +408,14 @@ static bool parse_signature_help(LSP *lsp, const JSON *json, LSPResponse *respon 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 result_value = json_get(json, "result"); + if (result_value.type == JSON_NULL) + return true; // no results + if (result_value.type != JSON_OBJECT) { + lsp_set_error(lsp, "Bad result type for textDocument/hover response."); + return false; + } + JSONObject result = json_force_object(result_value); JSONValue range = json_object_get(json, result, "range"); parse_range(lsp, json, range, &hover->range); @@ -446,7 +453,7 @@ static bool parse_hover(LSP *lsp, const JSON *json, LSPResponse *response) { if (value.type == JSON_STRING) { contents_string = value.val.string; } else { - lsp_set_error(lsp, "Bad contents field on textDocument/hover response."); + lsp_set_error(lsp, "Bad contents object in textDocument/hover response."); return false; } } diff --git a/lsp.c b/lsp.c index bc14c60..678c638 100644 --- a/lsp.c +++ b/lsp.c @@ -477,6 +477,7 @@ void lsp_document_changed(LSP *lsp, const char *document, LSPDocumentChangeEvent lsp_send_request(lsp, &request); } +#if 0 SymbolKind lsp_symbol_kind_to_ted(LSPSymbolKind kind) { switch (kind) { case LSP_SYMBOL_OTHER: @@ -521,48 +522,14 @@ SymbolKind lsp_symbol_kind_to_ted(LSPSymbolKind kind) { return SYMBOL_OTHER; } +#endif -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; - } +bool lsp_position_eq(LSPPosition a, LSPPosition b) { + return a.line == b.line && a.character == b.character; +} + +bool lsp_document_position_eq(LSPDocumentPosition a, LSPDocumentPosition b) { + return a.document == b.document && lsp_position_eq(a.pos, b.pos); } #undef write_bool diff --git a/lsp.h b/lsp.h index 0b25091..389f38e 100644 --- a/lsp.h +++ b/lsp.h @@ -1,6 +1,17 @@ typedef u32 LSPDocumentID; typedef u32 LSPID; +typedef struct { + u32 line; + // NOTE: this is the UTF-16 character index! + u32 character; +} LSPPosition; + +typedef struct { + LSPDocumentID document; + LSPPosition pos; +} LSPDocumentPosition; + typedef enum { LSP_REQUEST, LSP_RESPONSE @@ -10,12 +21,6 @@ typedef struct { u32 offset; } LSPString; -typedef struct { - u32 line; - // NOTE: this is the UTF-16 character index! - u32 character; -} LSPPosition; - typedef struct { LSPPosition start; LSPPosition end; @@ -83,11 +88,6 @@ typedef struct { char *message; } LSPRequestMessage; -typedef struct { - LSPDocumentID document; - LSPPosition pos; -} LSPDocumentPosition; - // these triggers are used for completion context and signature help context. #define LSP_TRIGGER_NONE 0 // not actually defined in LSP spec @@ -375,8 +375,6 @@ typedef struct LSP { char error[256]; } LSP; -// @TODO: function declarations - // returns true if there's an error. // returns false and sets error to "" if there's no error. // if clear = true, the error will be cleared. @@ -401,7 +399,7 @@ LSP *lsp_create(const char *root_dir, Language language, const char *analyzer_co // with root directory `new_root_dir`. bool lsp_try_add_root_dir(LSP *lsp, const char *new_root_dir); bool lsp_next_message(LSP *lsp, LSPMessage *message); +bool lsp_position_eq(LSPPosition a, LSPPosition b); +bool lsp_document_position_eq(LSPDocumentPosition a, LSPDocumentPosition b); void lsp_document_changed(LSP *lsp, const char *document, LSPDocumentChangeEvent change); void lsp_free(LSP *lsp); -SymbolKind lsp_symbol_kind_to_ted(LSPSymbolKind kind); -SymbolKind lsp_completion_kind_to_ted(LSPCompletionKind kind); diff --git a/main.c b/main.c index ba759f8..39e659b 100644 --- a/main.c +++ b/main.c @@ -124,10 +124,10 @@ static void die(char const *fmt, ...) { #include "command.h" #include "colors.h" #include "time.c" +#include "lsp.h" #include "ted.h" #include "gl.c" #include "text.c" -#include "lsp.h" #include "string32.c" #include "colors.c" @@ -660,8 +660,6 @@ 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: { @@ -676,8 +674,6 @@ 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; @@ -744,8 +740,6 @@ 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; @@ -758,15 +752,11 @@ 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 diff --git a/ted.h b/ted.h index f1748b0..004cc2a 100644 --- a/ted.h +++ b/ted.h @@ -63,56 +63,6 @@ enum { typedef u8 SyntaxState; -// If you are adding new languages, DO NOT change the constant values -// of the previous languages. It will mess up config files which use :set-language! -typedef enum { - LANG_NONE = 0, - LANG_C = 1, - LANG_CPP = 2, - LANG_RUST = 3, - LANG_PYTHON = 4, - LANG_TEX = 5, - LANG_MARKDOWN = 6, - LANG_HTML = 7, - LANG_CONFIG = 8, // .cfg files - LANG_JAVASCRIPT = 9, - LANG_JAVA = 10, - LANG_GO = 11, - LANG_TED_CFG = 12, // like LANG_CONFIG, but with multiline strings. - LANG_TYPESCRIPT = 13, - LANG_JSON = 14, - LANG_XML = 15, - LANG_GLSL = 16, - LANG_COUNT -} Language; - -typedef struct { - Language lang; - char const *name; -} LanguageName; - -static LanguageName const language_names[] = { - {LANG_NONE, "None"}, - {LANG_C, "C"}, - {LANG_CPP, "C++"}, - {LANG_RUST, "Rust"}, - {LANG_PYTHON, "Python"}, - {LANG_TEX, "Tex"}, - {LANG_MARKDOWN, "Markdown"}, - {LANG_HTML, "HTML"}, - {LANG_CONFIG, "Config"}, - {LANG_JAVASCRIPT, "JavaScript"}, - {LANG_JAVA, "Java"}, - {LANG_GO, "Go"}, - {LANG_TED_CFG, "TedCfg"}, - {LANG_TYPESCRIPT, "TypeScript"}, - {LANG_JSON, "JSON"}, - {LANG_XML, "XML"}, - {LANG_GLSL, "GLSL"}, -}; - -static_assert_if_possible(arr_count(language_names) == LANG_COUNT) - ENUM_U8 { SYNTAX_NORMAL, SYNTAX_KEYWORD, @@ -177,6 +127,8 @@ typedef struct { u32 buffer; } GlRcSAB; + + typedef struct { // NOTE: to add more options to ted, add fields here, // and change the options_ global constant in config.c @@ -450,10 +402,12 @@ typedef struct { 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; + // where the hover data is coming from. + // we use this to check if we need to refresh it. + LSPDocumentPosition requested_position; + LSPID requested_lsp; } Hover; diff --git a/text.c b/text.c index 6df77ff..d439b32 100644 --- a/text.c +++ b/text.c @@ -59,6 +59,7 @@ TextRenderState const text_render_state_default = { .min_x = -FLT_MAX, .max_x = +FLT_MAX, .min_y = -FLT_MAX, .max_y = +FLT_MAX, .color = {1, 0, 1, 1}, + .x_largest = -FLT_MAX, .y_largest = -FLT_MAX }; static char text_err[200]; @@ -295,7 +296,7 @@ top: if (state->wrap && c == '\n') { state->x = state->min_x; state->y += char_height; - return; + goto ret; } { @@ -334,7 +335,7 @@ top: } if (x0 > max_x || y0 > max_y || x1 < min_x || y1 < min_y) - return; + goto ret; if (x0 < min_x) { // left side of character is clipped s0 = (min_x-x0) / (x1-x0) * (s1-s0) + s0; @@ -367,6 +368,11 @@ top: arr_add(font->triangles[page], triangle2); } } + ret: + if (state->x > state->x_largest) + state->x_largest = state->x; + if (state->y > state->y_largest) + state->y_largest = state->y; } void text_utf8_with_state(Font *font, TextRenderState *state, char const *str) { @@ -387,19 +393,21 @@ void text_utf8_with_state(Font *font, TextRenderState *state, char const *str) { } } -static void text_render_utf8_internal(Font *font, char const *text, double *x, double *y, u32 color, bool render) { +static v2 text_render_utf8_internal(Font *font, char const *text, double x, double y, u32 color, bool render) { TextRenderState render_state = text_render_state_default; render_state.render = render; - render_state.x = *x; - render_state.y = *y; + render_state.x = x; + render_state.y = y; rgba_u32_to_floats(color, render_state.color); text_utf8_with_state(font, &render_state, text); - *x = render_state.x; - *y = render_state.y; + return V2( + (float)(render_state.x_largest - x), + (float)(render_state.y_largest - y) + ); } void text_utf8(Font *font, char const *text, double x, double y, u32 color) { - text_render_utf8_internal(font, text, &x, &y, color, true); + text_render_utf8_internal(font, text, x, y, color, true); } void text_utf8_anchored(Font *font, char const *text, double x, double y, u32 color, Anchor anchor) { @@ -422,9 +430,9 @@ void text_utf8_anchored(Font *font, char const *text, double x, double y, u32 co void text_get_size(Font *font, char const *text, float *width, float *height) { double x = 0, y = 0; - text_render_utf8_internal(font, text, &x, &y, 0, false); - if (width) *width = (float)x; - if (height) *height = (float)y + font->char_height; + v2 size = text_render_utf8_internal(font, text, x, y, 0, false); + if (width) *width = size.x; + if (height) *height = size.y + font->char_height; } v2 text_get_size_v2(Font *font, char const *text) { diff --git a/text.h b/text.h index 882b4ae..dbf223d 100644 --- a/text.h +++ b/text.h @@ -23,6 +23,10 @@ typedef struct { float min_x, max_x, min_y, max_y; // [0] = r, [1] = g, [2] = b, [3] = a. float color[4]; + + // largest x & y achieved (for computing size) + double x_largest; + double y_largest; } TextRenderState; typedef enum { -- cgit v1.2.3