From 8d96a4b0f0ebb059a63cc4c3193e0169ccf4f5b5 Mon Sep 17 00:00:00 2001 From: pommicket Date: Fri, 23 Dec 2022 21:43:07 -0500 Subject: framework for having multiple/configurable LSPs --- buffer.c | 2 +- lsp.c | 12 +++++++-- lsp.h | 9 +++---- main.c | 80 +++++++-------------------------------------------------- process-posix.c | 1 + process-win.c | 2 +- process.h | 1 + ted.c | 12 ++++++--- ted.cfg | 4 ++- ted.h | 7 +++-- 10 files changed, 44 insertions(+), 86 deletions(-) diff --git a/buffer.c b/buffer.c index 6df6c9c..d904c38 100644 --- a/buffer.c +++ b/buffer.c @@ -289,7 +289,7 @@ Language buffer_language(TextBuffer *buffer) { LSP *buffer_lsp(TextBuffer *buffer) { if (!buffer_is_named_file(buffer)) return NULL; - return ted_get_lsp(buffer->ted, buffer_language(buffer)); + return ted_get_lsp(buffer->ted, buffer->filename, buffer_language(buffer)); } diff --git a/lsp.c b/lsp.c index 01a2bf6..a8a2445 100644 --- a/lsp.c +++ b/lsp.c @@ -291,12 +291,16 @@ u32 lsp_document_id(LSP *lsp, const char *path) { return *value; } -bool lsp_create(LSP *lsp, const char *analyzer_command) { +LSP *lsp_create(const char *root_dir, Language language, const char *analyzer_command) { + LSP *lsp = calloc(1, sizeof *lsp); + if (!lsp) return NULL; + ProcessSettings settings = { .stdin_blocking = true, .stdout_blocking = false, .stderr_blocking = false, .separate_stderr = true, + .working_directory = root_dir, }; process_run_ex(&lsp->process, analyzer_command, &settings); LSPRequest initialize = { @@ -307,11 +311,13 @@ bool lsp_create(LSP *lsp, const char *analyzer_command) { write_request(lsp, &initialize); str_hash_table_create(&lsp->document_ids, sizeof(u32)); + strbuf_cpy(lsp->root_dir, root_dir); + lsp->language = language; lsp->quit_sem = SDL_CreateSemaphore(0); lsp->messages_mutex = SDL_CreateMutex(); lsp->requests_mutex = SDL_CreateMutex(); lsp->communication_thread = SDL_CreateThread(lsp_communication_thread, "LSP communicate", lsp); - return true; + return lsp; } bool lsp_next_message(LSP *lsp, LSPMessage *message) { @@ -343,6 +349,8 @@ void lsp_free(LSP *lsp) { } arr_free(lsp->messages); arr_free(lsp->trigger_chars); + memset(lsp, 0, sizeof *lsp); + free(lsp); } void lsp_document_changed(LSP *lsp, const char *document, LSPDocumentChangeEvent change) { diff --git a/lsp.h b/lsp.h index 16a6491..8383191 100644 --- a/lsp.h +++ b/lsp.h @@ -271,10 +271,7 @@ typedef struct LSP { LSPRequest *requests_server2client; // we keep track of client-to-server requests // so that we can process responses. - // also fucking rust-analyzer gives "waiting for cargo metadata or cargo check" - // WHY NOT JUST WAIT UNTIL YOUVE DONE THAT BEFORE SENDING THE INITIALIZE RESPONSE. YOU HAVE NOT FINISHED INITIALIZATION. YOU ARE LYING. - // YOU GIVE A -32801 ERROR CODE WHICH IS "ContentModified" -- WHAT THE FUCK? THATS JUST COMPLETLY WRONG - // so we need to re-send requests in that case. + // this also lets us re-send requests if that's ever necessary. LSPRequest *requests_sent; SDL_mutex *requests_mutex; bool initialized; // has the response to the initialize request been sent? @@ -284,6 +281,8 @@ typedef struct LSP { bool provides_completion; // can this LSP server handle completion requests? char32_t *trigger_chars; // dynamic array of "trigger characters" SDL_mutex *error_mutex; + Language language; + char root_dir[TED_PATH_MAX]; char error[256]; } LSP; @@ -299,7 +298,7 @@ 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); const char *lsp_response_string(const LSPResponse *response, LSPString string); -bool lsp_create(LSP *lsp, const char *analyzer_command); +LSP *lsp_create(const char *root_dir, Language language, const char *analyzer_command); bool lsp_next_message(LSP *lsp, LSPMessage *message); void lsp_document_changed(LSP *lsp, const char *document, LSPDocumentChangeEvent change); void lsp_free(LSP *lsp); diff --git a/main.c b/main.c index da6e3e2..997d3c9 100644 --- a/main.c +++ b/main.c @@ -307,74 +307,6 @@ int main(int argc, char **argv) { PROFILE_TIME(init_start) PROFILE_TIME(basic_init_start) - - if (0) { - // @TODO TEMPORARY - LSP lsp={0}; -// chdir("/p/test-lsp"); - if (!lsp_create(&lsp, "rust-analyzer")) { - printf("lsp_create: %s\n",lsp.error); - exit(1); - } - usleep(1000000);//if we don't do this we get "waiting for cargo metadata or cargo check" - LSPRequest test_req = {.type = LSP_REQUEST_COMPLETION}; - test_req.data.completion = (LSPRequestCompletion){ - .position = { - .document = lsp_document_id(&lsp, "/p/test-lsp/src/main.rs"), - .pos = { - .line = 2, - .character = 2, - }, - } - }; - lsp_send_request(&lsp, &test_req); - while (1) { - LSPMessage message = {0}; - while (lsp_next_message(&lsp, &message)) { - if (message.type == LSP_RESPONSE) { - const LSPResponse *response = &message.u.response; - switch (response->request.type) { - case LSP_REQUEST_COMPLETION: { - const LSPResponseCompletion *completion = &response->data.completion; - arr_foreach_ptr(completion->items, LSPCompletionItem, item) { - printf("(%d)%s => ", - item->text_edit.type, - lsp_response_string(response, item->label)); - printf("%s\n", - lsp_response_string(response, item->text_edit.new_text)); - } - } break; - default: - break; - } - } else if (message.type == LSP_REQUEST) { - const LSPRequest *request = &message.u.request; - switch (request->type) { - case LSP_REQUEST_SHOW_MESSAGE: { - const LSPRequestMessage *m = &request->data.message; - // @TODO actually show - printf("Show (%d): %s\n", m->type, m->message); - } break; - case LSP_REQUEST_LOG_MESSAGE: { - const LSPRequestMessage *m = &request->data.message; - // @TODO actually log - printf("Log (%d): %s\n", m->type, m->message); - } break; - default: break; - } - } - lsp_message_free(&message); - } - char error[256]; - if (lsp_get_error(&lsp, error, sizeof error, true)) { - printf("lsp error: %s\n", error); - } - usleep(10000); - } - lsp_free(&lsp); - exit(0); - } - #if __unix__ { struct sigaction act = {0}; @@ -432,9 +364,9 @@ int main(int argc, char **argv) { // @TODO TEMPORARY - ted->test_lsp = calloc(1, sizeof(LSP)); - if (!lsp_create(ted->test_lsp, "rust-analyzer")) { - printf("lsp_create: %s\n",ted->test_lsp->error); + ted->lsps[0] = lsp_create("/p/autosdf", LANG_RUST, "rust-analyzer"); + if (!ted->lsps[0]) { + printf("couldn't create LSP\n"); exit(1); } @@ -1175,8 +1107,14 @@ int main(int argc, char **argv) { build_stop(ted); if (ted->menu) menu_close(ted); + autocomplete_close(ted); session_write(ted); + for (int i = 0; i < TED_LSP_MAX; ++i) { + if (!ted->lsps[i]) break; + lsp_free(ted->lsps[i]); + ted->lsps[i] = NULL; + } arr_foreach_ptr(ted->shell_history, char *, cmd) { free(*cmd); } diff --git a/process-posix.c b/process-posix.c index c8ea376..57af73f 100644 --- a/process-posix.c +++ b/process-posix.c @@ -50,6 +50,7 @@ bool process_run_ex(Process *proc, const char *command, const ProcessSettings *s pid_t pid = fork(); if (pid == 0) { // child process + chdir(settings->working_directory); // put child in its own group. it will be in this group with all of its descendents, // so by killing everything in the group, we kill all the descendents of this process. // if we didn't do this, we would just be killing the sh process in process_kill. diff --git a/process-win.c b/process-win.c index 8ea7b91..58b7f73 100644 --- a/process-win.c +++ b/process-win.c @@ -1,4 +1,4 @@ -#error "@TODO : implement process_write, separate_stderr" +#error "@TODO : implement process_write, separate_stderr, working_directory" #include "process.h" diff --git a/process.h b/process.h index 2d5a10c..163d7cf 100644 --- a/process.h +++ b/process.h @@ -10,6 +10,7 @@ typedef struct { bool stdout_blocking; bool separate_stderr; bool stderr_blocking; // not applicable if separate_stderr is false. + const char *working_directory; } ProcessSettings; // get process ID of this process diff --git a/ted.c b/ted.c index c9ac007..8bc4f18 100644 --- a/ted.c +++ b/ted.c @@ -66,9 +66,15 @@ Settings *ted_active_settings(Ted *ted) { return settings; } -LSP *ted_get_lsp(Ted *ted, Language lang) { - // @TODO - return ted->test_lsp; +LSP *ted_get_lsp(Ted *ted, const char *path, Language lang) { + for (int i = 0; i < TED_LSP_MAX; ++i) { + LSP *lsp = ted->lsps[i]; + if (!lsp) break; + if (lsp->language != lang) continue; + if (!str_has_prefix(path, lsp->root_dir)) continue; + return lsp; + } + return NULL; } LSP *ted_get_active_lsp(Ted *ted) { diff --git a/ted.cfg b/ted.cfg index 4d1d843..98f8c5e 100644 --- a/ted.cfg +++ b/ted.cfg @@ -242,11 +242,13 @@ cancel = #ffa # autocomplete autocomplete-bg = #000 autocomplete-border = #999 -autocomplete-hl = #0a0 +autocomplete-hl = #f6a3 +# these control the text color for various kinds of completions autocomplete-variable = #bfb autocomplete-function = #aaf autocomplete-type = #fac + # Syntax highlighting keyword = #0c0 preprocessor = #77f diff --git a/ted.h b/ted.h index 4c7da63..a06b56d 100644 --- a/ted.h +++ b/ted.h @@ -4,6 +4,8 @@ #define TEXT_SIZE_MIN 6 #define TEXT_SIZE_MAX 70 +// max number of LSPs running at once +#define TED_LSP_MAX 200 // these all say "CPP" but really they're C/C++ enum { @@ -395,8 +397,9 @@ typedef struct { Rect rect; // rectangle where the autocomplete menu is (needed to avoid interpreting autocomplete clicks as other clicks) } Autocomplete; + typedef struct Ted { - struct LSP *test_lsp; // @TODO: something better + struct LSP *lsps[TED_LSP_MAX]; // current time, as of the start of this frame struct timespec frame_time; @@ -516,7 +519,7 @@ void ted_switch_to_buffer(Ted *ted, TextBuffer *buffer); // the settings of the active buffer, or the default settings if there is no active buffer Settings *ted_active_settings(Ted *ted); void ted_load_configs(Ted *ted, bool reloading); -struct LSP *ted_get_lsp(Ted *ted, Language lang); +struct LSP *ted_get_lsp(Ted *ted, const char *path, Language lang); static TextBuffer *find_search_buffer(Ted *ted); // first, we read all config files, then we parse them. // this is because we want less specific settings (e.g. settings applied -- cgit v1.2.3