From 55fd631d86769e719f81206901bc1c3fb598fb5e Mon Sep 17 00:00:00 2001 From: Leo Tenenbaum Date: Fri, 29 Jan 2021 22:33:31 -0500 Subject: quitting, closing tabs --- Untitled | 2 +- command.c | 21 +++++-- command.h | 2 + main.c | 13 ++-- math.c | 19 ++++++ node.c | 51 ++++++++++++++++ ted-base.c | 164 ------------------------------------------------- ted.c | 201 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ted.cfg | 1 + ted.h | 1 + 10 files changed, 300 insertions(+), 175 deletions(-) delete mode 100644 ted-base.c create mode 100644 ted.c diff --git a/Untitled b/Untitled index 3af9812..6609522 100644 --- a/Untitled +++ b/Untitled @@ -1,3 +1,3 @@ -aa 1 2 3 4 +aaaa 1 2 3 4 56 78 910 1112 testing diff --git a/command.c b/command.c index 5c7cfe0..5acd922 100644 --- a/command.c +++ b/command.c @@ -147,17 +147,21 @@ void command_execute(Ted *ted, Command c, i64 argument) { if (buffer) { if (buffer->filename && streq(buffer->filename, TED_UNTITLED)) { // don't worry, this won't catch files called "Untitled"; buffer->filename is the full path. - goto save_as; + command_execute(ted, CMD_SAVE_AS, 1); + return; } buffer_save(buffer); } break; case CMD_SAVE_AS: - save_as: if (buffer && !buffer->is_line_buffer) { menu_open(ted, MENU_SAVE_AS); } break; + case CMD_QUIT: + // @TODO: check for unsaved changes in all buffers + ted->quit = true; + break; case CMD_UNDO: if (buffer) buffer_undo(buffer, argument); break; @@ -189,11 +193,20 @@ void command_execute(Ted *ted, Command c, i64 argument) { } } break; + case CMD_TAB_CLOSE: { + Node *node = ted->active_node; + if (node) { + node_tab_close(ted, node, node->active_tab); + } else { + command_execute(ted, CMD_QUIT, 1); + return; + } + } break; case CMD_TAB_NEXT: - node_tab_next(ted, ted->active_node, argument); + if (ted->active_node) node_tab_next(ted, ted->active_node, argument); break; case CMD_TAB_PREV: - node_tab_prev(ted, ted->active_node, argument); + if (ted->active_node) node_tab_prev(ted, ted->active_node, argument); break; case CMD_ESCAPE: diff --git a/command.h b/command.h index 04a24f8..9c2fba3 100644 --- a/command.h +++ b/command.h @@ -47,6 +47,7 @@ ENUM_U16 { CMD_COPY, CMD_CUT, CMD_PASTE, + CMD_QUIT, CMD_TAB_CLOSE, CMD_TAB_NEXT, @@ -100,6 +101,7 @@ static CommandName const command_names[CMD_COUNT] = { {"new", CMD_NEW}, {"save", CMD_SAVE}, {"save-as", CMD_SAVE_AS}, + {"quit", CMD_QUIT}, {"undo", CMD_UNDO}, {"redo", CMD_REDO}, {"copy", CMD_COPY}, diff --git a/main.c b/main.c index 7cb7f06..c7e12f7 100644 --- a/main.c +++ b/main.c @@ -1,6 +1,8 @@ // @TODO: // - when closing tabs/window, warn on unsaved changes -// - try opening a file you don't have read permission for +// - try opening a file you don't have read permission for -- check for memory leaks! + +// - show something informative when there's no nodes open (i.e. ted->active_node == NULL). // - split // - Windows installation #include "base.h" @@ -42,7 +44,7 @@ no_warn_end #include "string32.c" #include "arr.c" #include "buffer.c" -#include "ted-base.c" +#include "ted.c" #include "ui.c" #include "node.c" #include "command.c" @@ -271,8 +273,7 @@ int main(int argc, char **argv) { Uint32 time_at_last_frame = SDL_GetTicks(); - bool quit = false; - while (!quit) { + while (!ted->quit) { #if DEBUG //printf("\033[H\033[2J"); #endif @@ -312,7 +313,7 @@ int main(int argc, char **argv) { | (u32)alt_down << KEY_MODIFIER_ALT_BIT; switch (event.type) { case SDL_QUIT: - quit = true; + command_execute(ted, CMD_QUIT, 1); break; case SDL_MOUSEWHEEL: { // scroll with mouse wheel @@ -476,7 +477,7 @@ int main(int argc, char **argv) { Font *font = ted->font; { - float x1 = 50, y1 = 50, x2 = window_width-50, y2 = window_height-50; + float x1 = 25, y1 = 25, x2 = window_width-25, y2 = window_height-25; Node *node = ted->root; node_frame(ted, node, rect4(x1, y1, x2, y2)); } diff --git a/math.c b/math.c index 71afabc..130aff7 100644 --- a/math.c +++ b/math.c @@ -46,12 +46,31 @@ static int clampi(int x, int a, int b) { return x; } +static i16 clamp_i16(i16 x, i16 a, i16 b) { + if (x < a) return a; + if (x > b) return b; + return x; +} + +static u16 clamp_u16(u16 x, u16 a, u16 b) { + if (x < a) return a; + if (x > b) return b; + return x; +} + static i32 clamp_i32(i32 x, i32 a, i32 b) { if (x < a) return a; if (x > b) return b; return x; } +static u32 clamp_u32(u32 x, u32 a, u32 b) { + if (x < a) return a; + if (x > b) return b; + return x; +} + + // remap x from the interval [from_a, from_b] to the interval [to_a, to_b], NOT clamping if x is outside the "from" interval. static float remapf(float x, float from_a, float from_b, float to_a, float to_b) { float pos = (x - from_a) / (from_b - from_a); diff --git a/node.c b/node.c index 6681a84..f927f70 100644 --- a/node.c +++ b/node.c @@ -20,6 +20,55 @@ static void node_tab_prev(Ted *ted, Node *node, i64 n) { node_tab_next(ted, node, -n); } +static void node_free(Node *node) { + arr_free(node->tabs); + memset(node, 0, sizeof *node); +} + +static void node_close(Ted *ted, Node *node) { + // @TODO(split) + + // delete all associated buffers + arr_foreach_ptr(node->tabs, u16, tab) { + u16 buffer_index = *tab; + ted_delete_buffer(ted, buffer_index); + } + + node_free(node); + + u16 node_index = (u16)(node - ted->nodes); + assert(node_index < TED_MAX_NODES); + ted->nodes_used[node_index] = false; + if (ted->active_node == node) { + ted->active_node = NULL; + } +} + +// close tab, WITHOUT checking for unsaved changes! +static void node_tab_close(Ted *ted, Node *node, u16 index) { + u16 ntabs = (u16)arr_len(node->tabs); + assert(index < ntabs); + if (ntabs == 1) { + // only 1 tab left, just close the node + node_close(ted, node); + } else { + u16 buffer_index = node->tabs[index]; + // remove tab from array + memmove(&node->tabs[index], &node->tabs[index + 1], (ntabs - (index + 1)) * sizeof *node->tabs); + arr_remove_last(node->tabs); + ted_delete_buffer(ted, buffer_index); + + ntabs = (u16)arr_len(node->tabs); // update ntabs + assert(ntabs); + // make sure active tab is valid + node->active_tab = clamp_u16(node->active_tab, 0, ntabs - 1); + if (ted->active_node == node) { + // fix active buffer if necessary + ted->active_buffer = &ted->buffers[node->tabs[node->active_tab]]; + } + } +} + static void node_frame(Ted *ted, Node *node, Rect r) { if (node->tabs) { bool is_active = node == ted->active_node; @@ -75,7 +124,9 @@ static void node_frame(Ted *ted, Node *node, Rect r) { } TextBuffer *buffer = &ted->buffers[buffer_index]; + assert(ted->buffers_used[buffer_index]); Rect buffer_rect = rect_translate(r, V2(0, tab_bar_height)); + buffer_rect.size.y -= tab_bar_height; buffer_render(buffer, buffer_rect); } else { diff --git a/ted-base.c b/ted-base.c deleted file mode 100644 index 8b0ab07..0000000 --- a/ted-base.c +++ /dev/null @@ -1,164 +0,0 @@ -// this is a macro so we get -Wformat warnings -#define ted_seterr(buffer, ...) \ - snprintf(ted->error, sizeof ted->error - 1, __VA_ARGS__) - -void ted_seterr_to_buferr(Ted *ted, TextBuffer *buffer) { - size_t size = sizeof ted->error; - if (sizeof buffer->error < size) size = sizeof buffer->error; - memcpy(ted->error, buffer->error, size); -} - -bool ted_haserr(Ted *ted) { - return ted->error[0] != '\0'; -} - -char const *ted_geterr(Ted *ted) { - return ted->error; -} - -void ted_clearerr(Ted *ted) { - ted->error[0] = '\0'; -} - -static void ted_out_of_mem(Ted *ted) { - ted_seterr(ted, "Out of memory."); -} - -static void *ted_malloc(Ted *ted, size_t size) { - void *ret = malloc(size); - if (!ret) ted_out_of_mem(ted); - return ret; -} - -static void *ted_calloc(Ted *ted, size_t n, size_t size) { - void *ret = calloc(n, size); - if (!ret) ted_out_of_mem(ted); - return ret; -} - -static void *ted_realloc(Ted *ted, void *p, size_t new_size) { - void *ret = realloc(p, new_size); - if (!ret) ted_out_of_mem(ted); - return ret; -} - -// Check the various places a file could be, and return the full path. -static Status ted_get_file(Ted const *ted, char const *name, char *out, size_t outsz) { - if (ted->search_cwd && fs_file_exists(name)) { - // check in current working directory - str_cpy(out, outsz, name); - return true; - } - if (*ted->local_data_dir) { - str_printf(out, outsz, "%s" PATH_SEPARATOR_STR "%s", ted->local_data_dir, name); - if (fs_file_exists(out)) - return true; - } - if (*ted->global_data_dir) { - str_printf(out, outsz, "%s" PATH_SEPARATOR_STR "%s", ted->global_data_dir, name); - if (fs_file_exists(out)) - return true; - } - return false; -} - -// Loads font from filename into *out, freeing any font that was previously there. -// *out is left unchanged on failure. -static void ted_load_font(Ted *ted, char const *filename, Font **out) { - char font_filename[TED_PATH_MAX]; - if (ted_get_file(ted, filename, font_filename, sizeof font_filename)) { - Font *font = text_font_load(font_filename, ted->settings.text_size); - if (font) { - if (*out) { - text_font_free(*out); - } - *out = font; - } else { - ted_seterr(ted, "Couldn't load font: %s", text_get_err()); - text_clear_err(); - } - } else { - ted_seterr(ted, "Couldn't find font file. There is probably a problem with your ted installation."); - } - -} - -// Load all the fonts ted will use. -static void ted_load_fonts(Ted *ted) { - ted_load_font(ted, "assets/font.ttf", &ted->font); - ted_load_font(ted, "assets/font-bold.ttf", &ted->font_bold); -} - -// returns the index of an available buffer, or -1 if none are available -static i32 ted_new_buffer(Ted *ted) { - bool *buffers_used = ted->buffers_used; - for (i32 i = 0; i < TED_MAX_BUFFERS; ++i) { - if (!buffers_used[i]) { - buffers_used[i] = true; - buffer_create(&ted->buffers[i], ted); - return i; - } - } - return -1; -} - -// returns the index of an available node, or -1 if none are available -static i32 ted_new_node(Ted *ted) { - bool *nodes_used = ted->nodes_used; - for (i32 i = 0; i < TED_MAX_NODES; ++i) { - if (!nodes_used[i]) { - nodes_used[i] = true; - return i; - } - } - ted_seterr(ted, "Too many buffers open!"); - return -1; - -} - -static void node_free(Node *node) { - arr_free(node->tabs); -} - -// returns true on success -static bool ted_open_file(Ted *ted, char const *filename) { - i32 new_buffer_index = ted_new_buffer(ted); - if (new_buffer_index >= 0) { - Node *node = ted->active_node; - if (arr_len(node->tabs) < TED_MAX_TABS) { - arr_add(node->tabs, (u16)new_buffer_index); - TextBuffer *new_buffer = &ted->buffers[new_buffer_index]; - if (node->tabs && buffer_load_file(new_buffer, filename)) { - ted->active_buffer = new_buffer; - node->active_tab = (u16)(arr_len(node->tabs) - 1); - return true; - } - } else { - ted_seterr(ted, "Too many tabs."); - } - } - return false; -} - -static void ted_new_file(Ted *ted) { - i32 new_buffer_index = ted_new_buffer(ted); - if (new_buffer_index >= 0) { - Node *node = ted->active_node; - if (arr_len(node->tabs) < TED_MAX_TABS) { - arr_add(node->tabs, (u16)new_buffer_index); - TextBuffer *new_buffer = &ted->buffers[new_buffer_index]; - if (node->tabs) { - buffer_new_file(new_buffer, TED_UNTITLED); - if (!buffer_haserr(new_buffer)) { - ted->active_buffer = new_buffer; - node->active_tab = (u16)(arr_len(node->tabs) - 1); - } - } - } else { - ted_seterr(ted, "Too many tabs."); - } - } -} - -static void menu_open(Ted *ted, Menu menu); -static void menu_escape(Ted *ted); diff --git a/ted.c b/ted.c new file mode 100644 index 0000000..bc34123 --- /dev/null +++ b/ted.c @@ -0,0 +1,201 @@ +// this is a macro so we get -Wformat warnings +#define ted_seterr(buffer, ...) \ + snprintf(ted->error, sizeof ted->error - 1, __VA_ARGS__) + +void ted_seterr_to_buferr(Ted *ted, TextBuffer *buffer) { + size_t size = sizeof ted->error; + if (sizeof buffer->error < size) size = sizeof buffer->error; + memcpy(ted->error, buffer->error, size); +} + +bool ted_haserr(Ted *ted) { + return ted->error[0] != '\0'; +} + +char const *ted_geterr(Ted *ted) { + return ted->error; +} + +void ted_clearerr(Ted *ted) { + ted->error[0] = '\0'; +} + +static void ted_out_of_mem(Ted *ted) { + ted_seterr(ted, "Out of memory."); +} + +static void *ted_malloc(Ted *ted, size_t size) { + void *ret = malloc(size); + if (!ret) ted_out_of_mem(ted); + return ret; +} + +static void *ted_calloc(Ted *ted, size_t n, size_t size) { + void *ret = calloc(n, size); + if (!ret) ted_out_of_mem(ted); + return ret; +} + +static void *ted_realloc(Ted *ted, void *p, size_t new_size) { + void *ret = realloc(p, new_size); + if (!ret) ted_out_of_mem(ted); + return ret; +} + +// Check the various places a file could be, and return the full path. +static Status ted_get_file(Ted const *ted, char const *name, char *out, size_t outsz) { + if (ted->search_cwd && fs_file_exists(name)) { + // check in current working directory + str_cpy(out, outsz, name); + return true; + } + if (*ted->local_data_dir) { + str_printf(out, outsz, "%s" PATH_SEPARATOR_STR "%s", ted->local_data_dir, name); + if (fs_file_exists(out)) + return true; + } + if (*ted->global_data_dir) { + str_printf(out, outsz, "%s" PATH_SEPARATOR_STR "%s", ted->global_data_dir, name); + if (fs_file_exists(out)) + return true; + } + return false; +} + +// Loads font from filename into *out, freeing any font that was previously there. +// *out is left unchanged on failure. +static void ted_load_font(Ted *ted, char const *filename, Font **out) { + char font_filename[TED_PATH_MAX]; + if (ted_get_file(ted, filename, font_filename, sizeof font_filename)) { + Font *font = text_font_load(font_filename, ted->settings.text_size); + if (font) { + if (*out) { + text_font_free(*out); + } + *out = font; + } else { + ted_seterr(ted, "Couldn't load font: %s", text_get_err()); + text_clear_err(); + } + } else { + ted_seterr(ted, "Couldn't find font file. There is probably a problem with your ted installation."); + } + +} + +// Load all the fonts ted will use. +static void ted_load_fonts(Ted *ted) { + ted_load_font(ted, "assets/font.ttf", &ted->font); + ted_load_font(ted, "assets/font-bold.ttf", &ted->font_bold); +} + +// returns the index of an available buffer, or -1 if none are available +static i32 ted_new_buffer(Ted *ted) { + bool *buffers_used = ted->buffers_used; + for (i32 i = 0; i < TED_MAX_BUFFERS; ++i) { + if (!buffers_used[i]) { + buffers_used[i] = true; + buffer_create(&ted->buffers[i], ted); + return i; + } + } + return -1; +} + +// Opposite of ted_new_buffer +// Make sure you set active_buffer to something else if you delete it! +static void ted_delete_buffer(Ted *ted, u16 index) { + TextBuffer *buffer = &ted->buffers[index]; + if (buffer == ted->active_buffer) + ted->active_buffer = NULL; // make sure we don't set the active buffer to something invalid + buffer_free(buffer); + ted->buffers_used[index] = false; +} + +// Returns the index of an available node, or -1 if none are available +static i32 ted_new_node(Ted *ted) { + bool *nodes_used = ted->nodes_used; + for (i32 i = 0; i < TED_MAX_NODES; ++i) { + if (!nodes_used[i]) { + nodes_used[i] = true; + return i; + } + } + ted_seterr(ted, "Too many nodes."); + return -1; + +} + +static void node_tab_close(Ted *ted, Node *node, u16 index); + +// Open a new buffer. Fills out *tab to the index of the tab used, and *buffer_idx to the index of the buffer. +// Returns true on success. +static Status ted_open_buffer(Ted *ted, u16 *buffer_idx, u16 *tab) { + i32 new_buffer_index = ted_new_buffer(ted); + if (new_buffer_index >= 0) { + Node *node = ted->active_node; + if (!node) { + // no active node; let's create one! + i32 node_idx = ted_new_node(ted); + if (node_idx >= 0) { + node = &ted->nodes[node_idx]; + ted->active_node = node; + } else { + ted_delete_buffer(ted, (u16)new_buffer_index); + return false; + } + } + if (arr_len(node->tabs) < TED_MAX_TABS) { + arr_add(node->tabs, (u16)new_buffer_index); + TextBuffer *new_buffer = &ted->buffers[new_buffer_index]; + ted->active_buffer = new_buffer; + node->active_tab = (u16)(arr_len(node->tabs) - 1); + *buffer_idx = (u16)new_buffer_index; + *tab = node->active_tab; + return true; + } else { + ted_seterr(ted, "Too many tabs."); + ted_delete_buffer(ted, (u16)new_buffer_index); + return false; + } + } else { + return false; + } +} + +// Returns true on success +static bool ted_open_file(Ted *ted, char const *filename) { + u16 buffer_idx, tab_idx; + if (ted_open_buffer(ted, &buffer_idx, &tab_idx)) { + TextBuffer *buffer = &ted->buffers[buffer_idx]; + if (buffer_load_file(buffer, filename)) { + return true; + } else { + node_tab_close(ted, ted->active_node, tab_idx); + ted_delete_buffer(ted, (u16)buffer_idx); + return false; + } + } else { + return false; + } +} + +static bool ted_new_file(Ted *ted) { + u16 buffer_idx, tab_idx; + if (ted_open_buffer(ted, &buffer_idx, &tab_idx)) { + TextBuffer *buffer = &ted->buffers[buffer_idx]; + buffer_new_file(buffer, TED_UNTITLED); + if (!buffer_haserr(buffer)) { + return true; + } else { + node_tab_close(ted, ted->active_node, tab_idx); + ted_delete_buffer(ted, (u16)buffer_idx); + return false; + } + } else { + return false; + } +} + +static void menu_open(Ted *ted, Menu menu); +static void menu_escape(Ted *ted); diff --git a/ted.cfg b/ted.cfg index bdc8c59..e7af61a 100644 --- a/ted.cfg +++ b/ted.cfg @@ -67,6 +67,7 @@ Ctrl+o = :open Ctrl+n = :new Ctrl+s = :save Ctrl+Shift+s = :save-as +Ctrl+q = :quit Ctrl+z = :undo Ctrl+Shift+z = :redo Ctrl+c = :copy diff --git a/ted.h b/ted.h index d589365..6271a6d 100644 --- a/ted.h +++ b/ted.h @@ -138,6 +138,7 @@ typedef struct Ted { double error_time; // time error box was opened (in seconds -- see time_get_seconds) KeyAction key_actions[KEY_COMBO_COUNT]; bool search_cwd; // should the working directory be searched for files? set to true if the executable isn't "installed" + bool quit; // if set to true, the window will close next frame. NOTE: this doesn't check for unsaved changes!! char warn_overwrite[TED_PATH_MAX]; // file name user is trying to overwrite char local_data_dir[TED_PATH_MAX]; char global_data_dir[TED_PATH_MAX]; -- cgit v1.2.3