From 27b8b36aea3bb5913a600bc7e5b41f68557c2587 Mon Sep 17 00:00:00 2001 From: pommicket Date: Tue, 27 Dec 2022 13:04:50 -0500 Subject: fixed previous issues, + "save as" should now work --- buffer.c | 103 ++++++++++++++++++++++++++++++++++++++++++--------------------- lsp.c | 6 ++-- lsp.h | 2 ++ main.c | 5 +--- ted.c | 10 ++++++- ted.h | 7 ++++- 6 files changed, 91 insertions(+), 42 deletions(-) diff --git a/buffer.c b/buffer.c index 298e8b6..49d63a4 100644 --- a/buffer.c +++ b/buffer.c @@ -249,7 +249,7 @@ static inline Font *buffer_font(TextBuffer *buffer) { // what programming language is this? Language buffer_language(TextBuffer *buffer) { - // @TODO: cache this? + // @TODO(optimization): cache this? // (we're calling buffer_lsp on every edit and that calls this) if (buffer->manual_language >= 1 && buffer->manual_language <= LANG_COUNT) return (Language)(buffer->manual_language - 1); @@ -286,10 +286,43 @@ Language buffer_language(TextBuffer *buffer) { return match; } +// set filename = NULL to default to buffer->filename +static void buffer_send_lsp_did_close(TextBuffer *buffer, LSP *lsp, const char *filename) { + LSPRequest did_close = {.type = LSP_REQUEST_DID_CLOSE}; + did_close.data.close = (LSPRequestDidClose){ + .document = lsp_document_id(lsp, filename ? filename : buffer->filename) + }; + lsp_send_request(lsp, &did_close); + buffer->lsp_opened_in = 0; +} + +// buffer_contents must either be NULL or allocated with malloc or similar +// - don't free it after calling this function. +// if buffer_contents = NULL, fetches the current buffer contents. +static void buffer_send_lsp_did_open(TextBuffer *buffer, LSP *lsp, char *buffer_contents) { + if (!buffer_contents) + buffer_contents = buffer_contents_utf8_alloc(buffer); + LSPRequest request = {.type = LSP_REQUEST_DID_OPEN}; + LSPRequestDidOpen *open = &request.data.open; + open->file_contents = buffer_contents; + open->document = lsp_document_id(lsp, buffer->filename); + open->language = buffer_language(buffer); + lsp_send_request(lsp, &request); + buffer->lsp_opened_in = lsp->id; +} + LSP *buffer_lsp(TextBuffer *buffer) { if (!buffer_is_named_file(buffer)) return NULL; - return ted_get_lsp(buffer->ted, buffer->filename, buffer_language(buffer)); + LSP *true_lsp = ted_get_lsp(buffer->ted, buffer->filename, buffer_language(buffer)); + LSP *curr_lsp = ted_get_lsp_by_id(buffer->ted, buffer->lsp_opened_in); + if (true_lsp != curr_lsp) { + if (curr_lsp) + buffer_send_lsp_did_close(buffer, curr_lsp, NULL); + if (true_lsp) + buffer_send_lsp_did_open(buffer, true_lsp, NULL); + } + return true_lsp; } @@ -410,11 +443,11 @@ char *buffer_get_utf8_text_at_pos(TextBuffer *buffer, BufferPos pos, size_t ncha } // Puts a UTF-8 string containing the contents of the buffer into out. -// Returns the number of bytes. +// Returns the number of bytes, including a null terminator. // To use, first pass NULL for out to get the number of bytes you need to allocate. size_t buffer_contents_utf8(TextBuffer *buffer, char *out) { - size_t size = 0; char *p = out, x[4]; + size_t size = 0; for (Line *line = buffer->lines, *end = line + buffer->nlines; line != end; ++line) { char32_t *str = line->str; for (u32 i = 0, len = line->len; i < len; ++i) { @@ -428,9 +461,21 @@ size_t buffer_contents_utf8(TextBuffer *buffer, char *out) { size += 1; } } + if (p) *p = '\0'; + size += 1; return size; } +// Returns a UTF-8 string containing the contents of `buffer`. +// free the return value. +char *buffer_contents_utf8_alloc(TextBuffer *buffer) { + size_t size = buffer_contents_utf8(buffer, NULL); + char *s = calloc(1, size); + buffer_contents_utf8(buffer, s); + return s; +} + + static BufferPos buffer_pos_advance(TextBuffer *buffer, BufferPos pos, size_t nchars) { buffer_pos_validate(buffer, &pos); size_t chars_left = nchars; @@ -739,11 +784,7 @@ void buffer_free(TextBuffer *buffer) { LSP *lsp = buffer_lsp(buffer); if (lsp) { - LSPRequest did_close = {.type = LSP_REQUEST_DID_CLOSE}; - did_close.data.close = (LSPRequestDidClose){ - .document = lsp_document_id(lsp, buffer->filename) - }; - lsp_send_request(lsp, &did_close); + buffer_send_lsp_did_close(buffer, lsp, NULL); } } @@ -1395,7 +1436,7 @@ LSPPosition buffer_pos_to_lsp(TextBuffer *buffer, BufferPos pos) { return lsp_pos; } -static void buffer_send_lsp_did_change_request(LSP *lsp, TextBuffer *buffer, BufferPos 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)) return; // this isn't a named buffer so we can't send a didChange request. @@ -1460,7 +1501,7 @@ BufferPos buffer_insert_text_at_pos(TextBuffer *buffer, BufferPos pos, String32 LSP *lsp = buffer_lsp(buffer); if (lsp) - buffer_send_lsp_did_change_request(lsp, buffer, pos, 0, str); + buffer_send_lsp_did_change(lsp, buffer, pos, 0, str); if (buffer->store_undo_events) { BufferEdit *last_edit = arr_lastp(buffer->undo_history); @@ -1732,7 +1773,7 @@ void buffer_delete_chars_at_pos(TextBuffer *buffer, BufferPos pos, i64 nchars_) LSP *lsp = buffer_lsp(buffer); if (lsp) - buffer_send_lsp_did_change_request(lsp, buffer, pos, nchars, (String32){0}); + buffer_send_lsp_did_change(lsp, buffer, pos, nchars, (String32){0}); if (buffer->store_undo_events) { // we need to make sure the undo history keeps track of the edit. @@ -2213,17 +2254,8 @@ Status buffer_load_file(TextBuffer *buffer, char const *filename) { buffer->view_only = true; } - LSP *lsp = buffer_lsp(buffer); - if (lsp) { - // send didOpen - LSPRequest request = {.type = LSP_REQUEST_DID_OPEN}; - LSPRequestDidOpen *open = &request.data.open; - open->file_contents = (char *)file_contents; - open->document = lsp_document_id(lsp, filename); - open->language = buffer_language(buffer); - lsp_send_request(lsp, &request); - file_contents = NULL; // don't free - } + // this will send a didOpen request if needed + buffer_lsp(buffer); } } @@ -2344,24 +2376,24 @@ bool buffer_save(TextBuffer *buffer) { // save, but with a different file name bool buffer_save_as(TextBuffer *buffer, char const *new_filename) { + LSP *lsp = buffer_lsp(buffer); char *prev_filename = buffer->filename; - if ((buffer->filename = buffer_strdup(buffer, new_filename))) { + buffer->filename = buffer_strdup(buffer, new_filename); + + if (buffer->filename && buffer_save(buffer)) { buffer->view_only = false; - // ensure whole file is syntax highlighted when saving with a different // file extension buffer->frame_earliest_line_modified = 0; buffer->frame_latest_line_modified = buffer->nlines - 1; - - if (buffer_save(buffer)) { - free(prev_filename); - return true; - } else { - free(buffer->filename); - buffer->filename = prev_filename; - return false; - } + if (lsp) + buffer_send_lsp_did_close(buffer, lsp, prev_filename); + // we'll send a didOpen the next time buffer_lsp is called. + free(prev_filename); + return true; } else { + free(buffer->filename); + buffer->filename = prev_filename; return false; } } @@ -2430,6 +2462,9 @@ bool buffer_handle_click(Ted *ted, TextBuffer *buffer, v2 click, u8 times) { // Render the text buffer in the given rectangle void buffer_render(TextBuffer *buffer, Rect r) { + + buffer_lsp(buffer); // this will send didOpen/didClose if the buffer's LSP changed + if (r.size.x < 1 || r.size.y < 1) { // rectangle less than 1 pixel // set x1,y1,x2,y2 to an size 0 rectangle diff --git a/lsp.c b/lsp.c index a3e07e7..025cc10 100644 --- a/lsp.c +++ b/lsp.c @@ -1,8 +1,7 @@ // print server-to-client communication #define LSP_SHOW_S2C 0 // print client-to-server communication -#define LSP_SHOW_C2S 1 - +#define LSP_SHOW_C2S 0 #define write_bool lsp_write_bool @@ -325,6 +324,9 @@ LSP *lsp_create(const char *root_dir, Language language, const char *analyzer_co LSP *lsp = calloc(1, sizeof *lsp); if (!lsp) return NULL; + static _Atomic LSPID curr_id = 1; + lsp->id = curr_id++; + #if DEBUG printf("Starting up LSP %p `%s` for language %s in %s\n", (void *)lsp, analyzer_command, language_to_str(language), root_dir); diff --git a/lsp.h b/lsp.h index e0595e2..c3770bb 100644 --- a/lsp.h +++ b/lsp.h @@ -1,4 +1,5 @@ typedef u32 LSPDocumentID; +typedef u32 LSPID; typedef enum { LSP_REQUEST, @@ -270,6 +271,7 @@ typedef struct { } LSPCapabilities; typedef struct LSP { + LSPID id; Process process; u32 request_id; // for our purposes, folders are "documents" diff --git a/main.c b/main.c index e82d539..350e717 100644 --- a/main.c +++ b/main.c @@ -1,8 +1,5 @@ /* @TODO: -- LSP IDs, and make buffer send (didClose +) didOpen if its ID isn't maching up - (this should fix current "unexpected didChange" multi-root rust-analyzer problem and - also fix "save as") - lsp_document_id / lsp_document_path thread-safety - double check thread safety of other things - ignore telemetry/event @@ -19,7 +16,7 @@ - go to definition using LSP - find usages - that thing where it shows you the current function argument -- workspaceFolders support (so we don't need to start up multiple instances of rust-analyzer) +- do something with lsp->error - document lsp.h and lsp.c. - maximum queue size for requests/responses just in case? - idea: configurable timeout diff --git a/ted.c b/ted.c index 825ab36..75a2246 100644 --- a/ted.c +++ b/ted.c @@ -95,6 +95,14 @@ Settings *ted_get_settings(Ted *ted, const char *path, Language language) { return settings; } +LSP *ted_get_lsp_by_id(Ted *ted, LSPID id) { + for (int i = 0; ted->lsps[i]; ++i) { + if (ted->lsps[i]->id == id) + return ted->lsps[i]; + } + return NULL; +} + // IMPORTANT NOTE ABOUT CACHING LSPs: // - be careful if you want to cache LSPs, e.g. adding a LSP *lsp member to `buffer`. // - right now we pretend that the server has workspace folder support until the initialize response is sent. @@ -134,7 +142,7 @@ LSP *ted_get_lsp(Ted *ted, const char *path, Language language) { char *root_dir = settings_get_root_dir(settings, path); ted->lsps[i] = lsp_create(root_dir, language, settings->lsp); free(root_dir); - return ted->lsps[i]; + // don't actually return it yet, since it's still initializing (see above) } return NULL; diff --git a/ted.h b/ted.h index 13e3331..a33a1eb 100644 --- a/ted.h +++ b/ted.h @@ -274,6 +274,9 @@ typedef struct { float x1, y1, x2, y2; u32 nlines; u32 lines_capacity; + + // which LSP this document is open in (this is a LSPID) + u32 lsp_opened_in; u32 undo_history_write_pos; // where in the undo history was the last write? used by buffer_unsaved_changes u32 first_line_on_screen, last_line_on_screen; // which lines are on screen? updated when buffer_render is called. @@ -412,7 +415,7 @@ typedef struct { typedef struct Ted { - struct LSP *lsps[TED_LSP_MAX]; + struct LSP *lsps[TED_LSP_MAX + 1]; // current time, as of the start of this frame struct timespec frame_time; @@ -527,6 +530,7 @@ typedef struct Ted { } Ted; void autocomplete_close(Ted *ted); +char *buffer_contents_utf8_alloc(TextBuffer *buffer); void command_execute(Ted *ted, Command c, i64 argument); 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 @@ -534,6 +538,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_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. // this is because we want less specific settings (e.g. settings applied -- cgit v1.2.3