diff options
author | pommicket <pommicket@gmail.com> | 2023-08-09 12:24:40 -0300 |
---|---|---|
committer | pommicket <pommicket@gmail.com> | 2023-08-09 12:25:33 -0300 |
commit | 310faf5d70b8a65c83c013a31ab87d9d3f301767 (patch) | |
tree | 9e48b8afad2651de90e6b4e74ac9b96d94968fbd | |
parent | 17e65f8dd57762a2e79aeb79a5c0fb69849c07f9 (diff) |
new node/buffer system, needs more testing
-rw-r--r-- | buffer.c | 6 | ||||
-rw-r--r-- | command.c | 19 | ||||
-rw-r--r-- | ds.h | 14 | ||||
-rw-r--r-- | ide-rename-symbol.c | 17 | ||||
-rw-r--r-- | main.c | 43 | ||||
-rw-r--r-- | node.c | 179 | ||||
-rw-r--r-- | session.c | 130 | ||||
-rw-r--r-- | ted-internal.h | 32 | ||||
-rw-r--r-- | ted.c | 273 | ||||
-rw-r--r-- | ted.h | 25 | ||||
-rw-r--r-- | unicode.h | 11 |
11 files changed, 369 insertions, 380 deletions
@@ -804,6 +804,8 @@ static void buffer_line_free(Line *line) { } void buffer_free(TextBuffer *buffer) { + if (!buffer) return; + if (!buffer->ted->quit) { // don't send didClose on quit (calling buffer_lsp would actually create a LSP if this is called after destroying all the LSPs which isnt good) LSP *lsp = buffer_lsp(buffer); @@ -2647,6 +2649,10 @@ 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, const char *path) { + if (!unicode_is_valid_utf8(path)) { + buffer_error(buffer, "Path is not valid UTF8."); + return false; + } if (!path || !path_is_absolute(path)) { buffer_error(buffer, "Loaded path '%s' is not an absolute path.", path); return false; @@ -443,17 +443,14 @@ void command_execute_ex(Ted *ted, Command c, const CommandArgument *full_argumen ted->quit = true; } else { *ted->warn_unsaved_names = 0; - bool *buffers_used = ted->buffers_used; bool first = true; - for (u16 i = 0; i < TED_MAX_BUFFERS; ++i) { - if (buffers_used[i]) { - buffer = &ted->buffers[i]; - if (buffer_unsaved_changes(buffer)) { - const char *path = buffer_display_filename(buffer); - strbuf_catf(ted->warn_unsaved_names, "%s%s", first ? "" : ", ", path); - first = false; - } + arr_foreach_ptr(ted->buffers, TextBufferPtr, pbuffer) { + buffer = *pbuffer; + if (buffer_unsaved_changes(buffer)) { + const char *path = buffer_display_filename(buffer); + strbuf_catf(ted->warn_unsaved_names, "%s%s", first ? "" : ", ", path); + first = false; } } @@ -569,7 +566,7 @@ void command_execute_ex(Ted *ted, Command c, const CommandArgument *full_argumen find_close(ted); } else if (node) { u16 tab_idx = node->active_tab; - buffer = &ted->buffers[node->tabs[tab_idx]]; + buffer = node->tabs[tab_idx]; // (an argument of 2 overrides the unsaved changes dialog) if (argument != 2 && buffer_unsaved_changes(buffer)) { // there are unsaved changes! @@ -581,7 +578,7 @@ void command_execute_ex(Ted *ted, Command c, const CommandArgument *full_argumen } } else if (ted->build_shown) { build_stop(ted); - } else if (ted->nodes_used[0]) { + } else if (arr_len(ted->nodes)) { // there are nodes open, but no active node. // do nothing. } else { @@ -208,6 +208,18 @@ static void *arr_remove_(void *arr, size_t member_size, size_t index) { } } +static i32 arr_index_of_(void *arr, size_t member_size, const void *item) { + if (!arr) return -1; + + ArrHeader *hdr = arr_hdr_(arr); + for (size_t i = 0; i < hdr->len; ++i) { + if (memcmp((const char *)arr + i * member_size, item, member_size) == 0) + return (i32)i; + } + + return -1; +} + static void *arr_remove_multiple_(void *arr, size_t member_size, size_t index, size_t count) { ArrHeader *hdr = arr_hdr_(arr); assert(index < hdr->len); @@ -269,6 +281,8 @@ static void *arr_copy_(const void *arr, size_t member_size) { #define arr_qsort(a, cmp) qsort((a), arr_len(a), sizeof *(a), (cmp)) #define arr_remove_last(a) do { assert(a); if (--arr_hdr_(a)->len == 0) arr_free(a); } while (0) #define arr_remove(a, i) (void)((a) = arr_remove_((a), sizeof *(a), (i))) +#define arr_remove_item(a, item) do { for (u32 _i = 0; _i < arr_len((a)); ++_i) if ((a)[_i] == item) { arr_remove((a), _i); break; } } while (0); +#define arr_index_of(a, item) (sizeof((a)[0] == (item)), arr_index_of_((a), sizeof *(a), &(item))) #define arr_remove_multiple(a, i, n) (void)((a) = arr_remove_multiple_((a), sizeof *(a), (i), (n))) #define arr_insert(a, i, x) do { u32 _index = (i); (a) = arr_cast_typeof(a) arr_grow1_((a), sizeof *(a)); \ if (a) { memmove((a) + _index + 1, (a) + _index, (arr_len(a) - _index) * sizeof *(a));\ diff --git a/ide-rename-symbol.c b/ide-rename-symbol.c index 97c16a5..0ff69e8 100644 --- a/ide-rename-symbol.c +++ b/ide-rename-symbol.c @@ -207,18 +207,17 @@ void rename_symbol_process_lsp_response(Ted *ted, const LSPResponse *response) { } } done: - - // end all edit chains in all buffers - // they're almost definitely all created by us - for (u16 i = 0; i < TED_MAX_BUFFERS; ++i) { - if (ted->buffers_used[i]) { - TextBuffer *buffer = &ted->buffers[i]; - buffer_end_edit_chain(buffer); + + { + // end all edit chains in all buffers + // they're almost definitely all created by us + arr_foreach_ptr(ted->buffers, TextBufferPtr, pbuffer) { + buffer_end_edit_chain(*pbuffer); } + + ted_save_all(ted); } - ted_save_all(ted); - cleanup: rename_symbol_clear(ted); if (menu_is_open(ted, MENU_RENAME_SYMBOL)) @@ -1,5 +1,6 @@ /* TODO: +- set limit for # of nodes/buffers/tabs - public Node API - public Selector/FileSelector API @@ -683,15 +684,13 @@ int main(int argc, char **argv) { // could switch to a different buffer. // line buffer click handling, IS done in buffer_render (yes this is less than ideal) if (!menu_is_any_open(ted)) { - for (u32 i = 0; i < TED_MAX_NODES; ++i) { - if (ted->nodes_used[i]) { - Node *node = &ted->nodes[i]; - if (node->tabs) { - buffer = &ted->buffers[node->tabs[node->active_tab]]; - if (buffer_handle_click(ted, buffer, pos, times)) { - add = false; - break; - } + arr_foreach_ptr(ted->nodes, NodePtr, pnode) { + Node *node = *pnode; + if (node->tabs) { + buffer = node->tabs[node->active_tab]; + if (buffer_handle_click(ted, buffer, pos, times)) { + add = false; + break; } } } @@ -966,7 +965,6 @@ int main(int argc, char **argv) { { const float padding = ted_active_settings(ted)->padding; float x1 = padding, y = window_height-padding, x2 = window_width-padding; - Node *node = &ted->nodes[0]; if (ted->find) { float y2 = y; y -= find_menu_height(ted); @@ -1003,7 +1001,8 @@ int main(int argc, char **argv) { y -= padding; } - if (ted->nodes_used[0]) { + if (arr_len(ted->nodes)) { + Node *node = ted->nodes[0]; float y1 = padding; node_frame(ted, node, rect4(x1, y1, x2, y)); autocomplete_frame(ted); @@ -1038,8 +1037,8 @@ int main(int argc, char **argv) { if (text_has_err()) { ted_error(ted, "Couldn't render text: %s", text_get_err()); } - for (u16 i = 0; i < TED_MAX_BUFFERS; ++i) { - TextBuffer *buffer = &ted->buffers[i]; + arr_foreach_ptr(ted->buffers, TextBufferPtr, pbuffer) { + TextBuffer *buffer = *pbuffer; if (buffer_has_error(buffer)) { ted_error_from_buffer(ted, buffer); buffer_clear_error(buffer); @@ -1120,9 +1119,8 @@ int main(int argc, char **argv) { ted_check_for_node_problems(ted); #if !NDEBUG - for (u16 i = 0; i < TED_MAX_BUFFERS; ++i) - if (ted->buffers_used[i]) - buffer_check_valid(&ted->buffers[i]); + for (u16 i = 0; i < arr_len(ted->buffers); ++i) + buffer_check_valid(ted->buffers[i]); buffer_check_valid(&ted->line_buffer); #endif @@ -1224,12 +1222,13 @@ int main(int argc, char **argv) { SDL_GL_DeleteContext(glctx); SDL_DestroyWindow(window); SDL_Quit(); - for (u16 i = 0; i < TED_MAX_BUFFERS; ++i) - if (ted->buffers_used[i]) - buffer_free(&ted->buffers[i]); - for (u16 i = 0; i < TED_MAX_NODES; ++i) - if (ted->nodes_used[i]) - node_free(&ted->nodes[i]); + for (u16 i = 0; i < arr_len(ted->buffers); ++i) + buffer_free(ted->buffers[i]); + arr_clear(ted->buffers); + arr_foreach_ptr(ted->nodes, NodePtr, pnode) { + node_free(*pnode); + } + arr_clear(ted->nodes); buffer_free(&ted->line_buffer); buffer_free(&ted->find_buffer); buffer_free(&ted->replace_buffer); @@ -10,8 +10,8 @@ static void node_switch_to_tab(Ted *ted, Node *node, u16 new_tab_index) { if (node == ted->active_node) { // switch active buffer assert(node->tabs); - u16 buffer_idx = node->tabs[new_tab_index]; - ted_switch_to_buffer(ted, &ted->buffers[buffer_idx]); + TextBuffer *buffer = node->tabs[new_tab_index]; + ted_switch_to_buffer(ted, buffer); } } @@ -41,130 +41,124 @@ void node_tabs_swap(Node *node, i32 tab1i, i32 tab2i) { u16 tab1 = (u16)tab1i, tab2 = (u16)tab2i; if (node->active_tab == tab1) node->active_tab = tab2; else if (node->active_tab == tab2) node->active_tab = tab1; - u16 tmp = node->tabs[tab1]; + TextBuffer *temp = node->tabs[tab1]; node->tabs[tab1] = node->tabs[tab2]; - node->tabs[tab2] = tmp; + node->tabs[tab2] = temp; } void node_free(Node *node) { arr_free(node->tabs); memset(node, 0, sizeof *node); + free(node); } -i32 node_parent(Ted *ted, i32 node_idx) { - const bool *nodes_used = ted->nodes_used; - if (node_idx < 0 || node_idx >= TED_MAX_NODES || !nodes_used[node_idx]) - return -1; - for (u16 i = 0; i < TED_MAX_NODES; ++i) { - if (nodes_used[i]) { - Node *node = &ted->nodes[i]; - if (!node->tabs) { - if (node->split_a == node_idx || node->split_b == node_idx) - return i; - } +Node *node_parent(Ted *ted, Node *child) { + if (!child) + return NULL; + arr_foreach_ptr(ted->nodes, NodePtr, pnode) { + Node *node = *pnode; + if (!node->tabs) { + if (node->split_a == child || node->split_b == child) + return node; } } - return -1; + return NULL; } // the root has depth 0, and a child node has 1 more than its parent's depth. -static u8 node_depth(Ted *ted, i32 node_idx) { +static u8 node_depth(Ted *ted, Node *node) { u8 depth = 0; - while (node_idx != -1) { - node_idx = node_parent(ted, node_idx); + while (node) { + node = node_parent(ted, node); ++depth; } return depth; } void node_join(Ted *ted, Node *node) { - i32 parent_idx = node_parent(ted, (u16)(node - ted->nodes)); - if (parent_idx >= 0) { - Node *parent = &ted->nodes[parent_idx]; - Node *a = &ted->nodes[parent->split_a]; - Node *b = &ted->nodes[parent->split_b]; + Node *parent = node_parent(ted, node); + if (parent) { + Node *a = parent->split_a; + Node *b = parent->split_b; if (a->tabs && b->tabs) { if (ted->active_node == a || ted->active_node == b) { ted->active_node = parent; } - arr_foreach_ptr(a->tabs, u16, tab) { + arr_foreach_ptr(a->tabs, TextBufferPtr, tab) { arr_add(parent->tabs, *tab); } - arr_foreach_ptr(b->tabs, u16, tab) { + arr_foreach_ptr(b->tabs, TextBufferPtr, tab) { arr_add(parent->tabs, *tab); } if (!parent->tabs) { ted_out_of_mem(ted); + return; + } + + parent->split_a = NULL; + parent->split_b = NULL; + if (node == a) { + parent->active_tab = a->active_tab; } else { - if (node == a) { - parent->active_tab = a->active_tab; - } else { - parent->active_tab = (u16)arr_len(a->tabs) + b->active_tab; - } - node_free(a); - node_free(b); - ted->nodes_used[parent->split_a] = false; - ted->nodes_used[parent->split_b] = false; - // this isn't really needed since parent->tabs is not NULL anymore. - parent->split_a = 0; - parent->split_b = 0; + parent->active_tab = (u16)arr_len(a->tabs) + b->active_tab; } + node_free(a); + node_free(b); + arr_remove_item(ted->nodes, a); + arr_remove_item(ted->nodes, b); + } } } -void node_close(Ted *ted, i32 node_idx) { +void node_close(Ted *ted, Node *node) { ted->dragging_tab_node = NULL; ted->resizing_split = NULL; - if (node_idx < 0 || node_idx >= TED_MAX_NODES) { + if (!node) { return; } - if (!ted->nodes_used[node_idx]) { - return; - } - i32 parent_idx = node_parent(ted, node_idx); - ted->nodes_used[node_idx] = false; - - Node *node = &ted->nodes[node_idx]; + + + Node *parent = node_parent(ted, node); bool was_active = ted->active_node == node; // delete all associated buffers - arr_foreach_ptr(node->tabs, u16, tab) { - u16 buffer_index = *tab; - ted_delete_buffer(ted, buffer_index); + arr_foreach_ptr(node->tabs, TextBufferPtr, tab) { + TextBuffer *buffer = *tab; + ted_delete_buffer(ted, buffer); } node_free(node); - if (parent_idx < 0) { + if (!parent) { // no parent; this must be the root node ted->active_node = NULL; } else { // turn parent from split node into tab node - Node *parent = &ted->nodes[parent_idx]; if (parent->tabs) { assert(0); // this node's parent should be a split node return; } - u16 other_side; - if (node_idx == parent->split_a) { + Node *other_side; + if (node == parent->split_a) { other_side = parent->split_b; } else { - assert(node_idx == parent->split_b); + assert(node == parent->split_b); other_side = parent->split_a; } // replace parent with other side of split - *parent = ted->nodes[other_side]; - - ted->nodes_used[other_side] = false; + *parent = *other_side; + arr_remove_item(ted->nodes, other_side); if (was_active) { Node *new_active_node = parent; // make sure we don't set the active node to a split while (!new_active_node->tabs) - new_active_node = &ted->nodes[new_active_node->split_a]; + new_active_node = new_active_node->split_a; ted_node_switch(ted, new_active_node); } } + + arr_remove_item(ted->nodes, node); } @@ -179,14 +173,14 @@ bool node_tab_close(Ted *ted, Node *node, i32 index) { if (ntabs == 1) { // only 1 tab left, just close the node - node_close(ted, (u16)(node - ted->nodes)); + node_close(ted, node); return false; } else { bool was_active = ted->active_node == node; // ted->active_node will be set to NULL when the active buffer is deleted. - u16 buffer_index = node->tabs[index]; + TextBuffer *buffer = node->tabs[index]; // remove tab from array arr_remove(node->tabs, (size_t)index); - ted_delete_buffer(ted, buffer_index); + ted_delete_buffer(ted, buffer); ntabs = (u16)arr_len(node->tabs); // update ntabs assert(ntabs); @@ -196,7 +190,7 @@ bool node_tab_close(Ted *ted, Node *node, i32 index) { node->active_tab = clamp_u16(node->active_tab, 0, ntabs - 1); if (was_active) { // fix active buffer if necessary - ted_switch_to_buffer(ted, &ted->buffers[node->tabs[node->active_tab]]); + ted_switch_to_buffer(ted, node->tabs[node->active_tab]); } return true; } @@ -240,7 +234,7 @@ void node_frame(Ted *ted, Node *node, Rect r) { if (tab_index <= arr_len(node->tabs)) { Node *drag_node = ted->dragging_tab_node; u16 drag_index = ted->dragging_tab_idx; - u16 tab = drag_node->tabs[drag_index]; + TextBuffer *tab = drag_node->tabs[drag_index]; // remove the old tab arr_remove(drag_node->tabs, drag_index); @@ -253,7 +247,7 @@ void node_frame(Ted *ted, Node *node, Rect r) { arr_insert(node->tabs, tab_index, tab); if (arr_len(drag_node->tabs) == 0) { // removed the last tab from a node; close it - node_close(ted, (u16)(drag_node - ted->nodes)); + node_close(ted, drag_node); } else { // make sure active tab is valid drag_node->active_tab = clamp_u16(drag_node->active_tab, 0, (u16)arr_len(drag_node->tabs) - 1); @@ -261,7 +255,7 @@ void node_frame(Ted *ted, Node *node, Rect r) { ted->dragging_tab_node = NULL; // stop dragging // switch to this buffer - ted_switch_to_buffer(ted, &ted->buffers[tab]); + ted_switch_to_buffer(ted, tab); } } } @@ -271,8 +265,7 @@ void node_frame(Ted *ted, Node *node, Rect r) { if (rect_contains_point(tab_bar_rect, click->pos)) { u16 tab_index = (u16)((click->pos.x - r.pos.x) / tab_width); if (tab_index < arr_len(node->tabs)) { - u16 buffer_idx = node->tabs[tab_index]; - TextBuffer *buffer = &ted->buffers[buffer_idx]; + TextBuffer *buffer = node->tabs[tab_index]; // close that tab if (buffer_unsaved_changes(buffer)) { // make sure unsaved changes dialog is opened @@ -292,7 +285,7 @@ void node_frame(Ted *ted, Node *node, Rect r) { TextRenderState text_state = text_render_state_default; for (u16 i = 0; i < ntabs; ++i) { - TextBuffer *buffer = &ted->buffers[node->tabs[i]]; + TextBuffer *buffer = node->tabs[i]; char tab_title[256]; const char *filename = buffer_display_filename(buffer); Rect tab_rect = rect_xywh(r.pos.x + tab_width * i, r.pos.y, tab_width, tab_bar_height); @@ -340,9 +333,7 @@ void node_frame(Ted *ted, Node *node, Rect r) { text_render(font); } - u16 buffer_index = node->tabs[node->active_tab]; - TextBuffer *buffer = &ted->buffers[buffer_index]; - assert(ted->buffers_used[buffer_index]); + TextBuffer *buffer = node->tabs[node->active_tab]; Rect buffer_rect = r; buffer_rect.pos.y += tab_bar_height; @@ -355,8 +346,7 @@ void node_frame(Ted *ted, Node *node, Rect r) { } else { float padding = settings->padding; // this node is a split - Node *a = &ted->nodes[node->split_a]; - Node *b = &ted->nodes[node->split_b]; + Node *a = node->split_a, *b = node->split_b; Rect r1 = r, r2 = r; SDL_Cursor *resize_cursor = node->split_vertical ? ted->cursor_resize_v : ted->cursor_resize_h; if (node == ted->resizing_split) { @@ -399,14 +389,12 @@ void node_frame(Ted *ted, Node *node, Rect r) { } void node_split(Ted *ted, Node *node, bool vertical) { - if (node_depth(ted, (u16)(node - ted->nodes)) >= 4) return; // prevent splitting too deep + if (node_depth(ted, node) >= 4) return; // prevent splitting too deep if (arr_len(node->tabs) > 1) { // need at least 2 tabs to split - i32 left_idx = ted_new_node(ted); - i32 right_idx = ted_new_node(ted); - if (left_idx >= 0 && right_idx >= 0) { - Node *left = &ted->nodes[left_idx]; - Node *right = &ted->nodes[right_idx]; + Node *left = ted_new_node(ted); + Node *right = ted_new_node(ted); + if (left && right) { u16 active_tab = node->active_tab; // put active tab on the right arr_add(right->tabs, node->tabs[active_tab]); @@ -418,38 +406,31 @@ void node_split(Ted *ted, Node *node, bool vertical) { } arr_clear(node->tabs); - node->split_a = (u16)left_idx; - node->split_b = (u16)right_idx; + node->split_a = left; + node->split_b = right; node->split_vertical = vertical; node->split_pos = 0.5f; if (node == ted->active_node) - ted_node_switch(ted, &ted->nodes[right_idx]); + ted_node_switch(ted, right); } } } void node_split_switch(Ted *ted) { - assert(ted->active_node); - u16 active_node_idx = (u16)(ted->active_node - ted->nodes); - i32 parent_idx = node_parent(ted, active_node_idx); - if (parent_idx < 0) return; - Node *parent = &ted->nodes[parent_idx]; - if (parent) { - if (parent->split_a == active_node_idx) { - ted_node_switch(ted, &ted->nodes[parent->split_b]); - } else { - ted_node_switch(ted, &ted->nodes[parent->split_a]); - } + Node *parent = node_parent(ted, ted->active_node); + if (!parent) return; + if (parent->split_a == ted->active_node) { + ted_node_switch(ted, parent->split_b); + } else { + ted_node_switch(ted, parent->split_a); } } void node_split_swap(Ted *ted) { assert(ted->active_node); - u16 active_node_idx = (u16)(ted->active_node - ted->nodes); - i32 parent_idx = node_parent(ted, active_node_idx); - if (parent_idx < 0) return; - Node *parent = &ted->nodes[parent_idx]; - u16 temp = parent->split_a; + Node *parent = node_parent(ted, ted->active_node); + if (!parent) return; + Node *temp = parent->split_a; parent->split_a = parent->split_b; parent->split_b = temp; } @@ -1,7 +1,7 @@ #include "ted-internal.h" #define SESSION_FILENAME "session.txt" -#define SESSION_VERSION "\x7fTED0002" +#define SESSION_VERSION "\x7fTED0003" static void write_u8(FILE *fp, u8 x) { putc(x, fp); @@ -156,39 +156,35 @@ static BufferPos buffer_pos_read(TextBuffer *buffer, FILE *fp) { } -static void session_write_node(Ted *ted, FILE *fp, u16 node_idx) { - Node *node = &ted->nodes[node_idx]; - write_u16(fp, node_idx); +static void session_write_node(Ted *ted, FILE *fp, Node *node) { bool is_split = !node->tabs; write_bool(fp, is_split); if (is_split) { write_float(fp, node->split_pos); write_bool(fp, node->split_vertical); - write_u16(fp, node->split_a); - write_u16(fp, node->split_b); + write_u16(fp, (u16)arr_index_of(ted->nodes, node->split_a)); + write_u16(fp, (u16)arr_index_of(ted->nodes, node->split_b)); } else { write_u16(fp, node->active_tab); // active tab write_u16(fp, (u16)arr_len(node->tabs)); // ntabs - arr_foreach_ptr(node->tabs, u16, tab) { - write_u16(fp, *tab); + arr_foreach_ptr(node->tabs, TextBufferPtr, pbuf) { + write_u16(fp, (u16)arr_index_of(ted->buffers, *pbuf)); } } } -static void session_read_node(Ted *ted, FILE *fp) { - u16 node_idx = read_u16(fp); - if (node_idx >= TED_MAX_NODES) { - debug_println("WARNING: Invalid node index (see %s:%d)!\n", __FILE__, __LINE__); - return; - } - ted->nodes_used[node_idx] = true; - Node *node = &ted->nodes[node_idx]; +static Status session_read_node(Ted *ted, FILE *fp) { + Node *node = ted_new_node(ted); bool is_split = read_bool(fp); if (is_split) { node->split_pos = clampf(read_float(fp), 0, 1); node->split_vertical = read_bool(fp); - node->split_a = clamp_u16(read_u16(fp), 0, TED_MAX_NODES); - node->split_b = clamp_u16(read_u16(fp), 0, TED_MAX_NODES); + u16 split_a_index = read_u16(fp), split_b_index = read_u16(fp); + if (split_a_index == split_b_index || split_a_index >= arr_len(ted->nodes) || split_b_index >= arr_len(ted->nodes)) { + return false; + } + node->split_a = ted->nodes[split_a_index]; + node->split_b = ted->nodes[split_b_index]; } else { node->active_tab = read_u16(fp); u16 ntabs = clamp_u16(read_u16(fp), 0, TED_MAX_TABS); @@ -196,15 +192,14 @@ static void session_read_node(Ted *ted, FILE *fp) { node->active_tab = 0; for (u16 i = 0; i < ntabs; ++i) { u16 buf_idx = read_u16(fp); - if (buf_idx >= TED_MAX_BUFFERS) continue; - arr_add(node->tabs, buf_idx); + if (buf_idx >= arr_len(ted->buffers)) return false; + arr_add(node->tabs, ted->buffers[buf_idx]); } } + return true; } -static void session_write_buffer(Ted *ted, FILE *fp, u16 buffer_idx) { - write_u16(fp, buffer_idx); - TextBuffer *buffer = &ted->buffers[buffer_idx]; +static void session_write_buffer(FILE *fp, TextBuffer *buffer) { // some info about the buffer that should be restored if (buffer_is_named_file(buffer)) write_cstr(fp, buffer->path); @@ -219,14 +214,8 @@ static void session_write_buffer(Ted *ted, FILE *fp, u16 buffer_idx) { buffer_pos_write(buffer->selection_pos, fp); } -static void session_read_buffer(Ted *ted, FILE *fp) { - u16 buffer_idx = read_u16(fp); - if (buffer_idx >= TED_MAX_BUFFERS) { - debug_println("WARNING: Invalid buffer index (see %s:%d)!\n", __FILE__, __LINE__); - return; - } - TextBuffer *buffer = &ted->buffers[buffer_idx]; - ted->buffers_used[buffer_idx] = true; +static bool session_read_buffer(Ted *ted, FILE *fp) { + TextBuffer *buffer = ted_new_buffer(ted); char filename[TED_PATH_MAX] = {0}; read_cstr(fp, filename, sizeof filename); buffer_create(buffer, ted); @@ -251,6 +240,7 @@ static void session_read_buffer(Ted *ted, FILE *fp) { buffer->selection = false; } } + return true; } static void session_write_file(Ted *ted, FILE *fp) { @@ -258,24 +248,19 @@ static void session_write_file(Ted *ted, FILE *fp) { write_cstr(fp, ted->cwd); - write_u16(fp, ted->active_node ? (u16)(ted->active_node - ted->nodes) : U16_MAX); // active node idx - write_u16(fp, ted->active_buffer ? (u16)(ted->active_buffer - ted->buffers) : U16_MAX); // active buffer idx + write_u16(fp, (u16)arr_index_of(ted->nodes, ted->active_node)); // active node idx + write_u16(fp, (u16)arr_index_of(ted->buffers, ted->active_buffer)); // active buffer idx - u16 nnodes = 0; - for (u16 i = 0; i < TED_MAX_NODES; ++i) - nnodes += ted->nodes_used[i]; - write_u16(fp, nnodes); - for (u16 i = 0; i < TED_MAX_NODES; ++i) - if (ted->nodes_used[i]) - session_write_node(ted, fp, i); - - u16 nbuffers = 0; - for (u16 i = 0; i < TED_MAX_BUFFERS; ++i) - nbuffers += ted->buffers_used[i]; - write_u16(fp, nbuffers); - for (u16 i = 0; i < TED_MAX_BUFFERS; ++i) - if (ted->buffers_used[i]) - session_write_buffer(ted, fp, i); + write_u16(fp, (u16)arr_len(ted->buffers)); + arr_foreach_ptr(ted->buffers, TextBufferPtr, pbuffer) { + TextBuffer *buffer = *pbuffer; + session_write_buffer(fp, buffer); + } + + write_u16(fp, (u16)arr_len(ted->nodes)); + arr_foreach_ptr(ted->nodes, NodePtr, pnode) { + session_write_node(ted, fp, *pnode); + } } static void session_read_file(Ted *ted, FILE *fp) { @@ -291,40 +276,51 @@ static void session_read_file(Ted *ted, FILE *fp) { u16 active_node_idx = read_u16(fp); u16 active_buffer_idx = read_u16(fp); - u16 nnodes = clamp_u16(read_u16(fp), 0, TED_MAX_NODES); - for (u16 i = 0; i < nnodes; ++i) { - session_read_node(ted, fp); - } - - u16 nbuffers = clamp_u16(read_u16(fp), 0, TED_MAX_BUFFERS); + u16 nbuffers = read_u16(fp); for (u16 i = 0; i < nbuffers; ++i) { - session_read_buffer(ted, fp); + if (!session_read_buffer(ted, fp)) { + arr_foreach_ptr(ted->buffers, TextBufferPtr, pbuffer) { + buffer_free(*pbuffer); + } + arr_clear(ted->buffers); + return; + } + } + + u16 nnodes = read_u16(fp); + for (u16 i = 0; i < nnodes; ++i) { + if (!session_read_node(ted, fp)) { + arr_foreach_ptr(ted->buffers, TextBufferPtr, pbuffer) { + buffer_free(*pbuffer); + } + arr_clear(ted->buffers); + arr_foreach_ptr(ted->nodes, NodePtr, pnode) { + node_free(*pnode); + } + arr_clear(ted->nodes); + return; + } } if (active_node_idx == U16_MAX) { ted->active_node = NULL; } else { - active_node_idx = clamp_u16(active_node_idx, 0, TED_MAX_NODES); - if (ted->nodes_used[active_node_idx]) - ted->active_node = &ted->nodes[active_node_idx]; + if (active_node_idx >= arr_len(ted->nodes)) + active_node_idx = 0; + ted->active_node = ted->nodes[active_node_idx]; } if (active_buffer_idx == U16_MAX) { ted->active_buffer = NULL; } else { - active_buffer_idx = clamp_u16(active_buffer_idx, 0, TED_MAX_BUFFERS); - if (ted->buffers_used[active_buffer_idx]) - ted->active_buffer = &ted->buffers[active_buffer_idx]; + if (active_buffer_idx >= arr_len(ted->buffers)) + active_buffer_idx = 0; + ted->active_buffer = ted->buffers[active_buffer_idx]; } if (nbuffers && !ted->active_buffer) { // set active buffer to something - for (u16 i = 0; i < TED_MAX_BUFFERS; ++i) { - if (ted->buffers_used[i]) { - ted_switch_to_buffer(ted, &ted->buffers[i]); - break; - } - } + ted->active_buffer = ted->buffers[0]; } } diff --git a/ted-internal.h b/ted-internal.h index 36d15d2..38a7dbc 100644 --- a/ted-internal.h +++ b/ted-internal.h @@ -301,24 +301,20 @@ struct FileSelector { // A node is a collection of tabs OR a split of two node struct Node { - /// dynamic array of indices into ted->buffers, or `NULL` if this is a split - u16 *tabs; + /// dynamic array of buffers, or `NULL` if this is a split + TextBuffer **tabs; /// number from 0 to 1 indicating where the split is. float split_pos; /// index of active tab in `tabs`. u16 active_tab; /// is the split vertical? if false, this split looks like a|b bool split_vertical; - /// split left/upper half; index into `ted->nodes` - u16 split_a; + /// split left/upper half + Node *split_a; /// split right/lower half - u16 split_b; + Node *split_b; }; -/// max number of buffers open at one time -#define TED_MAX_BUFFERS 256 -/// max number of nodes open at one time -#define TED_MAX_NODES 256 /// max tabs per node #define TED_MAX_TABS 100 /// max strings in all config files @@ -421,6 +417,9 @@ typedef struct { vec2 pos; } MouseRelease; +typedef TextBuffer *TextBufferPtr; +typedef Node *NodePtr; + struct Ted { /// all running LSP servers LSP *lsps[TED_LSP_MAX + 1]; @@ -570,12 +569,9 @@ struct Ted { char build_dir[TED_PATH_MAX]; /// where we are reading tags from char tags_dir[TED_PATH_MAX]; - bool nodes_used[TED_MAX_NODES]; /// `nodes[0]` is always the "root node", if any buffers are open. - Node nodes[TED_MAX_NODES]; - /// NOTE: the buffer at index 0 is reserved as a "null buffer" and should not be used. - bool buffers_used[TED_MAX_BUFFERS]; - TextBuffer buffers[TED_MAX_BUFFERS]; + Node **nodes; + TextBuffer **buffers; /// number of config file strings u32 nstrings; /// config file strings @@ -844,8 +840,12 @@ void ted_go_to_lsp_document_position(Ted *ted, LSP *lsp, LSPDocumentPosition pos void ted_cancel_lsp_request(Ted *ted, LSPServerRequestID *request); /// convert LSPWindowMessageType to MessageType MessageType ted_message_type_from_lsp(LSPWindowMessageType type); -/// Returns the index of an available node, or -1 if none are available -i32 ted_new_node(Ted *ted); +/// delete buffer - does NOT remove it from the node tree +void ted_delete_buffer(Ted *ted, TextBuffer *buffer); +/// Returns a new node, or NULL on out of memory +Node *ted_new_node(Ted *ted); +/// Returns a new buffer, or NULL on out of memory +TextBuffer *ted_new_buffer(Ted *ted); /// check for orphaned nodes and node cycles void ted_check_for_node_problems(Ted *ted); /// load ted configuration @@ -295,7 +295,7 @@ void ted_path_full(Ted *ted, const char *relpath, char *abspath, size_t abspath_ } static bool ted_is_regular_buffer(Ted *ted, TextBuffer *buffer) { - return buffer >= ted->buffers && buffer < ted->buffers + TED_MAX_BUFFERS; + return arr_index_of(ted->buffers, buffer) >= 0; } Status ted_get_file(Ted const *ted, const char *name, char *out, size_t outsz) { @@ -430,21 +430,15 @@ void ted_free_fonts(Ted *ted) { // get node and tab containing buffer static Node *ted_buffer_location_in_node_tree(Ted *ted, TextBuffer *buffer, u16 *tab_idx) { - // now we need to figure out where this buffer is - u16 idx = (u16)(buffer - ted->buffers); - const bool *nodes_used = ted->nodes_used; - for (u16 i = 0; i < TED_MAX_NODES; ++i) { - if (!nodes_used[i]) continue; - Node *node = &ted->nodes[i]; - arr_foreach_ptr(node->tabs, u16, tab) { - if (idx == *tab) { - if (tab_idx) - *tab_idx = (u16)(tab - node->tabs); - return node; - } + arr_foreach_ptr(ted->nodes, NodePtr, pnode) { + Node *node = *pnode; + i32 index = arr_index_of(node->tabs, buffer); + if (index >= 0) { + if (tab_idx) + *tab_idx = (u16)index; + return node; } } - assert(0); return NULL; } @@ -457,10 +451,14 @@ void ted_switch_to_buffer(Ted *ted, TextBuffer *buffer) { find_update(ted, true); // make sure find results are for this file } - if (buffer >= ted->buffers && buffer < ted->buffers + TED_MAX_BUFFERS) { + if (ted_is_regular_buffer(ted, buffer)) { ted->prev_active_buffer = buffer; u16 active_tab=0; Node *node = ted_buffer_location_in_node_tree(ted, buffer, &active_tab); + if (!node) { + assert(0); + return; + } node->active_tab = active_tab; ted->active_node = node; signature_help_retrigger(ted); @@ -471,11 +469,11 @@ void ted_switch_to_buffer(Ted *ted, TextBuffer *buffer) { } void ted_reset_active_buffer(Ted *ted) { - if (ted->nodes_used[0]) { - Node *node = &ted->nodes[0]; + if (arr_len(ted->nodes)) { + Node *node = ted->nodes[0]; while (!node->tabs) - node = &ted->nodes[node->split_a]; // arbitrarily pick split_a. - ted_switch_to_buffer(ted, &ted->buffers[node->tabs[node->active_tab]]); + node = node->split_a; // arbitrarily pick split_a. + ted_switch_to_buffer(ted, node->tabs[node->active_tab]); } else { // there's nothing to set it to ted_switch_to_buffer(ted, NULL); @@ -483,41 +481,36 @@ void ted_reset_active_buffer(Ted *ted) { } -static i32 ted_new_buffer(Ted *ted) { - bool *buffers_used = ted->buffers_used; - for (i32 i = 1; // start from 1, so as not to use the null buffer - i < TED_MAX_BUFFERS; ++i) { - if (!buffers_used[i]) { - buffers_used[i] = true; - buffer_create(&ted->buffers[i], ted); - return i; - } - } - return -1; -} - -void ted_delete_buffer(Ted *ted, u16 index) { - TextBuffer *buffer = &ted->buffers[index]; +void ted_delete_buffer(Ted *ted, TextBuffer *buffer) { if (buffer == ted->active_buffer) ted_switch_to_buffer(ted, NULL); // make sure we don't set the active buffer to something invalid if (buffer == ted->prev_active_buffer) ted->prev_active_buffer = NULL; buffer_free(buffer); - ted->buffers_used[index] = false; + arr_remove_item(ted->buffers, buffer); } -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]) { - memset(&ted->nodes[i], 0, sizeof ted->nodes[i]); // zero new node - nodes_used[i] = true; - return i; - } +Node *ted_new_node(Ted *ted) { + if (arr_len(ted->nodes) >= 100) { // TODO: constant + ted_error(ted, "Too many nodes."); + return NULL; } - ted_error(ted, "Too many nodes."); - return -1; - + Node *node = ted_calloc(ted, 1, sizeof *node); + if (!node) return NULL; + arr_add(ted->nodes, node); + return node; +} + +TextBuffer *ted_new_buffer(Ted *ted) { + if (arr_len(ted->buffers) >= 100) { // TODO: constant + ted_error(ted, "Too many buffers."); + return NULL; + } + TextBuffer *buffer = ted_calloc(ted, 1, sizeof *buffer); + if (!buffer) return NULL; + buffer_create(buffer, ted); + arr_add(ted->buffers, buffer); + return buffer; } float ted_line_buffer_height(Ted *ted) { @@ -527,50 +520,42 @@ float ted_line_buffer_height(Ted *ted) { void ted_node_switch(Ted *ted, Node *node) { assert(node->tabs); - ted_switch_to_buffer(ted, &ted->buffers[node->tabs[node->active_tab]]); -} - -// 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) { - if (!ted->nodes_used[0]) { - // no nodes open; create a root node - i32 node_idx = ted_new_node(ted); - assert(node_idx == 0); - node = &ted->nodes[node_idx]; - ted->active_node = node; - } else if (ted->prev_active_buffer) { - // opening a file while a menu is open - // it may happen.... (currently happens for rename symbol) - node = ted_buffer_location_in_node_tree(ted, ted->prev_active_buffer, NULL); - } else { - // idk what is going on - ted_error(ted, "internal error: can't figure out where to put this buffer."); - 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]; - buffer_create(new_buffer, ted); - node->active_tab = (u16)(arr_len(node->tabs) - 1); - *buffer_idx = (u16)new_buffer_index; - *tab = node->active_tab; - ted_switch_to_buffer(ted, new_buffer); - return true; + ted_switch_to_buffer(ted, node->tabs[node->active_tab]); + ted->active_node = node; +} + +// Open a new buffer. Fills out *tab to the index of the tab used. +static TextBuffer *ted_open_buffer(Ted *ted, u16 *tab) { + TextBuffer *new_buffer = ted_new_buffer(ted); + if (!new_buffer) return NULL; + Node *node = ted->active_node; + if (!node) { + if (!arr_len(ted->nodes)) { + // no nodes open; create a root node + node = ted->active_node = ted_new_node(ted); + } else if (ted->prev_active_buffer) { + // opening a file while a menu is open + // it may happen.... (currently happens for rename symbol) + node = ted_buffer_location_in_node_tree(ted, ted->prev_active_buffer, NULL); } else { - ted_error(ted, "Too many tabs."); - ted_delete_buffer(ted, (u16)new_buffer_index); - return false; + // idk what is going on + ted_error(ted, "internal error: can't figure out where to put this buffer."); + ted_delete_buffer(ted, new_buffer); + return NULL; } - } else { + } + + if (arr_len(node->tabs) >= TED_MAX_TABS) { + ted_error(ted, "Too many tabs."); + ted_delete_buffer(ted, new_buffer); return false; } + + arr_add(node->tabs, new_buffer); + node->active_tab = (u16)(arr_len(node->tabs) - 1); + *tab = node->active_tab; + ted_switch_to_buffer(ted, new_buffer); + return new_buffer; } TextBuffer *ted_get_buffer_with_file(Ted *ted, const char *path) { @@ -580,13 +565,10 @@ TextBuffer *ted_get_buffer_with_file(Ted *ted, const char *path) { return NULL; } - bool *buffers_used = ted->buffers_used; - TextBuffer *buffers = ted->buffers; - for (u16 i = 0; i < TED_MAX_BUFFERS; ++i) { - if (buffers_used[i]) { - if (buffers[i].path && paths_eq(path, buffers[i].path)) { - return &buffers[i]; - } + arr_foreach_ptr(ted->buffers, TextBufferPtr, pbuffer) { + TextBuffer *buffer = *pbuffer; + if (buffer->path && paths_eq(path, buffer->path)) { + return buffer; } } return NULL; @@ -609,12 +591,14 @@ bool ted_open_file(Ted *ted, const char *filename) { } // not open; we need to load it - u16 buffer_idx, tab_idx; - if (ted->active_buffer && !ted->active_buffer->path && buffer_empty(ted->active_buffer)) { + u16 tab_idx; + TextBuffer *buffer = NULL; + if (ted->active_buffer && !ted->active_buffer->path + && ted_is_regular_buffer(ted, ted->active_buffer) + && buffer_empty(ted->active_buffer)) { // the active buffer is just an empty untitled buffer. open it here. return buffer_load_file(ted->active_buffer, path); - } else if (ted_open_buffer(ted, &buffer_idx, &tab_idx)) { - TextBuffer *buffer = &ted->buffers[buffer_idx]; + } else if ((buffer = ted_open_buffer(ted, &tab_idx))) { if (buffer_load_file(buffer, path)) { return true; } else { @@ -628,7 +612,7 @@ bool ted_open_file(Ted *ted, const char *filename) { } bool ted_new_file(Ted *ted, const char *filename) { - u16 buffer_idx, tab_idx; + u16 tab_idx=0; char path[TED_PATH_MAX]; if (filename) ted_path_full(ted, filename, path, sizeof path); @@ -638,8 +622,7 @@ bool ted_new_file(Ted *ted, const char *filename) { if (buffer) { ted_switch_to_buffer(ted, buffer); return true; - } else if (ted_open_buffer(ted, &buffer_idx, &tab_idx)) { - buffer = &ted->buffers[buffer_idx]; + } else if ((buffer = ted_open_buffer(ted, &tab_idx))) { buffer_new_file(buffer, *path ? path : NULL); if (!buffer_has_error(buffer)) { return true; @@ -656,21 +639,18 @@ bool ted_new_file(Ted *ted, const char *filename) { bool ted_save_all(Ted *ted) { bool success = true; - bool *buffers_used = ted->buffers_used; - for (u16 i = 0; i < TED_MAX_BUFFERS; ++i) { - if (buffers_used[i]) { - TextBuffer *buffer = &ted->buffers[i]; - if (buffer_unsaved_changes(buffer)) { - if (!buffer->path) { - ted_switch_to_buffer(ted, buffer); - menu_open(ted, MENU_SAVE_AS); - success = false; // we haven't saved this buffer yet; we've just opened the "save as" menu. - break; - } else { - if (!buffer_save(buffer)) { - success = false; - ted_error_from_buffer(ted, buffer); - } + arr_foreach_ptr(ted->buffers, TextBufferPtr, pbuffer) { + TextBuffer *buffer = *pbuffer; + if (buffer_unsaved_changes(buffer)) { + if (!buffer->path) { + ted_switch_to_buffer(ted, buffer); + menu_open(ted, MENU_SAVE_AS); + success = false; // we haven't saved this buffer yet; we've just opened the "save as" menu. + break; + } else { + if (!buffer_save(buffer)) { + success = false; + ted_error_from_buffer(ted, buffer); } } } @@ -679,13 +659,10 @@ bool ted_save_all(Ted *ted) { } void ted_reload_all(Ted *ted) { - bool *buffers_used = ted->buffers_used; - for (u64 i = 0; i < TED_MAX_BUFFERS; ++i) { - if (buffers_used[i]) { - TextBuffer *buffer = &ted->buffers[i]; - if (!buffer_unsaved_changes(buffer)) { - buffer_reload(buffer); - } + arr_foreach_ptr(ted->buffers, TextBufferPtr, pbuffer) { + TextBuffer *buffer = *pbuffer; + if (!buffer_unsaved_changes(buffer)) { + buffer_reload(buffer); } } if (menu_is_open(ted, MENU_ASK_RELOAD)) { @@ -763,18 +740,13 @@ void ted_press_key(Ted *ted, SDL_Keycode keycode, SDL_Keymod modifier) { } bool ted_get_mouse_buffer_pos(Ted *ted, TextBuffer **pbuffer, BufferPos *ppos) { - for (u32 i = 0; i < TED_MAX_NODES; ++i) { - if (ted->nodes_used[i]) { - Node *node = &ted->nodes[i]; - if (node->tabs) { - TextBuffer *buffer = &ted->buffers[node->tabs[node->active_tab]]; - BufferPos pos = {0}; - if (buffer_pixels_to_pos(buffer, ted->mouse_pos, &pos)) { - if (ppos) *ppos = pos; - if (pbuffer) *pbuffer = buffer; - return true; - } - } + arr_foreach_ptr(ted->buffers, TextBufferPtr, pbuf) { + TextBuffer *buffer = *pbuf; + BufferPos pos = {0}; + if (buffer_pixels_to_pos(buffer, ted->mouse_pos, &pos)) { + if (ppos) *ppos = pos; + if (pbuffer) *pbuffer = buffer; + return true; } } return false; @@ -806,32 +778,35 @@ void ted_cancel_lsp_request(Ted *ted, LSPServerRequestID *request) { } -static void mark_node_reachable(Ted *ted, u16 node, bool reachable[TED_MAX_NODES]) { - if (reachable[node]) { - ted_error(ted, "Node %u reachable in 2 different ways\nThis should never happen.", node); - ted_log(ted, "Node %u reachable in 2 different ways\n", node); +static void mark_node_reachable(Ted *ted, Node *node, bool *reachable) { + i32 i = arr_index_of(ted->nodes, node); + if (i < 0) return; + if (reachable[i]) { + ted_error(ted, "Node %d reachable in 2 different ways\nThis should never happen.", i); + ted_log(ted, "Node %d reachable in 2 different ways\n", i); node_close(ted, node); return; } - reachable[node] = true; - Node *n = &ted->nodes[node]; - if (!n->tabs) { - mark_node_reachable(ted, n->split_a, reachable); - mark_node_reachable(ted, n->split_b, reachable); + reachable[i] = true; + if (!node->tabs) { + mark_node_reachable(ted, node->split_a, reachable); + mark_node_reachable(ted, node->split_b, reachable); } } void ted_check_for_node_problems(Ted *ted) { - bool reachable[TED_MAX_NODES] = {0}; - if (ted->nodes_used[0]) - mark_node_reachable(ted, 0, reachable); - for (u16 i = 0; i < TED_MAX_NODES; ++i) { - if (ted->nodes_used[i] && !reachable[i]) { + bool *reachable = ted_calloc(ted, arr_len(ted->nodes), 1); + if (arr_len(ted->nodes)) + mark_node_reachable(ted, ted->nodes[0], reachable); + for (u32 i = 0; i < arr_len(ted->nodes); ++i) { + if (!reachable[i]) { ted_error(ted, "ORPHANED NODE %u\nThis should never happen.", i); ted_log(ted, "ORPHANED NODE %u\n", i); - node_close(ted, i); + node_close(ted, ted->nodes[i]); + --i; } } + free(reachable); } MessageType ted_message_type_from_lsp(LSPWindowMessageType type) { @@ -902,14 +902,28 @@ void node_tab_switch(Ted *ted, Node *node, i32 tab); /// /// if `node` is a split or either index is out of range, nothing happens. void node_tabs_swap(Node *node, i32 tab1, i32 tab2); -/// returns index of parent in ted->nodes, or -1 if this is the root node. -i32 node_parent(Ted *ted, i32 node_idx); +/* TODO +/// get two children of split node. +/// +/// if `node` isn't a split, returns false and sets `*child1` and `*child2` to `NULL`. +bool node_children(Node *node, Node **child1, Node **child2); +/// returns true if this node is a vertical split +bool node_split_is_vertical(Node *node); +/// get number of tabs in node +u32 node_tab_count(Ted *ted, Node *node); +/// get buffer in tab at index of node. +/// +/// returns `NULL` if `tab` is out of range. +TextBuffer *node_tab_get(Ted *ted, Node *node, u32 tab); +*/ +/// returns parent node, or NULL if this is the root node. +Node *node_parent(Ted *ted, Node *node); /// join this node with its sibling void node_join(Ted *ted, Node *node); /// close a node, WITHOUT checking for unsaved changes /// -/// does nothing if node_idx is out of range -void node_close(Ted *ted, i32 node_idx); +/// does nothing if `node` is `NULL`. +void node_close(Ted *ted, Node *node); /// close tab, WITHOUT checking for unsaved changes! /// returns true if the node is still open /// @@ -1069,9 +1083,6 @@ bool ted_open_file(Ted *ted, const char *filename); /// if `filename` is NULL, this creates an untitled buffer. /// returns true on success. bool ted_new_file(Ted *ted, const char *filename); -/// Opposite of ted_new_buffer -/// Make sure you set active_buffer to something else if you delete it! -void ted_delete_buffer(Ted *ted, u16 index); /// save all changes to all buffers with unsaved changes. bool ted_save_all(Ted *ted); /// sets the active buffer to this buffer, and updates active_node, etc. accordingly @@ -211,4 +211,15 @@ static size_t unicode_utf16_to_utf8_offset(const char *str, size_t utf16_offset) return SIZE_MAX; } +static bool unicode_is_valid_utf8(const char *cstr) { + char32_t c = 0; + while (*cstr) { + size_t n = unicode_utf8_to_utf32(&c, cstr, 4); + if (n >= (size_t)-2) + return false; + cstr += n; + } + return true; +} + #endif // UNICODE_H_ |