From e45499816eab8abadbdd5bb6dd79b526a4ed6648 Mon Sep 17 00:00:00 2001 From: Albert Cervin Date: Sat, 11 Feb 2023 23:03:39 +0100 Subject: Implement undo This also fixes a bunch of valgrind errors --- src/undo.c | 200 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 200 insertions(+) create mode 100644 src/undo.c (limited to 'src/undo.c') diff --git a/src/undo.c b/src/undo.c new file mode 100644 index 0000000..6ebec12 --- /dev/null +++ b/src/undo.c @@ -0,0 +1,200 @@ +#include "undo.h" +#include "string.h" + +#include +#include + +void undo_init(struct undo_stack *undo, uint32_t initial_capacity) { + undo->top = INVALID_TOP; + undo->nrecords = 0; + undo->undo_in_progress = false; + + undo->records = calloc(initial_capacity, sizeof(struct undo_record)); + undo->capacity = initial_capacity; +} + +void grow_if_needed(struct undo_stack *undo, uint32_t needed_capacity) { + if (needed_capacity > undo->capacity) { + undo->capacity += undo->capacity + needed_capacity > undo->capacity * 2 + ? needed_capacity + : undo->capacity; + + undo->records = + realloc(undo->records, sizeof(struct undo_record) * undo->capacity); + } +} + +void undo_clear(struct undo_stack *undo) { + undo->top = INVALID_TOP; + undo->nrecords = 0; +} + +void undo_destroy(struct undo_stack *undo) { + for (uint32_t i = 0; i < undo->nrecords; ++i) { + struct undo_record *rec = &undo->records[i]; + if (rec->type == Undo_Delete && rec->delete.data != NULL && + rec->delete.nbytes > 0) { + free(rec->delete.data); + } + } + undo_clear(undo); + undo->capacity = 0; + + free(undo->records); + undo->records = NULL; +} + +uint32_t undo_push_boundary(struct undo_stack *undo, + struct undo_boundary boundary) { + grow_if_needed(undo, undo->nrecords + 1); + + undo->records[undo->nrecords].type = Undo_Boundary; + undo->records[undo->nrecords].boundary = boundary; + + if (!undo->undo_in_progress) { + undo->top = undo->nrecords; + } + + // we can only have one save point + if (boundary.save_point) { + for (uint32_t i = 0; i < undo->nrecords; ++i) { + if (undo->records[i].type && Undo_Boundary && + undo->records[i].boundary.save_point) { + undo->records[i].boundary.save_point = false; + } + } + } + + ++undo->nrecords; + return undo->nrecords - 1; +} + +bool pos_equal(struct position *a, struct position *b) { + return a->row == b->row && a->col == b->col; +} + +uint32_t undo_push_add(struct undo_stack *undo, struct undo_add add) { + grow_if_needed(undo, undo->nrecords + 1); + + // "compress" + if (undo->nrecords > 0 && + undo->records[undo->nrecords - 1].type == Undo_Add && + pos_equal(&undo->records[undo->nrecords - 1].add.end, &add.begin)) { + undo->records[undo->nrecords - 1].add.end = add.end; + return undo->nrecords; + } + + undo->records[undo->nrecords].type = Undo_Add; + undo->records[undo->nrecords].add = add; + + if (!undo->undo_in_progress) { + undo->top = undo->nrecords; + } + + ++undo->nrecords; + return undo->nrecords - 1; +} + +uint32_t undo_push_delete(struct undo_stack *undo, struct undo_delete delete) { + grow_if_needed(undo, undo->nrecords + 1); + + undo->records[undo->nrecords].type = Undo_Delete; + undo->records[undo->nrecords].delete = delete; + + if (!undo->undo_in_progress) { + undo->top = undo->nrecords; + } + + ++undo->nrecords; + return undo->nrecords - 1; +} + +void undo_begin(struct undo_stack *undo) { undo->undo_in_progress = true; } + +void undo_next(struct undo_stack *undo, struct undo_record **records_out, + uint32_t *nrecords_out) { + *nrecords_out = 0; + *records_out = NULL; + + if (undo->nrecords == 0) { + return; + } + + if (undo->top == INVALID_TOP) { + // reset back to the top (redo) + undo->top = undo->nrecords - 1; + } + + uint32_t nrecords = 1; + struct undo_record *current = &undo->records[undo->top]; + while (undo->top > 0 && current->type == Undo_Boundary) { + ++nrecords; + --undo->top; + current = &undo->records[undo->top]; + } + + while (undo->top > 0 && current->type != Undo_Boundary) { + ++nrecords; + --undo->top; + current = &undo->records[undo->top]; + } + + if (nrecords > 0) { + *records_out = calloc(nrecords, sizeof(struct undo_record)); + *nrecords_out = nrecords; + + struct undo_record *dest = *records_out; + + // copy backwards + for (uint32_t reci = undo->top + nrecords, outi = 0; reci > undo->top; + --reci, ++outi) { + dest[outi] = undo->records[reci - 1]; + } + } + + if (undo->top > 0) { + --undo->top; + } else { + undo->top = INVALID_TOP; + } +} + +void undo_end(struct undo_stack *undo) { undo->undo_in_progress = false; } + +uint32_t undo_size(struct undo_stack *undo) { return undo->nrecords; } +uint32_t undo_current_position(struct undo_stack *undo) { return undo->top; } + +size_t rec_to_str(struct undo_record *rec, char *buffer, size_t n) { + switch (rec->type) { + case Undo_Add: + return snprintf(buffer, n, "add { begin: (%d, %d) end: (%d, %d)}", + rec->add.begin.row, rec->add.begin.col, rec->add.end.row, + rec->add.end.col); + case Undo_Delete: + return snprintf(buffer, n, "delete { pos: (%d, %d), ptr: 0x%p, nbytes: %d}", + rec->delete.pos.row, rec->delete.pos.col, rec->delete.data, + rec->delete.nbytes); + default: + return snprintf(buffer, n, "boundary { save_point: %s }", + rec->boundary.save_point ? "yes" : "no"); + } +} + +const char *undo_dump(struct undo_stack *undo) { + uint32_t left = 8192; + const char *buf = malloc(left); + char *pos = (char *)buf; + pos[0] = '\0'; + + char rec_buf[256]; + for (uint32_t reci = 0; reci < undo->nrecords && left > 0; ++reci) { + struct undo_record *rec = &undo->records[reci]; + rec_to_str(rec, rec_buf, 256); + uint32_t written = snprintf(pos, left, "%d: [%s]%s\n", reci, rec_buf, + reci == undo->top ? " <- top" : ""); + left = written > left ? 0 : left - written; + pos += written; + } + + return buf; +} -- cgit v1.2.3