From 44d607df5d5a9a1c892fe56c4874fbef7f209464 Mon Sep 17 00:00:00 2001 From: pommicket Date: Mon, 26 Dec 2022 01:28:49 -0500 Subject: start workspace folders stuff --- lsp-parse.c | 33 +++++++++++++++++ lsp-write.c | 120 ++++++++++++++++++++++++++++++++++++++++++++---------------- lsp.c | 9 ++++- lsp.h | 6 ++- main.c | 2 + ted.c | 7 ++-- 6 files changed, 140 insertions(+), 37 deletions(-) diff --git a/lsp-parse.c b/lsp-parse.c index b5a05d0..de63401 100644 --- a/lsp-parse.c +++ b/lsp-parse.c @@ -266,6 +266,27 @@ static bool parse_completion(LSP *lsp, const JSON *json, LSPResponse *response) 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) { + JSONValue id_value = json_get(json, "id"); + switch (id_value.type) { + case JSON_NUMBER: { + double id = id_value.val.number; + if (id == (u32)id) { + request->id = (u32)id; + return true; + } + } break; + case JSON_STRING: + request->id_string = json_string_get_alloc(json, id_value.val.string); + return true; + default: break; + } + return false; +} + +// returns true if `request` was actually filled with a request. static bool parse_server2client_request(LSP *lsp, JSON *json, LSPRequest *request) { JSONValue method_value = json_get(json, "method"); if (!lsp_expect_string(lsp, method_value, "request method")) @@ -297,6 +318,18 @@ static bool parse_server2client_request(LSP *lsp, JSON *json, LSPRequest *reques m->type = (LSPWindowMessageType)mtype; m->message = json_string_get_alloc(json, message.val.string); return true; + } else if (streq(method, "workspace/workspaceFolders")) { + // we can deal with this request right here + LSPResponse response = {0}; + request = &response.request; + request->type = LSP_REQUEST_WORKSPACE_FOLDERS; + if (!parse_id(json, request)) { + // we can't even send an error response since we have no ID. + debug_println("Bad ID in workspace/workspaceFolders request. This shouldn't happen."); + return false; + } + lsp_send_response(lsp, &response); + return false; } else if (str_has_prefix(method, "$/")) { // we can safely ignore this } else { diff --git a/lsp-write.c b/lsp-write.c index b66def9..ef60b91 100644 --- a/lsp-write.c +++ b/lsp-write.c @@ -217,6 +217,17 @@ static void write_key_range(JSONWriter *o, const char *key, LSPRange range) { write_range(o, range); } +static void write_workspace_folders(JSONWriter *o, char **workspace_folders) { + write_arr_start(o); + arr_foreach_ptr(workspace_folders, char *, folder) { + write_arr_elem_obj_start(o); + write_key_file_uri_direct(o, "uri", *folder); + write_key_string(o, "name", *folder); + write_obj_end(o); + } + write_arr_end(o); +} + static const char *lsp_request_method(LSPRequest *request) { switch (request->type) { case LSP_REQUEST_NONE: break; @@ -240,6 +251,8 @@ static const char *lsp_request_method(LSPRequest *request) { return "shutdown"; case LSP_REQUEST_EXIT: return "exit"; + case LSP_REQUEST_WORKSPACE_FOLDERS: + return "workspace/workspaceFolders"; } assert(0); return "$/ignore"; @@ -259,22 +272,54 @@ 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_WORKSPACE_FOLDERS: return false; } assert(0); return false; } + +static const size_t max_header_size = 64; +static JSONWriter message_writer_new(LSP *lsp) { + JSONWriter writer = json_writer_new(lsp); + // this is where our header will go + str_builder_append_null(&writer.builder, max_header_size); + return writer; +} + +static void message_writer_write_and_free(LSP *lsp, JSONWriter *o) { + StrBuilder builder = o->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 LSP_SHOW_C2S + printf("%s%s%s\n",term_bold(stdout),content,term_clear(stdout)); + #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); +} + // NOTE: don't call lsp_request_free after calling this function. // I will do it for you. static void write_request(LSP *lsp, LSPRequest *request) { - JSONWriter writer = json_writer_new(lsp); + JSONWriter writer = message_writer_new(lsp); JSONWriter *o = &writer; - u32 max_header_size = 64; - // this is where our header will go - str_builder_append_null(&o->builder, max_header_size); - write_obj_start(o); write_key_string(o, "jsonrpc", "2.0"); @@ -291,6 +336,7 @@ static void write_request(LSP *lsp, LSPRequest *request) { // these are server-to-client-only requests case LSP_REQUEST_SHOW_MESSAGE: case LSP_REQUEST_LOG_MESSAGE: + case LSP_REQUEST_WORKSPACE_FOLDERS: assert(0); break; case LSP_REQUEST_INITIALIZED: @@ -335,11 +381,13 @@ static void write_request(LSP *lsp, LSPRequest *request) { 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); + write_obj_end(o); write_obj_end(o); - write_key_file_uri_direct(o, "rootUri", lsp->root_dir); -// write_key_arr_start(o, "workspaceFolders"); -// write_arr_elem_obj_start(o); -// write_arr_end(o); + write_key_file_uri_direct(o, "rootUri", lsp->workspace_folders[0]); + write_key(o, "workspaceFolders"); + write_workspace_folders(o, lsp->workspace_folders); write_key_obj_start(o, "clientInfo"); write_key_string(o, "name", "ted"); write_obj_end(o); @@ -410,29 +458,7 @@ static void write_request(LSP *lsp, LSPRequest *request) { write_obj_end(o); - StrBuilder builder = o->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 LSP_SHOW_C2S - printf("%s%s%s\n",term_bold(stdout),content,term_clear(stdout)); - #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); + message_writer_write_and_free(lsp, o); if (is_notification) { lsp_request_free(request); @@ -442,3 +468,33 @@ static void write_request(LSP *lsp, LSPRequest *request) { SDL_UnlockMutex(lsp->requests_mutex); } } + +// NOTE: don't call lsp_response_free after calling this function. +// I will do it for you. +static void write_response(LSP *lsp, LSPResponse *response) { + + JSONWriter writer = message_writer_new(lsp); + JSONWriter *o = &writer; + LSPRequest *request = &response->request; + + if (request->id_string) + write_key_string(o, "id", request->id_string); + else + write_key_number(o, "id", request->id); + write_key_obj_start(o, "result"); + + switch (response->request.type) { + case LSP_REQUEST_WORKSPACE_FOLDERS: + write_workspace_folders(o, lsp->workspace_folders); + break; + default: + // this is not a valid client-to-server response. + assert(0); + break; + } + write_obj_end(o); + + message_writer_write_and_free(lsp, o); + + lsp_response_free(response); +} diff --git a/lsp.c b/lsp.c index f2c3cf9..4267fd3 100644 --- a/lsp.c +++ b/lsp.c @@ -35,6 +35,7 @@ static void lsp_document_change_event_free(LSPDocumentChangeEvent *event) { } static void lsp_request_free(LSPRequest *r) { + free(r->id_string); switch (r->type) { case LSP_REQUEST_NONE: case LSP_REQUEST_INITIALIZE: @@ -43,6 +44,7 @@ static void lsp_request_free(LSPRequest *r) { case LSP_REQUEST_EXIT: case LSP_REQUEST_COMPLETION: case LSP_REQUEST_DID_CLOSE: + case LSP_REQUEST_WORKSPACE_FOLDERS: break; case LSP_REQUEST_DID_OPEN: { LSPRequestDidOpen *open = &r->data.open; @@ -108,8 +110,10 @@ static bool has_response(const char *data, size_t data_len, u64 *p_offset, u64 * static bool lsp_supports_request(LSP *lsp, const LSPRequest *request) { switch (request->type) { case LSP_REQUEST_NONE: + // return false for server-to-client requests since we should never send them case LSP_REQUEST_SHOW_MESSAGE: case LSP_REQUEST_LOG_MESSAGE: + case LSP_REQUEST_WORKSPACE_FOLDERS: return false; case LSP_REQUEST_INITIALIZE: case LSP_REQUEST_INITIALIZED: @@ -307,7 +311,7 @@ LSP *lsp_create(const char *root_dir, Language language, const char *analyzer_co #endif str_hash_table_create(&lsp->document_ids, sizeof(u32)); - strbuf_cpy(lsp->root_dir, root_dir); + arr_add(lsp->workspace_folders, str_dup(root_dir)); lsp->language = language; lsp->quit_sem = SDL_CreateSemaphore(0); lsp->messages_mutex = SDL_CreateMutex(); @@ -358,6 +362,9 @@ void lsp_free(LSP *lsp) { arr_foreach_ptr(lsp->messages, LSPMessage, message) { lsp_message_free(message); } + arr_foreach_ptr(lsp->workspace_folders, char *, folder) + free(*folder); + arr_free(lsp->workspace_folders); arr_free(lsp->messages); arr_free(lsp->trigger_chars); memset(lsp, 0, sizeof *lsp); diff --git a/lsp.h b/lsp.h index 4301636..666ff1b 100644 --- a/lsp.h +++ b/lsp.h @@ -36,6 +36,7 @@ typedef enum { // server-to-client LSP_REQUEST_SHOW_MESSAGE, LSP_REQUEST_LOG_MESSAGE, + LSP_REQUEST_WORKSPACE_FOLDERS, // NOTE: this is handled directly in lsp-parse.c (because it only needs information from the LSP struct) } LSPRequestType; typedef struct { @@ -100,6 +101,7 @@ typedef struct { // id is set by lsp.c; you shouldn't set it. u32 id; LSPRequestType type; + char *id_string; // if not NULL, this is the ID (only for server-to-client messages; we always use integer IDs) union { LSPRequestDidOpen open; LSPRequestDidClose close; @@ -274,7 +276,7 @@ typedef struct LSP { char32_t *trigger_chars; // dynamic array of "trigger characters" SDL_mutex *error_mutex; Language language; - char root_dir[TED_PATH_MAX]; + char **workspace_folders; // dynamic array of root directories of LSP "workspaces" (meaningless garbage) char error[256]; } LSP; @@ -289,6 +291,8 @@ void lsp_message_free(LSPMessage *message); u32 lsp_document_id(LSP *lsp, const char *path); // don't free the contents of this request! let me handle it! void lsp_send_request(LSP *lsp, LSPRequest *request); +// don't free the contents of this response! let me handle it! +void lsp_send_response(LSP *lsp, LSPResponse *response); const char *lsp_response_string(const LSPResponse *response, LSPString string); LSP *lsp_create(const char *root_dir, Language language, const char *analyzer_command); bool lsp_next_message(LSP *lsp, LSPMessage *message); diff --git a/main.c b/main.c index 6bec271..7fdb6de 100644 --- a/main.c +++ b/main.c @@ -1,6 +1,8 @@ /* @TODO: - what's wrong with gopls? +- ignore telemetry/event +- handle window/showMessageRequest - make sure "save as" works - more LSP stuff: - hover diff --git a/ted.c b/ted.c index 637a592..34d67a2 100644 --- a/ted.c +++ b/ted.c @@ -103,9 +103,10 @@ LSP *ted_get_lsp(Ted *ted, const char *path, Language language) { if (lsp->language != language) continue; // check if root matches up - const char *lsp_root = lsp->root_dir; - if (str_has_path_prefix(path, lsp_root)) - return lsp; + arr_foreach_ptr(lsp->workspace_folders, char *, lsp_root) { + if (str_has_path_prefix(path, *lsp_root)) + return lsp; + } } if (i == TED_LSP_MAX) return NULL; // why are there so many LSP open??? -- cgit v1.2.3