From b50f5c763c575d7284b7a7e7507b2151d1e98fbf Mon Sep 17 00:00:00 2001 From: Leo Tenenbaum Date: Sun, 20 Dec 2020 22:59:03 -0500 Subject: more undo --- base.h | 8 ++++- buffer.c | 123 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 113 insertions(+), 18 deletions(-) diff --git a/base.h b/base.h index 65c8f20..b17a998 100644 --- a/base.h +++ b/base.h @@ -10,7 +10,7 @@ #endif #include -#include +#include #include #include #include @@ -24,11 +24,17 @@ typedef uint16_t u16; typedef uint32_t u32; typedef uint64_t u64; +#define U32_FMT "%" PRIu32 +#define U64_FMT "%" PRIu64 + typedef int8_t i8; typedef int16_t i16; typedef int32_t i32; typedef int64_t i64; +#define I32_FMT "%" PRId32 +#define I64_FMT "%" PRId64 + typedef unsigned int uint; typedef unsigned long ulong; diff --git a/buffer.c b/buffer.c index 54c6dbe..39a25a5 100644 --- a/buffer.c +++ b/buffer.c @@ -90,11 +90,19 @@ static void buffer_out_of_mem(TextBuffer *buffer) { buffer_seterr(buffer, "Out of memory."); } +// add this edit to the undo history static void buffer_append_edit(TextBuffer *buffer, BufferEdit *edit) { edit->next = buffer->undo_history; buffer->undo_history = edit; } +// add this edit to the redo history +static void buffer_append_redo(TextBuffer *buffer, BufferEdit *edit) { + edit->next = buffer->redo_history; + buffer->redo_history = edit; +} + + static void *buffer_calloc(TextBuffer *buffer, size_t n, size_t size) { void *ret = calloc(n, size); if (!ret) buffer_out_of_mem(buffer); @@ -107,10 +115,77 @@ static void *buffer_realloc(TextBuffer *buffer, void *p, size_t new_size) { return ret; } +// ensures that `p` refers to a valid position. +static void buffer_pos_validate(TextBuffer *buffer, BufferPos *p) { + if (p->line >= buffer->nlines) + p->line = buffer->nlines - 1; + u32 line_len = buffer->lines[p->line].len; + if (p->index > line_len) + p->index = line_len; +} + +static bool buffer_pos_valid(TextBuffer *buffer, BufferPos p) { + return p.line < buffer->nlines && p.index <= buffer->lines[p.line].len; +} + + +// get some number of characters of text from the given position in the buffer. +static Status buffer_get_text_at_pos(TextBuffer *buffer, BufferPos pos, char32_t *text, u32 nchars) { + if (!buffer_pos_valid(buffer, pos)) { + return false; + } + char32_t *p = text; + u32 chars_left = nchars; + Line *line = &buffer->lines[pos.line], *end = buffer->lines + buffer->nlines; + u32 index = pos.index; + while (chars_left) { + u32 chars_from_this_line = line->len - index; + if (chars_left <= chars_from_this_line) { + memcpy(p, line->str, chars_left); + chars_left = 0; + } else { + memcpy(p, line->str, chars_from_this_line); + p += chars_from_this_line; + *p++ = U'\n'; + chars_left -= chars_from_this_line+1; + } + + index = 0; + ++line; + if (chars_left && line == end) { + // reached end of file before getting full text... this isn't good + buffer_seterr(buffer, "Failed to fetch " U32_FMT " characters at " U32_FMT ":" U32_FMT ".", nchars, pos.line+1, pos.index+1); + return false; + } + } + return true; +} + // functions for creating buffer edits: -// call these before you make an edit to keep an undo event -static WarnUnusedResult BufferEdit *buffer_edit_delete_text(TextBuffer *buffer, BufferPos start, u32 len) { - // @TODO +// call these before you make an edit to create an undo event +static WarnUnusedResult BufferEdit *buffer_create_edit_delete_text(TextBuffer *buffer, BufferPos start, u32 len) { + BufferEdit *e = buffer_calloc(buffer, 1, sizeof *e + len * sizeof *e->text); + if (e) { + e->type = BUFFER_EDIT_DELETE_TEXT; + e->pos = start; + e->text_len = len; + if (!buffer_get_text_at_pos(buffer, start, e->text, len)) { + free(e); + e = NULL; + } + } + return e; +} + +static WarnUnusedResult BufferEdit *buffer_create_edit_insert_text(TextBuffer *buffer, BufferPos start, u32 len) { + if (!buffer_pos_valid(buffer, start)) return NULL; + BufferEdit *e = buffer_calloc(buffer, 1, sizeof *e); + if (e) { + e->type = BUFFER_EDIT_INSERT_TEXT; + e->pos = start; + e->text_len = len; + } + return e; } @@ -563,20 +638,6 @@ static void buffer_scroll_to_cursor(TextBuffer *buffer) { buffer_correct_scroll(buffer); // it's possible that min/max_scroll_x/y go too far } - -// ensures that `p` refers to a valid position. -static void buffer_pos_validate(TextBuffer *buffer, BufferPos *p) { - if (p->line >= buffer->nlines) - p->line = buffer->nlines - 1; - u32 line_len = buffer->lines[p->line].len; - if (p->index > line_len) - p->index = line_len; -} - -static bool buffer_pos_valid(TextBuffer *buffer, BufferPos p) { - return p.line < buffer->nlines && p.index <= buffer->lines[p.line].len; -} - // returns "p2 - p1", that is, the number of characters between p1 and p2. static i64 buffer_pos_diff(TextBuffer *buffer, BufferPos p1, BufferPos p2) { assert(buffer_pos_valid(buffer, p1)); @@ -1038,3 +1099,31 @@ void buffer_backspace_words_at_cursor(TextBuffer *buffer, i64 nwords) { buffer_backspace_words_at_pos(buffer, &buffer->cursor_pos, nwords); } +void buffer_undo(TextBuffer *buffer, i64 ntimes) { + for (i64 i = 0; i < ntimes; ++i) { + BufferEdit *edit = buffer->undo_history; + BufferEdit *inverse = NULL; + + switch (edit->type) { + case BUFFER_EDIT_INSERT_TEXT: + // the inverse edit is the deletion of this text + inverse = buffer_create_edit_delete_text(buffer, edit->pos, edit->text_len); + // delete the text + buffer_delete_chars_at_pos(buffer, edit->pos, edit->text_len); + break; + case BUFFER_EDIT_DELETE_TEXT: { + // the inverse edit is the insertion of this text + inverse = buffer_create_edit_insert_text(buffer, edit->pos, edit->text_len); + // insert the text + String32 str = {edit->text_len, edit->text}; + buffer_insert_text_at_pos(buffer, edit->pos, str); + } break; + } + + if (inverse) { + buffer_append_redo(buffer, inverse); + } + + free(edit); + } +} -- cgit v1.2.3