diff options
author | Leo Tenenbaum <pommicket@gmail.com> | 2021-02-04 13:00:26 -0500 |
---|---|---|
committer | Leo Tenenbaum <pommicket@gmail.com> | 2021-02-04 13:00:26 -0500 |
commit | 79766eb49ee18073da2950c22a271e5820b9f740 (patch) | |
tree | ce527bb878182efc9a63d7af49bb85aaa82d132c | |
parent | c77fc69367003edb43b480f1726db02641bdd0be (diff) |
improve auto-add-newline, start reloading
-rw-r--r-- | buffer.c | 375 | ||||
-rw-r--r-- | config.c | 1 | ||||
-rw-r--r-- | main.c | 10 | ||||
-rw-r--r-- | node.c | 46 | ||||
-rw-r--r-- | ted.c | 1 | ||||
-rw-r--r-- | ted.cfg | 2 | ||||
-rw-r--r-- | ted.h | 4 |
7 files changed, 242 insertions, 197 deletions
@@ -570,179 +570,6 @@ void buffer_clear(TextBuffer *buffer) { memcpy(buffer->error, error, sizeof error); } -// 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"); - bool success = true; - if (fp) { - fseek(fp, 0, SEEK_END); - long file_pos = ftell(fp); - size_t file_size = (size_t)file_pos; - fseek(fp, 0, SEEK_SET); - if (file_pos == -1 || file_pos == LONG_MAX) { - buffer_seterr(buffer, "Couldn't get file position. There is something wrong with the file '%s'.", filename); - success = false; - } else if (file_size > 10L<<20) { - 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) { - u32 lines_capacity = 4; - Line *lines = buffer_calloc(buffer, lines_capacity, sizeof *buffer->lines); // initial lines - if (lines) { - u32 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)(-1)) { - // 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); - } - } - 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; - } - } - } else { - 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); - } - } - free(file_contents); - } - if (ferror(fp)) { - buffer_seterr(buffer, "Error reading from file."); - success = false; - } - } - if (fclose(fp) != 0) { - buffer_seterr(buffer, "Error closing file."); - success = false; - } - } else { - buffer_seterr(buffer, "Couldn't open file %s: %s.", filename, strerror(errno)); - success = false; - } - return success; -} - -void buffer_new_file(TextBuffer *buffer, char const *filename) { - buffer_clear(buffer); - - buffer->filename = buffer_strdup(buffer, filename); - buffer->lines_capacity = 4; - buffer->lines = buffer_calloc(buffer, buffer->lines_capacity, sizeof *buffer->lines); - buffer->nlines = 1; -} - -// Save the buffer to its current filename. This will rewrite the entire file, regardless of -// whether there are any unsaved changes. -bool buffer_save(TextBuffer *buffer) { - Settings const *settings = buffer_settings(buffer); - if (!buffer->is_line_buffer && buffer->filename) { - FILE *out = fopen(buffer->filename, "wb"); - if (out) { - for (Line *line = buffer->lines, *end = line + buffer->nlines; line != end; ++line) { - for (char32_t *p = line->str, *p_end = p + line->len; p != p_end; ++p) { - char utf8[4] = {0}; - size_t bytes = unicode_utf32_to_utf8(utf8, *p); - if (bytes != (size_t)-1) { - if (fwrite(utf8, 1, bytes, out) != bytes) { - buffer_seterr(buffer, "Couldn't write to %s.", buffer->filename); - } - } - } - - if (line != end-1) { - putc('\n', out); - } else { - if (settings->auto_add_newline) { - if (line->len) { - // if the last line isn't empty, add a newline. - putc('\n', out); - } - } - } - } - if (ferror(out)) { - if (!buffer_haserr(buffer)) - buffer_seterr(buffer, "Couldn't write to %s.", buffer->filename); - } - if (fclose(out) != 0) { - if (!buffer_haserr(buffer)) - buffer_seterr(buffer, "Couldn't close file %s.", buffer->filename); - } - bool success = !buffer_haserr(buffer); - if (success) { - buffer->modified = false; - } - return success; - } else { - buffer_seterr(buffer, "Couldn't open file %s for writing: %s.", buffer->filename, strerror(errno)); - return false; - } - } else { - // user tried to save line buffer. whatever - return true; - } -} - -// save, but with a different file name -bool buffer_save_as(TextBuffer *buffer, char const *new_filename) { - char *prev_filename = buffer->filename; - if ((buffer->filename = buffer_strdup(buffer, new_filename))) { - if (buffer_save(buffer)) { - free(prev_filename); - return true; - } else { - free(buffer->filename); - buffer->filename = prev_filename; - return false; - } - } else { - return false; - } -} // print the contents of a buffer to stdout static void buffer_print(TextBuffer const *buffer) { @@ -1859,6 +1686,208 @@ 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"); + bool success = true; + if (fp) { + fseek(fp, 0, SEEK_END); + long file_pos = ftell(fp); + size_t file_size = (size_t)file_pos; + fseek(fp, 0, SEEK_SET); + if (file_pos == -1 || file_pos == LONG_MAX) { + buffer_seterr(buffer, "Couldn't get file position. There is something wrong with the file '%s'.", filename); + success = false; + } else if (file_size > 10L<<20) { + 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) { + u32 lines_capacity = 4; + Line *lines = buffer_calloc(buffer, lines_capacity, sizeof *buffer->lines); // initial lines + if (lines) { + u32 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)(-1)) { + // 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); + } + } + 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; + } + } + } else { + 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); + } + } + free(file_contents); + } + if (ferror(fp)) { + buffer_seterr(buffer, "Error reading from file."); + success = false; + } + } + if (fclose(fp) != 0) { + buffer_seterr(buffer, "Error closing file."); + success = false; + } + } else { + buffer_seterr(buffer, "Couldn't open file %s: %s.", filename, strerror(errno)); + success = false; + } + return success; +} + +// Reloads the file loaded in the buffer. +// Note that this clears undo history, etc. +void buffer_reload(TextBuffer *buffer) { + if (buffer->filename && !buffer_is_untitled(buffer)) { + BufferPos cursor_pos = buffer->cursor_pos; + buffer_load_file(buffer, buffer->filename); + buffer->cursor_pos = cursor_pos; + buffer_validate_cursor(buffer); + } +} + +// has this buffer been changed by another program since last save? +bool buffer_externally_changed(TextBuffer *buffer) { + if (!buffer->filename || buffer_is_untitled(buffer)) + return false; + + if (!timespec_eq(buffer->last_write_time, time_last_modified(buffer->filename))) { + // the write time has been updated, but has the file actually been changed? + // @TODO + } + return false; +} + +void buffer_new_file(TextBuffer *buffer, char const *filename) { + buffer_clear(buffer); + + buffer->filename = buffer_strdup(buffer, filename); + buffer->lines_capacity = 4; + buffer->lines = buffer_calloc(buffer, buffer->lines_capacity, sizeof *buffer->lines); + buffer->nlines = 1; +} + +// Save the buffer to its current filename. This will rewrite the entire file, regardless of +// whether there are any unsaved changes. +bool buffer_save(TextBuffer *buffer) { + Settings const *settings = buffer_settings(buffer); + if (!buffer->is_line_buffer && buffer->filename) { + FILE *out = fopen(buffer->filename, "wb"); + if (out) { + for (u32 i = 0; i < buffer->nlines; ++i) { + Line *line = &buffer->lines[i]; + for (char32_t *p = line->str, *p_end = p + line->len; p != p_end; ++p) { + char utf8[4] = {0}; + size_t bytes = unicode_utf32_to_utf8(utf8, *p); + if (bytes != (size_t)-1) { + if (fwrite(utf8, 1, bytes, out) != bytes) { + buffer_seterr(buffer, "Couldn't write to %s.", buffer->filename); + } + } + } + + if (i != buffer->nlines-1) { + putc('\n', out); + } else { + if (settings->auto_add_newline) { + if (line->len) { + // if the last line isn't empty, add a newline to the end of the file + char32_t c = '\n'; + String32 s = {&c, 1}; + buffer_insert_text_at_pos(buffer, buffer_end_of_file(buffer), s); + } + } + } + } + if (ferror(out)) { + if (!buffer_haserr(buffer)) + buffer_seterr(buffer, "Couldn't write to %s.", buffer->filename); + } + if (fclose(out) != 0) { + if (!buffer_haserr(buffer)) + buffer_seterr(buffer, "Couldn't close file %s.", buffer->filename); + } + bool success = !buffer_haserr(buffer); + if (success) { + buffer->modified = false; + } + buffer->last_write_time = time_last_modified(buffer->filename); + return success; + } else { + buffer_seterr(buffer, "Couldn't open file %s for writing: %s.", buffer->filename, strerror(errno)); + return false; + } + } else { + // user tried to save line buffer. whatever + return true; + } +} + +// save, but with a different file name +bool buffer_save_as(TextBuffer *buffer, char const *new_filename) { + char *prev_filename = buffer->filename; + if ((buffer->filename = buffer_strdup(buffer, new_filename))) { + if (buffer_save(buffer)) { + free(prev_filename); + return true; + } else { + free(buffer->filename); + buffer->filename = prev_filename; + return false; + } + } else { + return false; + } +} + // for debugging #if DEBUG static void buffer_pos_check_valid(TextBuffer *buffer, BufferPos p) { @@ -174,6 +174,7 @@ void config_read(Ted *ted, char const *filename) { OptionBool const options_bool[] = { {"auto-indent", &settings->auto_indent}, {"auto-add-newline", &settings->auto_add_newline}, + {"auto-reload", &settings->auto_reload}, {"syntax-highlighting", &settings->syntax_highlighting}, {"line-numbers", &settings->line_numbers}, }; @@ -39,8 +39,8 @@ no_warn_end #include "command.h" #include "colors.h" -#include "ted.h" #include "time.c" +#include "ted.h" #include "string32.c" #include "arr.c" #include "syntax.c" @@ -462,6 +462,14 @@ int main(int argc, char **argv) { } } } + + // check if active buffer should be reloaded + { + TextBuffer *active_buffer = ted->active_buffer; + if (active_buffer && buffer_externally_changed(active_buffer)) { + buffer_reload(active_buffer); + } + } u32 key_modifier = (u32)ctrl_down << KEY_MODIFIER_CTRL_BIT @@ -94,31 +94,33 @@ static void node_frame(Ted *ted, Node *node, Rect r) { { // tab bar u16 ntabs = (u16)arr_len(node->tabs); float tab_width = r.size.x / ntabs; - for (u16 c = 0; c < ted->nmouse_clicks[SDL_BUTTON_LEFT]; ++c) { - v2 click = ted->mouse_clicks[SDL_BUTTON_LEFT][c]; - if (rect_contains_point(tab_bar_rect, click)) { - u16 tab_index = (u16)((click.x - r.pos.x) / tab_width); - node_switch_to_tab(ted, node, tab_index); + if (!ted->menu) { + for (u16 c = 0; c < ted->nmouse_clicks[SDL_BUTTON_LEFT]; ++c) { + v2 click = ted->mouse_clicks[SDL_BUTTON_LEFT][c]; + if (rect_contains_point(tab_bar_rect, click)) { + u16 tab_index = (u16)((click.x - r.pos.x) / tab_width); + node_switch_to_tab(ted, node, tab_index); + } } - } - for (u16 c = 0; c < ted->nmouse_clicks[SDL_BUTTON_MIDDLE]; ++c) { - v2 click = ted->mouse_clicks[SDL_BUTTON_MIDDLE][c]; - if (rect_contains_point(tab_bar_rect, click)) { - u16 tab_index = (u16)((click.x - r.pos.x) / tab_width); - u16 buffer_idx = node->tabs[tab_index]; - TextBuffer *buffer = &ted->buffers[buffer_idx]; - // close that tab - if (buffer_unsaved_changes(buffer)) { - // make sure unsaved changes dialog is opened - ted_switch_to_buffer(ted, buffer_idx); - command_execute(ted, CMD_TAB_CLOSE, 1); - } else { - if (!node_tab_close(ted, node, tab_index)) { - return; // node closed + for (u16 c = 0; c < ted->nmouse_clicks[SDL_BUTTON_MIDDLE]; ++c) { + v2 click = ted->mouse_clicks[SDL_BUTTON_MIDDLE][c]; + if (rect_contains_point(tab_bar_rect, click)) { + u16 tab_index = (u16)((click.x - r.pos.x) / tab_width); + u16 buffer_idx = node->tabs[tab_index]; + TextBuffer *buffer = &ted->buffers[buffer_idx]; + // close that tab + if (buffer_unsaved_changes(buffer)) { + // make sure unsaved changes dialog is opened + ted_switch_to_buffer(ted, buffer_idx); + command_execute(ted, CMD_TAB_CLOSE, 1); + } else { + if (!node_tab_close(ted, node, tab_index)) { + return; // node closed + } } + ntabs = (u16)arr_len(node->tabs); + tab_width = r.size.x / ntabs; } - ntabs = (u16)arr_len(node->tabs); - tab_width = r.size.x / ntabs; } } @@ -193,6 +193,7 @@ static bool ted_open_file(Ted *ted, char const *filename) { for (u16 i = 0; i < TED_MAX_BUFFERS; ++i) { if (buffers_used[i]) { if (streq(filename, buffer_get_filename(&buffers[i]))) { + buffer_reload(&buffers[i]); // make sure buffer is up to date with the file ted_switch_to_buffer(ted, i); return true; } @@ -21,6 +21,8 @@ auto-indent = on auto-add-newline = on syntax-highlighting = on line-numbers = on +# Reload files when changed? +auto-reload = off [keyboard] # motion and selection @@ -71,6 +71,7 @@ typedef struct { bool auto_add_newline; bool syntax_highlighting; bool line_numbers; + bool auto_reload; u8 tab_width; u8 cursor_width; u8 undo_save_time; @@ -124,6 +125,7 @@ typedef struct { char *filename; // NULL if this buffer doesn't correspond to a file (e.g. line buffers) struct Ted *ted; // we keep a back-pointer to the ted instance so we don't have to pass it in to every buffer function double scroll_x, scroll_y; // number of characters scrolled in the x/y direction + struct timespec last_write_time; // last write time to filename. BufferPos cursor_pos; BufferPos selection_pos; // if selection is true, the text between selection_pos and cursor_pos is selected. bool is_line_buffer; // "line buffers" are buffers which can only have one line of text (used for inputs) @@ -151,7 +153,7 @@ ENUM_U16 { MENU_NONE, MENU_OPEN, MENU_SAVE_AS, - MENU_WARN_UNSAVED // warn about unsaved changes + MENU_WARN_UNSAVED, // warn about unsaved changes } ENUM_U16_END(Menu); // file entries for file selectors |