diff options
Diffstat (limited to 'buffer.c')
-rw-r--r-- | buffer.c | 262 |
1 files changed, 179 insertions, 83 deletions
@@ -67,6 +67,10 @@ bool buffer_is_untitled(TextBuffer *buffer) { return false; } +bool buffer_is_named_file(TextBuffer *buffer) { + return buffer->filename && !buffer_is_untitled(buffer); +} + // add this edit to the undo history static void buffer_append_edit(TextBuffer *buffer, BufferEdit const *edit) { // whenever an edit is made, clear the redo history @@ -245,6 +249,8 @@ static inline Font *buffer_font(TextBuffer *buffer) { // what programming language is this? Language buffer_language(TextBuffer *buffer) { + // @TODO: 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); Settings const *settings = buffer->ted->default_settings; // important we don't use buffer_settings here since that would cause a loop! @@ -280,6 +286,13 @@ Language buffer_language(TextBuffer *buffer) { return match; } +LSP *buffer_lsp(TextBuffer *buffer) { + if (!buffer_is_named_file(buffer)) + return NULL; + return ted_get_lsp(buffer->ted, buffer_language(buffer)); +} + + // score is higher if context is closer match. static long context_score(const char *path, Language lang, const SettingsContext *context) { long score = 0; @@ -545,6 +558,10 @@ void buffer_check_valid(TextBuffer *buffer) { } } #else +static void buffer_pos_check_valid(TextBuffer *buffer, BufferPos p) { + (void)buffer; (void)p; +} + void buffer_check_valid(TextBuffer *buffer) { (void)buffer; } @@ -729,6 +746,15 @@ static void buffer_line_free(Line *line) { // Free a buffer. Once a buffer is freed, you can call buffer_create on it again. // Does not free the pointer `buffer` (buffer might not have even been allocated with malloc) 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); + } + Line *lines = buffer->lines; u32 nlines = buffer->nlines; for (u32 i = 0; i < nlines; ++i) { @@ -1180,16 +1206,6 @@ i64 buffer_cursor_move_down(TextBuffer *buffer, i64 by) { return ret; } -// Is this character a "word" character? -// This determines how buffer_pos_move_words (i.e. ctrl+left/right) works -static bool is_word(char32_t c) { - return c > WCHAR_MAX || c == '_' || iswalnum((wint_t)c); -} - -static bool is_space(char32_t c) { - return c > WCHAR_MAX || iswspace((wint_t)c); -} - // move left / right by the specified number of words // returns the number of words successfully moved forward i64 buffer_pos_move_words(TextBuffer *buffer, BufferPos *pos, i64 nwords) { @@ -1370,6 +1386,36 @@ static Status buffer_insert_lines(TextBuffer *buffer, u32 where, u32 number) { return false; } +// 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 lsp_pos = { + .line = pos.line + }; + buffer_pos_validate(buffer, &pos); + const Line *line = &buffer->lines[pos.line]; + const char32_t *str = line->str; + for (uint32_t i = 0; i < pos.index; ++i) { + if (str[i] < 0x10000) + lsp_pos.character += 1; // this codepoint needs 1 UTF-16 word + else + lsp_pos.character += 2; // this codepoint needs 2 UTF-16 words + } + return lsp_pos; +} + +static void buffer_send_lsp_did_change_request(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. + 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); + BufferPos pos_end = buffer_pos_advance(buffer, pos, nchars_deleted); + event.range.end = buffer_pos_to_lsp(buffer, pos_end); + lsp_document_changed(lsp, buffer->filename, event); +} + // inserts the given text, returning the position of the end of the text BufferPos buffer_insert_text_at_pos(TextBuffer *buffer, BufferPos pos, String32 str) { buffer_pos_validate(buffer, &pos); @@ -1399,12 +1445,30 @@ BufferPos buffer_insert_text_at_pos(TextBuffer *buffer, BufferPos pos, String32 str.str = str_copy; } + if (buffer->is_line_buffer) { // remove all the newlines from str. str32_remove_all_instances_of_char(&str, '\n'); } str32_remove_all_instances_of_char(&str, '\r'); + + if (buffer->ted->autocomplete.open) { + // close completions if a non-word character is typed + bool close_completions = false; + for (u32 i = 0; i < str.len; ++i) { + if (!is_word(str.str[i])) { + close_completions = true; + break; + } + } + if (close_completions) + autocomplete_close(buffer->ted); + } + + LSP *lsp = buffer_lsp(buffer); + if (lsp) + buffer_send_lsp_did_change_request(lsp, buffer, pos, 0, str); if (buffer->store_undo_events) { BufferEdit *last_edit = arr_lastp(buffer->undo_history); @@ -1649,6 +1713,34 @@ void buffer_delete_chars_at_pos(TextBuffer *buffer, BufferPos pos, i64 nchars_) // When generating undo events, we allocate nchars characters of memory (see buffer_edit below). // Not doing this might also cause other bugs, best to keep it here just in case. nchars = (u32)buffer_get_text_at_pos(buffer, pos, NULL, nchars); + + if (buffer->ted->autocomplete.open) { + // close completions if a non-word character is deleted + bool close_completions = false; + if (nchars > 256) { + // this is a massive deletion + // even if it's all word characters, let's close the completion menu anyways + close_completions = true; + } else { + char32_t text[256]; + size_t n = buffer_get_text_at_pos(buffer, pos, text, nchars); + (void)n; + assert(n == nchars); + for (u32 i = 0; i < nchars; ++i) { + if (!is_word(text[i])) { + close_completions = true; + break; + } + } + } + if (close_completions) + autocomplete_close(buffer->ted); + } + + + LSP *lsp = buffer_lsp(buffer); + if (lsp) + buffer_send_lsp_did_change_request(lsp, buffer, pos, nchars, (String32){0}); if (buffer->store_undo_events) { // we need to make sure the undo history keeps track of the edit. @@ -2040,7 +2132,6 @@ void buffer_paste(TextBuffer *buffer) { } } - // if an error occurs, buffer is left untouched (except for the error field) and the function returns false. Status buffer_load_file(TextBuffer *buffer, char const *filename) { FILE *fp = fopen(filename, "rb"); @@ -2059,88 +2150,93 @@ Status buffer_load_file(TextBuffer *buffer, char const *filename) { buffer_seterr(buffer, "File too big (size: %zu).", file_size); success = false; } else { - u8 *file_contents = buffer_calloc(buffer, 1, file_size); - if (file_contents) { - lines_capacity = 4; - lines = buffer_calloc(buffer, lines_capacity, sizeof *buffer->lines); // initial lines - if (lines) { - nlines = 1; - size_t bytes_read = fread(file_contents, 1, file_size, fp); - if (bytes_read == file_size) { - char32_t c = 0; - for (u8 *p = file_contents, *end = p + file_size; p != end; ) { - if (*p == '\r' && p != end-1 && p[1] == '\n') { - // CRLF line endings - p += 2; - c = '\n'; - } else { - size_t n = unicode_utf8_to_utf32(&c, (char *)p, (size_t)(end - p)); - if (n == 0) { - // null character - c = 0; - ++p; - } else if (n >= (size_t)(-2)) { - // invalid UTF-8 - success = false; - buffer_seterr(buffer, "Invalid UTF-8 (position: %td).", p - file_contents); - break; - } else { - p += n; - } - } - if (c == '\n') { - if (buffer_lines_set_min_capacity(buffer, &lines, &lines_capacity, nlines + 1)) - ++nlines; - } else { - u32 line_idx = nlines - 1; - Line *line = &lines[line_idx]; - buffer_line_append_char(buffer, line, c); - } - } + u8 *file_contents = buffer_calloc(buffer, 1, file_size + 1); + lines_capacity = 4; + lines = buffer_calloc(buffer, lines_capacity, sizeof *buffer->lines); // initial lines + nlines = 1; + size_t bytes_read = fread(file_contents, 1, file_size, fp); + if (bytes_read == file_size) { + char32_t c = 0; + for (u8 *p = file_contents, *end = p + file_size; p != end; ) { + if (*p == '\r' && p != end-1 && p[1] == '\n') { + // CRLF line endings + p += 2; + c = '\n'; } else { - success = false; + size_t n = unicode_utf8_to_utf32(&c, (char *)p, (size_t)(end - p)); + if (n == 0) { + // null character + c = 0; + ++p; + } else if (n >= (size_t)(-2)) { + // invalid UTF-8 + success = false; + buffer_seterr(buffer, "Invalid UTF-8 (position: %td).", p - file_contents); + break; + } else { + p += n; + } } - if (!success) { - // something went wrong; we need to free all the memory we used - for (u32 i = 0; i < nlines; ++i) - buffer_line_free(&lines[i]); - free(lines); + if (c == '\n') { + if (buffer_lines_set_min_capacity(buffer, &lines, &lines_capacity, nlines + 1)) + ++nlines; + } else { + u32 line_idx = nlines - 1; + Line *line = &lines[line_idx]; + buffer_line_append_char(buffer, line, c); } } - free(file_contents); } - if (ferror(fp)) { + if (ferror(fp)) { buffer_seterr(buffer, "Error reading from file."); success = false; } + if (!success) { + // something went wrong; we need to free all the memory we used + for (u32 i = 0; i < nlines; ++i) + buffer_line_free(&lines[i]); + free(lines); + } + + if (success) { + char *filename_copy = buffer_strdup(buffer, filename); + if (!filename_copy) success = false; + if (success) { + // everything is good + buffer_clear(buffer); + buffer->lines = lines; + buffer->nlines = nlines; + buffer->frame_earliest_line_modified = 0; + buffer->frame_latest_line_modified = nlines - 1; + buffer->lines_capacity = lines_capacity; + buffer->filename = filename_copy; + buffer->last_write_time = time_last_modified(buffer->filename); + if (!(fs_path_permission(filename) & FS_PERMISSION_WRITE)) { + // can't write to this file; make the buffer view only. + 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 + } + } + + free(file_contents); } - if (fclose(fp) != 0) { - buffer_seterr(buffer, "Error closing file."); - success = false; - } + fclose(fp); } else { buffer_seterr(buffer, "Couldn't open file %s: %s.", filename, strerror(errno)); success = false; } - if (success) { - char *filename_copy = buffer_strdup(buffer, filename); - if (!filename_copy) success = false; - if (success) { - // everything is good - buffer_clear(buffer); - buffer->lines = lines; - buffer->nlines = nlines; - buffer->frame_earliest_line_modified = 0; - buffer->frame_latest_line_modified = nlines - 1; - buffer->lines_capacity = lines_capacity; - buffer->filename = filename_copy; - buffer->last_write_time = time_last_modified(buffer->filename); - if (!(fs_path_permission(filename) & FS_PERMISSION_WRITE)) { - // can't write to this file; make the buffer view only. - buffer->view_only = true; - } - } - } return success; } @@ -2284,11 +2380,11 @@ u32 buffer_last_rendered_line(TextBuffer *buffer) { // returns true if the buffer "used" this event bool buffer_handle_click(Ted *ted, TextBuffer *buffer, v2 click, u8 times) { BufferPos buffer_pos; - if (ted->autocomplete) { - if (rect_contains_point(ted->autocomplete_rect, click)) + if (ted->autocomplete.open) { + if (rect_contains_point(ted->autocomplete.rect, click)) return false; // don't look at clicks in the autocomplete menu else - ted->autocomplete = false; // close autocomplete menu if user clicks outside of it + autocomplete_close(ted); // close autocomplete menu if user clicks outside of it } if (buffer_pixels_to_pos(buffer, click, &buffer_pos)) { // user clicked on buffer |