From b60da59ced90256d67af20f6d8f7365b97fc802c Mon Sep 17 00:00:00 2001 From: "Michael D. Lowis" Date: Wed, 26 Oct 2016 20:47:03 -0400 Subject: [PATCH] first crack at undo/redo. *horribly* inefficient --- buf.c | 192 +++++++++++++++++++++++++++++++++++++++++++++------- edit.h | 47 +++++++++---- keyboard.c | 40 +++++++---- mouse.c | 8 +-- tests/buf.c | 7 +- xedit.c | 6 +- 6 files changed, 243 insertions(+), 57 deletions(-) diff --git a/buf.c b/buf.c index 67b85d5..05ce9f7 100644 --- a/buf.c +++ b/buf.c @@ -6,7 +6,7 @@ #include "edit.h" void buf_load(Buf* buf, char* path) { - buf->insert_mode = true; + buf_setlocked(buf, false); if (!strcmp(path,"-")) { buf->charset = UTF_8; Rune r; @@ -29,8 +29,8 @@ void buf_load(Buf* buf, char* path) { buf_ins(buf, 0, (Rune)'\n'); funmap(file); } - buf->insert_mode = false; - buf->modified = false; + buf_setlocked(buf, true); + buf->modified = false; } void buf_save(Buf* buf) { @@ -80,32 +80,41 @@ static void syncgap(Buf* buf, unsigned off) { } void buf_init(Buf* buf) { - buf->insert_mode = false; - buf->modified = false; - buf->charset = DEFAULT_CHARSET; - buf->crlf = DEFAULT_CRLF; - buf->bufsize = BufSize; - buf->bufstart = (Rune*)malloc(buf->bufsize * sizeof(Rune)); - buf->bufend = buf->bufstart + buf->bufsize; - buf->gapstart = buf->bufstart; - buf->gapend = buf->bufend; + buf->locked = true; + buf->modified = false; + buf->charset = DEFAULT_CHARSET; + buf->crlf = DEFAULT_CRLF; + buf->bufsize = BufSize; + buf->bufstart = (Rune*)malloc(buf->bufsize * sizeof(Rune)); + buf->bufend = buf->bufstart + buf->bufsize; + buf->gapstart = buf->bufstart; + buf->gapend = buf->bufend; + buf->undo = NULL; + buf->redo = NULL; } -void buf_clr(Buf* buf) { - free(buf->bufstart); - buf_init(buf); +//static void log_insert(Log** list, unsigned beg, unsigned end) { +static void log_insert(Log** list, Log* log) { + Log* newlog = (Log*)calloc(sizeof(Log), 1); + *newlog = *log; + newlog->next = *list; + *list = newlog; } -void buf_del(Buf* buf, unsigned off) { - if (!buf->insert_mode) { return; } - buf->modified = true; - syncgap(buf, off); - buf->gapend++; +//static void log_delete(Log** list, unsigned off, Rune r) { +static void log_delete(Log** list, Log* log) { + size_t len = log->data.del.len; + Rune* runes = (Rune*)malloc(sizeof(Rune) * len); + memcpy(runes, log->data.del.runes, sizeof(Rune) * len); + Log* newlog = (Log*)calloc(sizeof(Log), 1); + *newlog = *log; + newlog->next = *list; + newlog->data.del.runes = runes; + *list = newlog; } -void buf_ins(Buf* buf, unsigned off, Rune rune) { - if (!buf->insert_mode) { return; } - buf->modified = true; +static void insert(Buf* buf, unsigned off, Rune rune) +{ syncgap(buf, off); if (buf->crlf && rune == '\n' && buf_get(buf, off-1) == '\r') *(buf->gapstart-1) = RUNE_CRLF; @@ -113,6 +122,132 @@ void buf_ins(Buf* buf, unsigned off, Rune rune) { *(buf->gapstart++) = rune; } +void buf_ins(Buf* buf, unsigned off, Rune rune) { + if (buf->locked) { return; } + buf->modified = true; + //log_insert(&(buf->undo), off); + log_insert(&(buf->undo), &(Log){ + .insert = true, + .data.ins = { .beg = off, .end = off+1 } + }); + insert(buf, off, rune); + + ///* add or update the insert log */ + //if (NULL == buf->undo || !buf->undo->insert || buf->undo->locked || + // off < buf->undo->data.ins.beg || off > buf->undo->data.ins.end) { + // puts("creating insert log"); + // Log* log = (Log*)calloc(sizeof(Log),1); + // log->insert = true; + // log->data.ins.beg = off; + // log->data.ins.end = off+1; + // log->next = buf->undo; + // if (buf->undo) + // buf->undo->locked = true; + // buf->undo = log; + //} else { + // puts("updating insert log"); + // buf->undo->data.ins.end++; + //} +} + +static void delete(Buf* buf, unsigned off) { + syncgap(buf, off); + buf->gapend++; +} + +void buf_del(Buf* buf, unsigned off) { + if (buf->locked) { return; } + buf->modified = true; + + //log_delete(&(buf->undo), off, buf_get(buf, off)); + log_delete(&(buf->undo), &(Log){ + .insert = false, + .data.del = { .off = off, .len = 1, .runes = &(Rune){ buf_get(buf,off) } } + }); + delete(buf, off); + + /* add or update the insert log */ + //if (NULL == buf->undo || buf->undo->insert || buf->undo->locked || + // off < buf->undo->data.ins.beg || off > buf->undo->data.ins.end) { + // puts("creating insert log"); + // Log* log = (Log*)calloc(sizeof(Log),1); + // log->insert = true; + // log->data.ins.beg = off; + // log->data.ins.end = off+1; + // log->next = buf->undo; + // if (buf->undo) + // buf->undo->locked = true; + // buf->undo = log; + //} else { + // puts("updating insert log"); + // buf->undo->data.ins.end++; + //} +} + +unsigned buf_undo(Buf* buf, unsigned pos) { + /* pop the last undo action */ + Log* log = buf->undo; + if (!log) return pos; + buf->undo = log->next; + /* */ + Log* redo = (Log*)calloc(sizeof(Log), 1); + if (log->insert) { + redo->insert = false; + size_t n = (log->data.ins.end - log->data.ins.beg); + redo->data.del.off = log->data.ins.beg; + redo->data.del.len = n; + redo->data.del.runes = (Rune*)malloc(n * sizeof(Rune)); + for (size_t i = 0; i < n; i++) { + redo->data.del.runes[i] = buf_get(buf, log->data.ins.beg); + delete(buf, log->data.ins.beg); + } + pos = redo->data.del.off; + } else { + redo->insert = true; + redo->data.ins.beg = log->data.del.off; + redo->data.ins.end = log->data.del.off + log->data.del.len; + for (size_t i = log->data.del.len; i > 0; i--) { + insert(buf, redo->data.ins.beg, log->data.del.runes[i-1]); + } + pos = redo->data.ins.end; + } + redo->next = buf->redo; + buf->redo = redo; + return pos; +} + +unsigned buf_redo(Buf* buf, unsigned pos) { + /* pop the last redo action */ + Log* log = buf->redo; + if (!log) return pos; + buf->redo = log->next; + /* */ + Log* undo = (Log*)calloc(sizeof(Log), 1); + if (log->insert) { + undo->insert = false; + size_t n = (log->data.ins.end - log->data.ins.beg); + undo->data.del.off = log->data.ins.beg; + undo->data.del.len = n; + undo->data.del.runes = (Rune*)malloc(n * sizeof(Rune)); + for (size_t i = 0; i < n; i++) { + undo->data.del.runes[i] = buf_get(buf, log->data.ins.beg); + delete(buf, log->data.ins.beg); + } + pos = undo->data.del.off; + } else { + undo->insert = true; + undo->data.ins.beg = log->data.del.off; + undo->data.ins.end = log->data.del.off + log->data.del.len; + for (size_t i = log->data.del.len; i > 0; i--) { + insert(buf, undo->data.ins.beg, log->data.del.runes[i-1]); + } + pos = undo->data.ins.end; + } + undo->next = buf->undo; + buf->undo = undo; + return pos; +} + Rune buf_get(Buf* buf, unsigned off) { if (off >= buf_end(buf)) return (Rune)'\n'; size_t bsz = (buf->gapstart - buf->bufstart); @@ -122,6 +257,16 @@ Rune buf_get(Buf* buf, unsigned off) { return *(buf->gapend + (off - bsz)); } +void buf_setlocked(Buf* buf, bool locked) { + if (locked) + buf->undo->locked =true; + buf->locked = locked; +} + +bool buf_locked(Buf* buf) { + return buf->locked; +} + bool buf_iseol(Buf* buf, unsigned off) { Rune r = buf_get(buf, off); return (r == '\n' || r == RUNE_CRLF); @@ -254,3 +399,4 @@ unsigned buf_setcol(Buf* buf, unsigned pos, unsigned col) { } return curr; } + diff --git a/edit.h b/edit.h index 0a4ddd0..576f813 100644 --- a/edit.h +++ b/edit.h @@ -41,26 +41,49 @@ bool risblank(Rune r); /* Buffer management functions *****************************************************************************/ +typedef struct Log { + struct Log* next; + bool locked; + bool insert; + union { + struct { + size_t beg; + size_t end; + } ins; + struct { + size_t off; + size_t len; + Rune* runes; + } del; + } data; +} Log; + typedef struct buf { - char* path; /* the path to the open file */ - int charset; /* the character set of the buffer */ - int crlf; /* tracks whether the file uses dos style line endings */ - bool insert_mode; /* tracks current mode */ - bool modified; /* tracks whether the buffer has been modified */ - size_t bufsize; /* size of the buffer in runes */ - Rune* bufstart; /* start of the data buffer */ - Rune* bufend; /* end of the data buffer */ - Rune* gapstart; /* start of the gap */ - Rune* gapend; /* end of the gap */ + char* path; /* the path to the open file */ + int charset; /* the character set of the buffer */ + int crlf; /* tracks whether the file uses dos style line endings */ + bool locked; /* tracks current mode */ + bool modified; /* tracks whether the buffer has been modified */ + size_t bufsize; /* size of the buffer in runes */ + Rune* bufstart; /* start of the data buffer */ + Rune* bufend; /* end of the data buffer */ + Rune* gapstart; /* start of the gap */ + Rune* gapend; /* end of the gap */ + Log* undo; /* undo list */ + Log* redo; /* redo list */ } Buf; void buf_load(Buf* buf, char* path); void buf_save(Buf* buf); void buf_init(Buf* buf); -void buf_clr(Buf* buf); -void buf_del(Buf* buf, unsigned pos); void buf_ins(Buf* buf, unsigned pos, Rune); +void buf_del(Buf* buf, unsigned pos); +unsigned buf_undo(Buf* buf, unsigned pos); +unsigned buf_redo(Buf* buf, unsigned pos); Rune buf_get(Buf* buf, unsigned pos); +void buf_setlocked(Buf* buf, bool locked); +bool buf_locked(Buf* buf); + bool buf_iseol(Buf* buf, unsigned pos); unsigned buf_bol(Buf* buf, unsigned pos); unsigned buf_eol(Buf* buf, unsigned pos); diff --git a/keyboard.c b/keyboard.c index 3012a04..59df3a2 100644 --- a/keyboard.c +++ b/keyboard.c @@ -36,16 +36,16 @@ static void cursor_eol(void) { static void insert_before(void) { SelEnd = SelBeg; - Buffer.insert_mode = true; + buf_setlocked(&Buffer,false); } static void insert_after(void) { SelBeg = ++SelEnd; - Buffer.insert_mode = true; + buf_setlocked(&Buffer,false); } static void exit_insert(void) { - Buffer.insert_mode = false; + buf_setlocked(&Buffer,true); } static void write(void) { @@ -65,19 +65,19 @@ static void quit(void) { static void dot_delete(void) { if (SelEnd == buf_end(&Buffer)) return; size_t n = SelEnd - SelBeg; - bool insert = Buffer.insert_mode; - if (!insert || !n) n++; - Buffer.insert_mode = true; + bool locked = buf_locked(&Buffer); + if (locked || !n) n++; + buf_setlocked(&Buffer,false); for (size_t i = 0; i < n; i++) buf_del(&Buffer, SelBeg); SelEnd = SelBeg; TargetCol = buf_getcol(&Buffer, SelEnd); - Buffer.insert_mode = insert; + buf_setlocked(&Buffer, locked); } static void dot_change(void) { dot_delete(); - Buffer.insert_mode = true; + buf_setlocked(&Buffer,false); } static void dot_backspace(void) { @@ -88,7 +88,7 @@ static void dot_backspace(void) { } static void insert(Rune r) { - if (!Buffer.insert_mode) return; + if (buf_locked(&Buffer)) return; buf_ins(&Buffer, SelEnd++, r); SelBeg = SelEnd; TargetCol = buf_getcol(&Buffer, SelEnd); @@ -116,6 +116,18 @@ static void cursor_prevbigword(void) { /*****************************************************************************/ +static void undo(void) { + SelBeg = SelEnd = buf_undo(&Buffer, SelEnd); + TargetCol = buf_getcol(&Buffer, SelEnd); +} + +static void redo(void) { + SelBeg = SelEnd = buf_redo(&Buffer, SelEnd); + TargetCol = buf_getcol(&Buffer, SelEnd); +} + +/*****************************************************************************/ + typedef struct { Rune key; void (*action)(void); @@ -156,8 +168,8 @@ static KeyBinding_T Normal[] = { { 'B', cursor_prevbigword }, /* undo/redo handling */ - //{ 'u', undo }, - //{ 'r', redo }, + { 'u', undo }, + { 'r', redo }, /* insert mode handling */ { 'a', insert_after }, @@ -216,8 +228,8 @@ void handle_key(Rune key) { if (key == '\r') key = '\n'; if (key == '\n' && Buffer.crlf) key = RUNE_CRLF; /* handle the key */ - if (Buffer.insert_mode) - process_table(Insert, key); - else + if (buf_locked(&Buffer)) process_table(Normal, key); + else + process_table(Insert, key); } diff --git a/mouse.c b/mouse.c index d8ef254..f623917 100644 --- a/mouse.c +++ b/mouse.c @@ -29,19 +29,19 @@ void selection(MouseEvent* mevnt) { } else if (risword(r)) { SelBeg = buf_bow(&Buffer, SelEnd); SelEnd = buf_eow(&Buffer, SelEnd); - if (Buffer.insert_mode) SelEnd++; + if (!buf_locked(&Buffer)) SelEnd++; } else if (r == '(' || r == ')') { SelBeg = buf_lscan(&Buffer, SelEnd, '('); SelEnd = buf_rscan(&Buffer, SelEnd, ')'); - if (Buffer.insert_mode) SelEnd++; + if (!buf_locked(&Buffer)) SelEnd++; } else if (r == '[' || r == ']') { SelBeg = buf_lscan(&Buffer, SelEnd, '['); SelEnd = buf_rscan(&Buffer, SelEnd, ']'); - if (Buffer.insert_mode) SelEnd++; + if (!buf_locked(&Buffer)) SelEnd++; } else if (r == '{' || r == '}') { SelBeg = buf_lscan(&Buffer, SelEnd, '{'); SelEnd = buf_rscan(&Buffer, SelEnd, '}'); - if (Buffer.insert_mode) SelEnd++; + if (!buf_locked(&Buffer)) SelEnd++; } else { bigword(mevnt); } diff --git a/tests/buf.c b/tests/buf.c index fac28c2..ccd1f1c 100644 --- a/tests/buf.c +++ b/tests/buf.c @@ -3,12 +3,17 @@ static Buf TestBuf; +static void buf_clr(Buf* buf) { + free(buf->bufstart); + buf_init(buf); +} + static void set_buffer_text(char* str) { int i = 0; buf_clr(&TestBuf); for (Rune* curr = TestBuf.bufstart; curr < TestBuf.bufend; curr++) *curr = '-'; - TestBuf.insert_mode = true; + TestBuf.locked = false; while (*str) buf_ins(&TestBuf, i++, (Rune)*str++); } diff --git a/xedit.c b/xedit.c index 2d2f3df..4a9755e 100644 --- a/xedit.c +++ b/xedit.c @@ -377,11 +377,11 @@ static void draw_cursor(unsigned csrx, unsigned csry) { unsigned rwidth; UGlyph* csrrune = screen_getglyph(csry, csrx, &rwidth); csrrune->attr = (CLR_BASE3 << 8 | CLR_BASE03); - if (Buffer.insert_mode) { - XftDrawRect(X.xft, clr(CLR_BASE3), csrx * Fonts.base.width, (csry+1) * Fonts.base.height, 1, Fonts.base.height); - } else { + if (buf_locked(&Buffer)) { XftDrawRect(X.xft, clr(CLR_BASE3), csrx * Fonts.base.width, (csry+1) * Fonts.base.height, rwidth * Fonts.base.width, Fonts.base.height); draw_glyphs(csrx * Fonts.base.width, (csry+2) * Fonts.base.height, csrrune, 1, rwidth); + } else { + XftDrawRect(X.xft, clr(CLR_BASE3), csrx * Fonts.base.width, (csry+1) * Fonts.base.height, 1, Fonts.base.height); } } -- 2.49.0