From e112a90ff73f7f407ed2251f905565713c237bc1 Mon Sep 17 00:00:00 2001 From: pommicket Date: Tue, 27 Dec 2022 18:28:56 -0500 Subject: start signature help --- autocomplete.c | 2 +- buffer.c | 27 ++++++++++++++++++++++++--- lsp-parse.c | 51 +++++++++++++++++++++++++++++++++------------------ lsp-write.c | 7 +++++++ lsp.c | 4 +++- lsp.h | 28 +++++++++++++++++++++------- main.c | 25 ++++++++++++++++++++++++- signature-help.c | 12 ++++++++++++ ted.h | 7 +++++++ 9 files changed, 132 insertions(+), 31 deletions(-) create mode 100644 signature-help.c diff --git a/autocomplete.c b/autocomplete.c index 2955183..5045a3d 100644 --- a/autocomplete.c +++ b/autocomplete.c @@ -121,7 +121,7 @@ static void autocomplete_send_completion_request(Ted *ted, TextBuffer *buffer, B request.data.completion = (LSPRequestCompletion) { .position = { .document = lsp_document_id(lsp, buffer->filename), - .pos = buffer_pos_to_lsp(buffer, pos) + .pos = buffer_pos_to_lsp_position(buffer, pos) }, .context = { .trigger_kind = lsp_trigger, diff --git a/buffer.c b/buffer.c index 7b569bd..9796eb5 100644 --- a/buffer.c +++ b/buffer.c @@ -1419,8 +1419,13 @@ static Status buffer_insert_lines(TextBuffer *buffer, u32 where, u32 number) { return false; } +LSPDocumentID buffer_lsp_document_id(TextBuffer *buffer) { + LSP *lsp = buffer_lsp(buffer); + return lsp_document_id(lsp, buffer->filename); +} + // LSP uses UTF-16 indices because Microsoft fucking loves UTF-16 and won't let it die -LSPPosition buffer_pos_to_lsp(TextBuffer *buffer, BufferPos pos) { +LSPPosition buffer_pos_to_lsp_position(TextBuffer *buffer, BufferPos pos) { LSPPosition lsp_pos = { .line = pos.line }; @@ -1436,6 +1441,22 @@ LSPPosition buffer_pos_to_lsp(TextBuffer *buffer, BufferPos pos) { return lsp_pos; } +LSPDocumentPosition buffer_pos_to_lsp_document_position(TextBuffer *buffer, BufferPos pos) { + LSPDocumentPosition docpos = { + .document = buffer_lsp_document_id(buffer), + .pos = buffer_pos_to_lsp_position(buffer, pos) + }; + return docpos; +} + +LSPPosition buffer_cursor_pos_as_lsp_position(TextBuffer *buffer) { + return buffer_pos_to_lsp_position(buffer, buffer->cursor_pos); +} + +LSPDocumentPosition buffer_cursor_pos_as_lsp_document_position(TextBuffer *buffer) { + return buffer_pos_to_lsp_document_position(buffer, buffer->cursor_pos); +} + static void buffer_send_lsp_did_change(LSP *lsp, TextBuffer *buffer, BufferPos pos, u32 nchars_deleted, String32 new_text) { if (!buffer_is_named_file(buffer)) @@ -1443,9 +1464,9 @@ static void buffer_send_lsp_did_change(LSP *lsp, TextBuffer *buffer, BufferPos p LSPDocumentChangeEvent event = {0}; if (new_text.len > 0) event.text = str32_to_utf8_cstr(new_text); - event.range.start = buffer_pos_to_lsp(buffer, pos); + event.range.start = buffer_pos_to_lsp_position(buffer, pos); BufferPos pos_end = buffer_pos_advance(buffer, pos, nchars_deleted); - event.range.end = buffer_pos_to_lsp(buffer, pos_end); + event.range.end = buffer_pos_to_lsp_position(buffer, pos_end); lsp_document_changed(lsp, buffer->filename, event); } diff --git a/lsp-parse.c b/lsp-parse.c index a6e932e..339c490 100644 --- a/lsp-parse.c +++ b/lsp-parse.c @@ -78,6 +78,28 @@ static bool parse_range(LSP *lsp, const JSON *json, JSONValue range_value, LSPRa } +static uint32_t *parse_trigger_characters(const JSON *json, JSONArray trigger_chars) { + uint32_t *array = NULL; + arr_reserve(array, trigger_chars.len); + 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) { + // 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. "::" + char32_t c = 0; + unicode_utf8_to_utf32(&c, character, strlen(character)); + if (c) arr_add(array, c); + } + } + return array; +} + static void parse_capabilities(LSP *lsp, const JSON *json, JSONObject capabilities) { LSPCapabilities *cap = &lsp->capabilities; @@ -88,26 +110,19 @@ static void parse_capabilities(LSP *lsp, const JSON *json, JSONObject capabiliti 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); - } - } - } + lsp->completion_trigger_chars = parse_trigger_characters(json, trigger_chars); } + // check SignatureHelpOptions + JSONValue signature_help_value = json_object_get(json, capabilities, "signatureHelpProvider"); + if (signature_help_value.type == JSON_OBJECT) { + cap->signature_help_support = true; + JSONObject signature_help = signature_help_value.val.object; + JSONArray trigger_chars = json_object_get_array(json, signature_help, "triggerCharacters"); + lsp->signature_help_trigger_chars = parse_trigger_characters(json, trigger_chars); + JSONArray retrigger_chars = json_object_get_array(json, signature_help, "retriggerCharacters"); + lsp->signature_help_retrigger_chars = parse_trigger_characters(json, retrigger_chars); + } JSONObject workspace = json_object_get_object(json, capabilities, "workspace"); // check WorkspaceFoldersServerCapabilities diff --git a/lsp-write.c b/lsp-write.c index ffa5337..835cfce 100644 --- a/lsp-write.c +++ b/lsp-write.c @@ -251,6 +251,8 @@ static const char *lsp_request_method(LSPRequest *request) { return "textDocument/didChange"; case LSP_REQUEST_COMPLETION: return "textDocument/completion"; + case LSP_REQUEST_SIGNATURE_HELP: + return "textDocument/signatureHelp"; case LSP_REQUEST_WORKSPACE_FOLDERS: return "workspace/workspaceFolders"; case LSP_REQUEST_DID_CHANGE_WORKSPACE_FOLDERS: @@ -275,6 +277,7 @@ static bool request_type_is_notification(LSPRequestType type) { case LSP_REQUEST_SHOW_MESSAGE: case LSP_REQUEST_LOG_MESSAGE: case LSP_REQUEST_COMPLETION: + case LSP_REQUEST_SIGNATURE_HELP: case LSP_REQUEST_WORKSPACE_FOLDERS: return false; } @@ -386,6 +389,10 @@ static void write_request(LSP *lsp, LSPRequest *request) { write_obj_end(o); write_key_bool(o, "contextSupport", true); write_obj_end(o); + write_key_obj_start(o, "signatureHelp"); + // we don't have context support because sending the activeSignatureHelp member is annoying + //write_key_bool(o, "contextSupport", true); + 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 a490aad..9ec7ad7 100644 --- a/lsp.c +++ b/lsp.c @@ -447,7 +447,9 @@ void lsp_free(LSP *lsp) { arr_free(lsp->workspace_folders); - arr_free(lsp->trigger_chars); + arr_free(lsp->completion_trigger_chars); + arr_free(lsp->signature_help_trigger_chars); + arr_free(lsp->signature_help_retrigger_chars); memset(lsp, 0, sizeof *lsp); free(lsp); } diff --git a/lsp.h b/lsp.h index 5452dc2..1c75861 100644 --- a/lsp.h +++ b/lsp.h @@ -33,6 +33,7 @@ typedef enum { LSP_REQUEST_DID_CLOSE, // textDocument/didClose LSP_REQUEST_DID_CHANGE, // textDocument/didChange LSP_REQUEST_COMPLETION, // textDocument/completion + LSP_REQUEST_SIGNATURE_HELP, // textDocument/signatureHelp LSP_REQUEST_DID_CHANGE_WORKSPACE_FOLDERS, // workspace/didChangeWorkspaceFolders // server-to-client @@ -82,12 +83,15 @@ typedef struct { LSPPosition pos; } LSPDocumentPosition; -typedef enum { - LSP_TRIGGER_NONE = 0, // not actually defined in LSP spec - LSP_TRIGGER_INVOKED = 1, - LSP_TRIGGER_CHARACTER = 2, - LSP_TRIGGER_INCOMPLETE = 3 -} LSPCompletionTriggerKind; + +// these triggers are used for completion context and signature help context. +#define LSP_TRIGGER_NONE 0 // not actually defined in LSP spec +#define LSP_TRIGGER_INVOKED 1 +#define LSP_TRIGGER_CHARACTER 2 +#define LSP_TRIGGER_INCOMPLETE 3 +#define LSP_TRIGGER_CONTENT_CHANGE 3 +typedef u8 LSPCompletionTriggerKind; +typedef u8 LSPSignatureHelpTriggerKind; typedef struct { LSPCompletionTriggerKind trigger_kind; @@ -99,6 +103,10 @@ typedef struct { LSPCompletionContext context; } LSPRequestCompletion; +typedef struct { + LSPDocumentPosition position; +} LSPRequestSignatureHelp; + typedef struct { LSPDocumentID *removed; // dynamic array LSPDocumentID *added; // dynamic array @@ -115,6 +123,7 @@ typedef struct { LSPRequestDidClose close; LSPRequestDidChange change; LSPRequestCompletion completion; + LSPRequestSignatureHelp signature_help; // LSP_REQUEST_SHOW_MESSAGE or LSP_REQUEST_LOG_MESSAGE LSPRequestMessage message; LSPRequestDidChangeWorkspaceFolders change_workspace_folders; @@ -263,6 +272,7 @@ typedef struct { } LSPDocumentData; typedef struct { + bool signature_help_support; bool completion_support; // support for multiple root folders // sadly, as of me writing this, clangd and rust-analyzer don't support this @@ -316,7 +326,11 @@ typedef struct LSP { // never accessed in main thread before `initialized = true`. LSPCapabilities capabilities; // thread-safety: same as `capabilities` - char32_t *trigger_chars; // dynamic array of "trigger characters" + char32_t *completion_trigger_chars; // dynamic array + // thread-safety: same as `capabilities` + char32_t *signature_help_trigger_chars; // dynamic array + // thread-safety: same as `capabilities` + char32_t *signature_help_retrigger_chars; // dynamic array SDL_mutex *workspace_folders_mutex; LSPDocumentID *workspace_folders; // dynamic array of root directories of LSP workspace folders SDL_mutex *error_mutex; diff --git a/main.c b/main.c index e10d6c3..3f028f6 100644 --- a/main.c +++ b/main.c @@ -5,6 +5,8 @@ - hover - go to definition using LSP - find usages +- go through signature help capabilities +- separate signature-help setting (dont use trigger-characters) - check if there are any other non-optional/nice-to-have-support-for server-to-client requests - do something with lsp->error - document lsp.h and lsp.c. @@ -138,6 +140,7 @@ bool tag_goto(Ted *ted, char const *tag); #include "tags.c" #include "menu.c" #include "autocomplete.c" +#include "signature-help.c" #include "command.c" #include "config.c" #include "session.c" @@ -768,13 +771,33 @@ int main(int argc, char **argv) { 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) { + arr_foreach_ptr(lsp->completion_trigger_chars, char32_t, c) { if (*c == last_char) { autocomplete_open(ted, last_char); break; } } + bool signature_help = false; + arr_foreach_ptr(lsp->signature_help_trigger_chars, char32_t, c) { + if (*c == last_char) { + signature_help = true; + break; + } + } + + if (ted->signature_help.open) { + arr_foreach_ptr(lsp->signature_help_retrigger_chars, char32_t, c) { + if (*c == last_char) { + signature_help = true; + break; + } + } + } + + if (signature_help) + signature_help_open(ted, last_char); + if (settings->identifier_trigger_characters && is_word(last_char) && !is_digit(last_char)) autocomplete_open(ted, last_char); diff --git a/signature-help.c b/signature-help.c new file mode 100644 index 0000000..df8c564 --- /dev/null +++ b/signature-help.c @@ -0,0 +1,12 @@ +// deals with textDocument/signatureHelp LSP requests + +void signature_help_open(Ted *ted, char32_t trigger) { + (void)trigger; // for now we don't send context + TextBuffer *buffer = ted->active_buffer; + if (!buffer) return; + LSP *lsp = buffer_lsp(buffer); + LSPRequest request = {.type = LSP_REQUEST_SIGNATURE_HELP}; + LSPRequestSignatureHelp *s = &request.data.signature_help; + s->position = buffer_cursor_pos_as_lsp_document_position(buffer); + lsp_send_request(lsp, &request); +} diff --git a/ted.h b/ted.h index 8d5d40c..7cfdbd4 100644 --- a/ted.h +++ b/ted.h @@ -417,6 +417,11 @@ typedef struct { Rect rect; // rectangle where the autocomplete menu is (needed to avoid interpreting autocomplete clicks as other clicks) } Autocomplete; +// "signature help" (LSP) is thing that shows the current parameter, etc. +typedef struct { + bool open; +} SignatureHelp; + typedef struct Ted { struct LSP *lsps[TED_LSP_MAX + 1]; @@ -463,6 +468,7 @@ 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; @@ -542,6 +548,7 @@ Settings *ted_active_settings(Ted *ted); Settings *ted_get_settings(Ted *ted, const char *path, Language lang); void ted_load_configs(Ted *ted, bool reloading); struct LSP *ted_get_lsp(Ted *ted, const char *path, Language lang); +struct LSP *ted_active_lsp(Ted *ted); struct LSP *ted_get_lsp_by_id(Ted *ted, u32 id); static TextBuffer *find_search_buffer(Ted *ted); // first, we read all config files, then we parse them. -- cgit v1.2.3