diff options
author | pommicket <pommicket@gmail.com> | 2022-12-09 15:57:24 -0500 |
---|---|---|
committer | pommicket <pommicket@gmail.com> | 2022-12-09 15:57:24 -0500 |
commit | 2dce45002b9d4d93cc2db15b1697d13c1709fb92 (patch) | |
tree | 808efd41be71afe286a1d8bf83480ca1ff7df75d /lsp.c | |
parent | 27c9af7de5f47c33217be02107e31061366f481f (diff) |
more lsp
Diffstat (limited to 'lsp.c')
-rw-r--r-- | lsp.c | 362 |
1 files changed, 262 insertions, 100 deletions
@@ -1,36 +1,86 @@ +// @TODO: documentation +// @TODO : make sure offsets are utf-16! // @TODO: maximum queue size for requests/responses just in case? typedef enum { + LSP_REQUEST, + LSP_RESPONSE +} LSPMessageType; + +typedef enum { LSP_NONE, + + // client-to-server LSP_INITIALIZE, LSP_INITIALIZED, LSP_OPEN, LSP_COMPLETION, LSP_SHUTDOWN, - LSP_EXIT + LSP_EXIT, + + // server-to-client + LSP_SHOW_MESSAGE, + LSP_LOG_MESSAGE } LSPRequestType; typedef struct { // buffer language Language language; - // these will be free'd. + // freed by lsp_request_free char *filename; + // freed by lsp_request_free char *file_contents; } LSPRequestOpen; +typedef enum { + ERROR = 1, + WARNING = 2, + INFO = 3, + LOG = 4 +} LSPWindowMessageType; + +typedef struct { + LSPWindowMessageType type; + // freed by lsp_request_free + char *message; +} LSPRequestMessage; + +typedef struct { + // freed by lsp_request_free + char *path; + u32 line; + u32 character; +} LSPDocumentPosition; + +typedef struct { + LSPDocumentPosition position; +} LSPRequestCompletion; + typedef struct { LSPRequestType type; union { LSPRequestOpen open; + LSPRequestCompletion completion; + // for LSP_SHOW_MESSAGE and LSP_LOG_MESSAGE + LSPRequestMessage message; } data; } LSPRequest; typedef struct { + LSPMessageType type; + union { + LSPRequest request; + JSON response; + } u; +} LSPMessage; + +typedef struct { Process process; u64 request_id; - JSON *responses; - SDL_mutex *responses_mutex; - LSPRequest *requests; + LSPMessage *messages; + SDL_mutex *messages_mutex; + LSPRequest *requests_client2server; + LSPRequest *requests_server2client; SDL_mutex *requests_mutex; bool initialized; // has the response to the initialize request been sent? SDL_Thread *communication_thread; @@ -62,17 +112,6 @@ bool lsp_get_error(LSP *lsp, char *error, size_t error_size, bool clear) { SDL_UnlockMutex(lsp->error_mutex);\ } while (0) -static void write_request_content(LSP *lsp, const char *content) { - char header[128]; - size_t content_size = strlen(content); - strbuf_printf(header, "Content-Length: %zu\r\n\r\n", content_size); - #if 0 - printf("\x1b[1m%s%s\x1b[0m\n", header, content); - #endif - process_write(&lsp->process, header, strlen(header)); - process_write(&lsp->process, content, content_size); -} - static const char *lsp_language_id(Language lang) { switch (lang) { case LANG_CONFIG: @@ -112,67 +151,98 @@ static const char *lsp_language_id(Language lang) { static u64 write_request(LSP *lsp, const LSPRequest *request) { unsigned long long id = lsp->request_id++; + StrBuilder builder = str_builder_new(); + + u32 max_header_size = 64; + // this is where our header will go + str_builder_append_null(&builder, max_header_size); + + str_builder_append(&builder, "{\"jsonrpc\":\"2.0\","); + switch (request->type) { case LSP_NONE: + // these are server-to-client-only requests + case LSP_SHOW_MESSAGE: + case LSP_LOG_MESSAGE: assert(0); break; case LSP_INITIALIZE: { - char content[1024]; - strbuf_printf(content, - "{\"jsonrpc\":\"2.0\",\"id\":%llu,\"method\":\"initialize\",\"params\":{" + str_builder_appendf(&builder, + "\"id\":%llu,\"method\":\"initialize\",\"params\":{" "\"processId\":%d," "\"capabilities\":{}" - "}}", id, process_get_id()); - write_request_content(lsp, content); - } break; - case LSP_INITIALIZED: { - write_request_content(lsp, "{\"jsonrpc\":\"2.0\",\"method\":\"initialized\"}"); + "}", id, process_get_id()); } break; + case LSP_INITIALIZED: + str_builder_append(&builder, "\"method\":\"initialized\""); + break; case LSP_OPEN: { const LSPRequestOpen *open = &request->data.open; char *escaped_filename = json_escape(open->filename); - char *did_open = a_sprintf( - "{\"jsonrpc\":\"2.0\",\"id\":%llu,\"method\":\"textDocument/open\",\"params\":{" + char *escaped_text = json_escape(open->file_contents); + + str_builder_appendf(&builder, + "\"id\":%llu,\"method\":\"textDocument/open\",\"params\":{" "textDocument:{" "uri:\"file://%s\"," "languageId:\"%s\"," "version:1," - "text:\"", - id, escaped_filename, lsp_language_id(open->language)); + "text:\"%s\"}}", + id, + escaped_filename, + lsp_language_id(open->language), + escaped_text); + free(escaped_text); free(escaped_filename); - - size_t did_open_sz = strlen(did_open) + 2 * strlen(open->file_contents) + 16; - did_open = realloc(did_open, did_open_sz); - - size_t n = json_escape_to(did_open + strlen(did_open), - did_open_sz - 10 - strlen(did_open), - open->file_contents); - char *p = did_open + n; - sprintf(p, "\"}}}"); - - free(did_open); - - write_request_content(lsp, did_open); } break; case LSP_COMPLETION: { - char content[1024]; - // no params needed - strbuf_printf(content, - "{\"jsonrpc\":\"2.0\",\"id\":%llu,\"method\":\"textDocument/completion\",\"params\":{" - "}}", id); - write_request_content(lsp, content); - } break; - case LSP_SHUTDOWN: { - char content[1024]; - strbuf_printf(content, - "{\"jsonrpc\":\"2.0\",\"id\":%llu,\"method\":\"shutdown\"}", id); - write_request_content(lsp, content); - } break; - case LSP_EXIT: { - write_request_content(lsp, "{\"jsonrpc\":\"2.0\",\"method\":\"exit\"}"); + const LSPRequestCompletion *completion = &request->data.completion; + char *escaped_path = json_escape(completion->position.path); + str_builder_appendf(&builder,"\"id\":%llu,\"method\":\"textDocument/completion\",\"params\":{" + "textDocument:\"%s\"," + "position:{" + "line:%lu," + "character:%lu" + "}" + "}", + id, + escaped_path, + (ulong)completion->position.line, + (ulong)completion->position.character); + free(escaped_path); } break; + case LSP_SHUTDOWN: + str_builder_appendf(&builder, "\"id\":%llu,\"method\":\"shutdown\"", id); + break; + case LSP_EXIT: + str_builder_appendf(&builder, "\"method\":\"exit\""); + break; } + str_builder_append(&builder, "}"); + + // this is kind of hacky but it lets us send the whole request with one write call. + // probably not *actually* needed. i thought it would help fix an error but it didn't. + size_t content_length = str_builder_len(&builder) - max_header_size; + char content_length_str[32]; + sprintf(content_length_str, "%zu", content_length); + size_t header_size = strlen("Content-Length: \r\n\r\n") + strlen(content_length_str); + char *header = &builder.str[max_header_size - header_size]; + strcpy(header, "Content-Length: "); + strcat(header, content_length_str); + // we specifically DON'T want a null byte + memcpy(header + strlen(header), "\r\n\r\n", 4); + + char *content = header; + #if 1 + printf("\x1b[1m%s\x1b[0m\n",content); + #endif + + // @TODO: does write always write the full amount? probably not. this should be fixed. + process_write(&lsp->process, content, strlen(content)); + + str_builder_free(&builder); + return (u64)id; } @@ -195,55 +265,121 @@ static bool has_response(const char *data, size_t data_len, u64 *p_offset, u64 * void lsp_send_request(LSP *lsp, const LSPRequest *request) { SDL_LockMutex(lsp->requests_mutex); - arr_add(lsp->requests, *request); + arr_add(lsp->requests_client2server, *request); SDL_UnlockMutex(lsp->requests_mutex); } -static void process_response(LSP *lsp, const JSON *json) { +static bool parse_server2client_request(LSP *lsp, JSON *json, LSPRequest *request) { + JSONValue method = json_get(json, "method"); + if (method.type != JSON_STRING) { + lsp_set_error(lsp, "Bad type for request method: %s", json_type_to_str(method.type)); + } + + char str[64] = {0}; + json_string_get(json, &method.val.string, str, sizeof str); + + if (streq(str, "window/showMessage")) { + request->type = LSP_SHOW_MESSAGE; + goto window_message; + } else if (streq(str, "window/logMessage")) { + request->type = LSP_LOG_MESSAGE; + window_message:; + JSONValue type = json_get(json, "params.type"); + JSONValue message = json_get(json, "params.message"); + if (type.type != JSON_NUMBER) { + lsp_set_error(lsp, "Expected MessageType, got %s", json_type_to_str(type.type)); + return false; + } + if (message.type != JSON_STRING) { + lsp_set_error(lsp, "Expected message string, got %s", json_type_to_str(message.type)); + return false; + } + + int mtype = (int)type.val.number; + if (mtype < 1 || mtype > 4) { + lsp_set_error(lsp, "Bad MessageType: %g", type.val.number); + return false; + } + + LSPRequestMessage *m = &request->data.message; + m->type = (LSPWindowMessageType)mtype; + m->message = json_string_get_alloc(json, &message.val.string); + return true; + } else if (str_has_prefix(str, "$/")) { + // we can safely ignore this + } else { + lsp_set_error(lsp, "Unrecognized request method: %s", str); + } + return false; +} + +static void process_message(LSP *lsp, JSON *json) { #if 1 printf("\x1b[3m"); json_debug_print(json); printf("\x1b[0m\n"); #endif + bool keep_json = false; + JSONValue error = json_get(json, "error.message"); + if (error.type == JSON_STRING) { + char err[256] = {0}; + json_string_get(json, &error.val.string, err, sizeof err);; + lsp_set_error(lsp, "%s", err); + goto ret; + } JSONValue result = json_get(json, "result"); - JSONValue id = json_get(json, "id"); + if (result.type != JSON_UNDEFINED) { + JSONValue id = json_get(json, "id"); - if (result.type == JSON_UNDEFINED || id.type != JSON_NUMBER) { - // uh oh - JSONValue error = json_get(json, "error.message"); - if (error.type == JSON_STRING) { - char err[256] = {0}; - json_string_get(json, &error.val.string, err, sizeof err);; - lsp_set_error(lsp, "%s", err); + if (id.type != JSON_NUMBER) { + // what + lsp_set_error(lsp, "Response with no ID."); + goto ret; + } + if (id.val.number == 0) { + // it's the response to our initialize request! + // let's send back an "initialized" request (notification) because apparently + // that's something we need to do. + LSPRequest initialized = { + .type = LSP_INITIALIZED, + .data = {0}, + }; + u64 initialized_id = write_request(lsp, &initialized); + // this should be the second request. + (void)initialized_id; + assert(initialized_id == 1); + // we can now send requests which have nothing to do with initialization + lsp->initialized = true; } else { - lsp_set_error(lsp, "Server error (no message)"); + SDL_LockMutex(lsp->messages_mutex); + LSPMessage *message = arr_addp(lsp->messages); + message->type = LSP_RESPONSE; + message->u.response = *json; + SDL_UnlockMutex(lsp->messages_mutex); + keep_json = true; + } + } else if (json_has(json, "method")) { + LSPRequest request = {0}; + if (parse_server2client_request(lsp, json, &request)) { + SDL_LockMutex(lsp->messages_mutex); + LSPMessage *message = arr_addp(lsp->messages); + message->type = LSP_REQUEST; + message->u.request = request; + SDL_UnlockMutex(lsp->messages_mutex); } - } else if (id.val.number == 0) { - // it's the response to our initialize request! - // let's send back an "initialized" request (notification) because apparently - // that's something we need to do. - LSPRequest initialized = { - .type = LSP_INITIALIZED, - .data = {0}, - }; - u64 initialized_id = write_request(lsp, &initialized); - // this should be the second request. - (void)initialized_id; - assert(initialized_id == 1); - // we can now send requests which have nothing to do with initialization - lsp->initialized = true; } else { - SDL_LockMutex(lsp->responses_mutex); - arr_add(lsp->responses, *json); - SDL_UnlockMutex(lsp->responses_mutex); + lsp_set_error(lsp, "Bad message from server (no result, no method)."); } + ret: + if (!keep_json) + json_free(json); } -// receive responses from LSP, up to max_size bytes. +// receive responses/requests/notifications from LSP, up to max_size bytes. static void lsp_receive(LSP *lsp, size_t max_size) { { @@ -283,9 +419,8 @@ static void lsp_receive(LSP *lsp, size_t max_size) { if (json_parse(&json, copy)) { assert(json.text == copy); json.is_text_copied = true; - process_response(lsp, &json); + process_message(lsp, &json); } else { - lsp_set_error(lsp, "couldn't parse response JSON: %s", json.error); json_free(&json); } @@ -298,22 +433,44 @@ static void lsp_receive(LSP *lsp, size_t max_size) { } } -static void free_request(LSPRequest *r) { +static void lsp_position_free(LSPDocumentPosition *position) { + free(position->path); +} + +static void lsp_request_free(LSPRequest *r) { switch (r->type) { case LSP_NONE: assert(0); break; case LSP_INITIALIZE: case LSP_INITIALIZED: - case LSP_COMPLETION: case LSP_SHUTDOWN: case LSP_EXIT: break; + case LSP_COMPLETION: { + LSPRequestCompletion *completion = &r->data.completion; + lsp_position_free(&completion->position); + } break; case LSP_OPEN: { LSPRequestOpen *open = &r->data.open; free(open->filename); free(open->file_contents); } break; + case LSP_SHOW_MESSAGE: + case LSP_LOG_MESSAGE: + free(r->data.message.message); + break; + } +} + +void lsp_message_free(LSPMessage *message) { + switch (message->type) { + case LSP_REQUEST: + lsp_request_free(&message->u.request); + break; + case LSP_RESPONSE: + json_free(&message->u.response); + break; } } @@ -326,10 +483,10 @@ static bool lsp_send(LSP *lsp) { LSPRequest *requests = NULL; SDL_LockMutex(lsp->requests_mutex); - size_t n_requests = arr_len(lsp->requests); + size_t n_requests = arr_len(lsp->requests_client2server); requests = calloc(n_requests, sizeof *requests); - memcpy(requests, lsp->requests, n_requests * sizeof *requests); - arr_clear(lsp->requests); + memcpy(requests, lsp->requests_client2server, n_requests * sizeof *requests); + arr_clear(lsp->requests_client2server); SDL_UnlockMutex(lsp->requests_mutex); bool quit = false; @@ -340,7 +497,7 @@ static bool lsp_send(LSP *lsp) { // whatever. write_request(lsp, r); } - free_request(r); + lsp_request_free(r); if (SDL_SemTryWait(lsp->quit_sem) == 0) { quit = true; @@ -416,27 +573,32 @@ bool lsp_create(LSP *lsp, const char *analyzer_command) { write_request(lsp, &initialize); lsp->quit_sem = SDL_CreateSemaphore(0); - lsp->responses_mutex = SDL_CreateMutex(); + lsp->messages_mutex = SDL_CreateMutex(); lsp->communication_thread = SDL_CreateThread(lsp_communication_thread, "LSP communicate", lsp); return true; } -bool lsp_next_response(LSP *lsp, JSON *json) { +bool lsp_next_message(LSP *lsp, LSPMessage *message) { bool any = false; - SDL_LockMutex(lsp->responses_mutex); - if (arr_len(lsp->responses)) { - *json = lsp->responses[0]; - arr_remove(lsp->responses, 0); + SDL_LockMutex(lsp->messages_mutex); + if (arr_len(lsp->messages)) { + *message = lsp->messages[0]; + arr_remove(lsp->messages, 0); any = true; } - SDL_UnlockMutex(lsp->responses_mutex); + SDL_UnlockMutex(lsp->messages_mutex); return any; } void lsp_free(LSP *lsp) { SDL_SemPost(lsp->quit_sem); SDL_WaitThread(lsp->communication_thread, NULL); + SDL_DestroyMutex(lsp->messages_mutex); SDL_DestroySemaphore(lsp->quit_sem); process_kill(&lsp->process); arr_free(lsp->received_data); + arr_foreach_ptr(lsp->messages, LSPMessage, message) { + lsp_message_free(message); + } + arr_free(lsp->messages); } |