#include #include #include #include "abuf.h" #include "editor.h" #include "buffer.h" #include "undo.h" undo_node * undo_node_new(undo_kind kind) { undo_node *node = NULL; node = (undo_node *)malloc(sizeof(undo_node)); assert(node != NULL); node->kind = kind; node->row = node->col = 0; ab_init(&node->text); node->next = NULL; node->parent = NULL; return node; } void undo_node_free(undo_node *node) { if (node == NULL) { return; } ab_free(&node->text); } void undo_node_free_all(undo_node *node) { undo_node *next = NULL; if (node == NULL) { return; } while (node != NULL) { next = node->next; undo_node_free(node); free(node); node = next; } } void undo_tree_init(undo_tree *tree) { assert(tree != NULL); tree->root = NULL; tree->current = NULL; tree->pending = NULL; } void undo_tree_free(undo_tree *tree) { assert(tree != NULL); undo_node_free(tree->pending); undo_node_free_all(tree->root); undo_tree_init(tree); } void undo_begin(undo_tree *tree, undo_kind kind) { undo_node *pending = NULL; if (tree->pending != NULL) { if (tree->pending->kind == kind) { /* don't initiate a new undo sequence if it's the same kind */ return; } undo_commit(tree); } pending = undo_node_new(kind); assert(pending != NULL); tree->pending = pending; } void undo_prepend(undo_tree *tree, abuf *buf) { assert(tree != NULL); assert(tree->pending != NULL); ab_prepend_ab(&tree->pending->text, buf); } void undo_append(undo_tree *tree, abuf *buf) { assert(tree != NULL); assert(tree->pending != NULL); ab_append_ab(&tree->pending->text, buf); } void undo_prependch(undo_tree *tree, char c) { assert(tree != NULL); assert(tree->pending != NULL); ab_prependch(&tree->pending->text, c); } void undo_appendch(undo_tree *tree, char c) { assert(tree != NULL); assert(tree->pending != NULL); ab_appendch(&tree->pending->text, c); } void undo_commit(undo_tree *tree) { assert(tree != NULL); if (tree->pending == NULL) { return; } if (tree->root == NULL) { tree->root = tree->pending; tree->current = tree->pending; tree->pending = NULL; return; } undo_node_free_all(tree->current->next); tree->current->next = tree->pending; tree->pending->parent = tree->current; tree->current = tree->pending; tree->pending = NULL; } /* --- Helper functions for applying undo/redo operations --- */ static void row_insert_at(buffer *b, size_t at, const char *s, size_t len) { abuf *newrows = realloc(b->row, sizeof(abuf) * (b->nrows + 1)); assert(newrows != NULL); b->row = newrows; if (at < b->nrows) { memmove(&b->row[at + 1], &b->row[at], sizeof(abuf) * (b->nrows - at)); } ab_init(&b->row[at]); if (len > 0) { ab_append(&b->row[at], s, len); } b->nrows++; b->dirty++; } static void ensure_row_exists(buffer *b, size_t r) { while (r >= b->nrows) { row_insert_at(b, b->nrows, "", 0); } } static void row_insert_ch_at(abuf *row, size_t col, char ch) { if (col > row->size) { col = row->size; } ab_resize(row, row->size + 2); memmove(&row->b[col + 1], &row->b[col], row->size - col + 1); row->b[col] = ch; row->size++; row->b[row->size] = '\0'; } static void row_delete_ch_at(abuf *row, size_t col) { if (col >= row->size) { return; } memmove(&row->b[col], &row->b[col + 1], row->size - col); row->size--; row->b[row->size] = '\0'; } static void split_row_at(buffer *b, size_t r, size_t c) { ensure_row_exists(b, r); abuf *row = &b->row[r]; if (c > row->size) c = row->size; size_t rhs_len = row->size - c; char *rhs = NULL; if (rhs_len > 0) { rhs = malloc(rhs_len); assert(rhs != NULL); memcpy(rhs, &row->b[c], rhs_len); } row->size = c; if (row->cap <= row->size) { ab_resize(row, row->size + 1); } row->b[row->size] = '\0'; row_insert_at(b, r + 1, rhs ? rhs : "", rhs_len); if (rhs) free(rhs); b->dirty++; } static void join_with_next_row(buffer *b, size_t r, size_t c) { if (r >= b->nrows) return; abuf *row = &b->row[r]; if (c > row->size) c = row->size; if (r + 1 >= b->nrows) return; abuf *next = &b->row[r + 1]; /* Make room in current row at end if needed (we append next->b after position c). */ size_t tail_len = row->size - c; size_t add_len = next->size; ab_resize(row, row->size + add_len + 1); /* Move tail to make room for next content */ memmove(&row->b[c + add_len], &row->b[c], tail_len); memcpy(&row->b[c], next->b, add_len); row->size += add_len; row->b[row->size] = '\0'; /* Delete next row */ ab_free(next); if (b->nrows - (r + 2) > 0) { memmove(&b->row[r + 1], &b->row[r + 2], sizeof(abuf) * (b->nrows - (r + 2))); } b->nrows--; b->dirty++; } static void buffer_insert_text(buffer *b, size_t row, size_t col, const char *s, size_t len) { ensure_row_exists(b, row); size_t r = row; size_t c = col; if (c > b->row[r].size) c = b->row[r].size; for (size_t i = 0; i < len; i++) { char ch = s[i]; if (ch == '\n') { split_row_at(b, r, c); r++; c = 0; } else { row_insert_ch_at(&b->row[r], c, ch); c++; } b->dirty++; } } static void buffer_delete_text(buffer *b, size_t row, size_t col, const char *s, size_t len) { if (row >= b->nrows) return; size_t r = row; size_t c = col; if (c > b->row[r].size) c = b->row[r].size; for (size_t i = 0; i < len; i++) { char ch = s[i]; if (ch == '\n') { /* delete newline at (r,c): join row r with next */ join_with_next_row(b, r, c); } else { row_delete_ch_at(&b->row[r], c); /* c stays the same because character at c is removed */ } b->dirty++; if (r >= b->nrows) break; /* safety */ if (c > b->row[r].size) c = b->row[r].size; } } void undo_apply(struct buffer *buf, int direction) { undo_tree *tree = &buf->undo; undo_node *node = NULL; undo_commit(tree); if (tree->root == NULL) { return; } if (direction < 0) { /* UNDO: apply inverse of current node, then move back */ node = tree->current; if (node == NULL) { return; } switch (node->kind) { /* support insert first */ case UNDO_INSERT: buffer_delete_text(buf, node->row, node->col, node->text.b, node->text.size); break; default: /* unknown type: do nothing */ break; } tree->current = node->parent; /* move back in history */ } else if (direction > 0) { /* REDO: move forward then apply node */ undo_node *next = (tree->current == NULL) ? tree->root : tree->current->next; if (next == NULL) { return; /* nothing to redo */ } switch (next->kind) { /* support insert first */ case UNDO_INSERT: buffer_insert_text(buf, next->row, next->col, next->text.b, next->text.size); break; default: /* unknown type: do nothing */ break; } tree->current = next; /* move forward in history */ } } void editor_undo(struct buffer *buf) { undo_apply(buf, -1); } void editor_redo(struct buffer *buf) { undo_apply(buf, 1); }