]> git.mdlowis.com Git - projs/tide.git/commitdiff
minor reorginization in buf.c and view.c
authorMichael D. Lowis <mike@mdlowis.com>
Thu, 1 Jun 2017 01:07:47 +0000 (21:07 -0400)
committerMichael D. Lowis <mike@mdlowis.com>
Thu, 1 Jun 2017 01:07:47 +0000 (21:07 -0400)
TODO.md
inc/edit.h
lib/buf.c
lib/view.c

diff --git a/TODO.md b/TODO.md
index 249a880d6d4acae2d9e0136f29ee3b81bf63227a..44963862541c5c73f2fa772c7979242017c7c237 100644 (file)
--- a/TODO.md
+++ b/TODO.md
@@ -7,7 +7,7 @@ Up Next:
 * Add a way to CD using a builtin (buffers will track original dir)
 * shortcut to jump to previous edit
 * xrandr dpi setting does not affect tide like it should
-
+* add command line flags to toggle options (Tabs, Indent, etc..)
 
 The Future:
 
@@ -22,7 +22,6 @@ The Future:
 * tab inserts dont coalesce like one would expect
 * Run commands in the background and don't block the main thread.
 * shortcut to repeat previous operation
-* add command line flags to toggle options (Tabs, Indent, etc..)
 * add command env vars to set options (Tabs, Indent, etc..)
 * implement command diffing logic to optimize the undo/redo log
 * Status line should omit characters from beginning of path to make file path fit
index d5497d31982476a9e3bca68437732869cf1548ae..65e2f2b1435746336db71f2cdc063b89205b16e8 100644 (file)
@@ -68,36 +68,36 @@ typedef struct {
 } Sel;
 
 void buf_init(Buf* buf, void (*errfn)(char*));
+size_t buf_load(Buf* buf, char* path);
 void buf_reload(Buf* buf);
-unsigned buf_load(Buf* buf, char* path);
 void buf_save(Buf* buf);
-Rune buf_get(Buf* buf, unsigned pos);
-unsigned buf_end(Buf* buf);
-unsigned buf_insert(Buf* buf, bool indent, unsigned off, Rune rune);
-unsigned buf_delete(Buf* buf, unsigned beg, unsigned end);
-unsigned buf_change(Buf* buf, unsigned beg, unsigned end);
+Rune buf_get(Buf* buf, size_t pos);
+size_t buf_end(Buf* buf);
+size_t buf_insert(Buf* buf, bool indent, size_t off, Rune rune);
+size_t buf_delete(Buf* buf, size_t beg, size_t end);
+size_t buf_change(Buf* buf, size_t beg, size_t end);
 void buf_undo(Buf* buf, Sel* sel);
 void buf_redo(Buf* buf, Sel* sel);
 void buf_loglock(Buf* buf);
 void buf_logclear(Buf* buf);
-bool buf_iseol(Buf* buf, unsigned pos);
-unsigned buf_bol(Buf* buf, unsigned pos);
-unsigned buf_eol(Buf* buf, unsigned pos);
-unsigned buf_bow(Buf* buf, unsigned pos);
-unsigned buf_eow(Buf* buf, unsigned pos);
-unsigned buf_lscan(Buf* buf, unsigned pos, Rune r);
-unsigned buf_rscan(Buf* buf, unsigned pos, Rune r);
+bool buf_iseol(Buf* buf, size_t pos);
+size_t buf_bol(Buf* buf, size_t pos);
+size_t buf_eol(Buf* buf, size_t pos);
+size_t buf_bow(Buf* buf, size_t pos);
+size_t buf_eow(Buf* buf, size_t pos);
+size_t buf_lscan(Buf* buf, size_t pos, Rune r);
+size_t buf_rscan(Buf* buf, size_t pos, Rune r);
 void buf_getword(Buf* buf, bool (*isword)(Rune), Sel* sel);
 void buf_getblock(Buf* buf, Rune beg, Rune end, Sel* sel);
-unsigned buf_byrune(Buf* buf, unsigned pos, int count);
-unsigned buf_byword(Buf* buf, unsigned pos, int count);
-unsigned buf_byline(Buf* buf, unsigned pos, int count);
+size_t buf_byrune(Buf* buf, size_t pos, int count);
+size_t buf_byword(Buf* buf, size_t pos, int count);
+size_t buf_byline(Buf* buf, size_t pos, int count);
 void buf_find(Buf* buf, int dir, size_t* beg, size_t* end);
 void buf_findstr(Buf* buf, int dir, char* str, size_t* beg, size_t* end);
-unsigned buf_setln(Buf* buf, unsigned line);
-unsigned buf_getcol(Buf* buf, unsigned pos);
-unsigned buf_setcol(Buf* buf, unsigned pos, unsigned col);
 void buf_lastins(Buf* buf, size_t* beg, size_t* end);
+size_t buf_setln(Buf* buf, size_t line);
+size_t buf_getcol(Buf* buf, size_t pos);
+size_t buf_setcol(Buf* buf, size_t pos, size_t col);
 
 /* Charset Handling
  *****************************************************************************/
@@ -126,7 +126,7 @@ typedef struct {
 } UGlyph;
 
 typedef struct {
-    size_t off;    /* offset of the first rune in the row */
+    size_t off;     /* offset of the first rune in the row */
     size_t rlen;   /* number of runes displayed in the row */
     size_t len;    /* number of screen columns taken up by row */
     UGlyph cols[]; /* row data */
index b8dca1dbc19d1e803249e93e7eda770f81337d2e..35737b06573de5661efb68faf38e56f2b8b9170f 100644 (file)
--- a/lib/buf.c
+++ b/lib/buf.c
 #include <unistd.h>
 #include <config.h>
 
+size_t buf_setln(Buf* buf, size_t line);
+size_t buf_getcol(Buf* buf, size_t pos);
+size_t buf_setcol(Buf* buf, size_t pos, size_t col);
 static void buf_resize(Buf* buf, size_t sz);
-
-static void log_clear(Log** list) {
-    while (*list) {
-        Log* deadite = *list;
-        *list = (*list)->next;
-        if (!deadite->insert)
-            free(deadite->data.del.runes);
-        free(deadite);
-    }
-}
-
-static void log_insert(Buf* buf, Log** list, unsigned beg, unsigned end) {
-    Log* log = *list;
-    bool locked = (!log || log->transid != buf->transid);
-    if (locked || !log->insert || (end != log->data.ins.end+1)) {
-        buf_loglock(buf);
-        Log* newlog  = (Log*)calloc(sizeof(Log), 1);
-        newlog->transid = buf->transid;
-        newlog->insert = true;
-        newlog->data.ins.beg = beg;
-        newlog->data.ins.end = end;
-        newlog->next = *list;
-        *list = newlog;
-    } else if (beg < log->data.ins.beg) {
-        log->data.ins.beg--;
-    } else {
-        log->data.ins.end++;
-    }
-}
-
-static void log_delete(Buf* buf, Log** list, unsigned off, Rune* r, size_t len) {
-    Log* log = *list;
-    bool locked = (!log || log->transid != buf->transid);
-    if (locked || log->insert || ((off != log->data.del.off) && (off+1 != log->data.del.off))) {
-        buf_loglock(buf);
-        Log* newlog = (Log*)calloc(sizeof(Log), 1);
-        newlog->transid = buf->transid;
-        newlog->insert = false;
-        newlog->data.del.off = off;
-        newlog->data.del.len = len;
-        newlog->data.del.runes = (Rune*)malloc(sizeof(Rune) * len);
-        for (size_t i = 0; i < len; i++)
-            newlog->data.del.runes[i] = r[i];
-        newlog->next = *list;
-        *list = newlog;
-    } else if (off == log->data.del.off) {
-        log->data.del.len++;
-        log->data.del.runes = (Rune*)realloc(log->data.del.runes, sizeof(Rune) * log->data.del.len);
-        log->data.del.runes[log->data.del.len-1] = *r;
-    } else {
-        size_t bytes = sizeof(Rune) * log->data.del.len;
-        log->data.del.len++;
-        log->data.del.off--;
-        log->data.del.runes = (Rune*)realloc(log->data.del.runes, bytes + sizeof(Rune));
-        memmove(log->data.del.runes+1, log->data.del.runes, bytes);
-        log->data.del.runes[0] = *r;
-    }
-}
-
-static void syncgap(Buf* buf, unsigned off) {
-    assert(off <= buf_end(buf));
-    /* If the buffer is full, resize it before syncing */
-    if (0 == (buf->gapend - buf->gapstart))
-        buf_resize(buf, buf->bufsize << 1);
-    /* Move the gap to the desired offset */
-    Rune* newpos = (buf->bufstart + off);
-    if (newpos < buf->gapstart) {
-        while (newpos < buf->gapstart)
-            *(--buf->gapend) = *(--buf->gapstart);
-    } else {
-        while (newpos > buf->gapstart)
-            *(buf->gapstart++) = *(buf->gapend++);
-    }
-}
-
-static void buf_resize(Buf* buf, size_t sz) {
-    /* allocate the new buffer and gap */
-    Buf copy = *buf;
-    copy.bufsize  = sz;
-    copy.bufstart = (Rune*)malloc(copy.bufsize * sizeof(Rune));
-    copy.bufend   = copy.bufstart + copy.bufsize;
-    copy.gapstart = copy.bufstart;
-    copy.gapend   = copy.bufend;
-    /* copy the data from the old buffer to the new one */
-    for (Rune* curr = buf->bufstart; curr < buf->gapstart; curr++)
-        *(copy.gapstart++) = *(curr);
-    for (Rune* curr = buf->gapend; curr < buf->bufend; curr++)
-        *(copy.gapstart++) = *(curr);
-    /* free the buffer and commit the changes */
-    free(buf->bufstart);
-    *buf = copy;
-}
-
-static void delete(Buf* buf, unsigned off) {
-    syncgap(buf, off);
-    buf->gapend++;
-}
-
-static unsigned insert(Buf* buf, unsigned off, Rune rune) {
-    unsigned rcount = 1;
-    syncgap(buf, off);
-    if (buf->crlf && rune == '\n' && buf_get(buf, off-1) == '\r') {
-        rcount = 0;
-        *(buf->gapstart-1) = RUNE_CRLF;
-    } else if (buf->crlf && rune == '\n') {
-        *(buf->gapstart++) = RUNE_CRLF;
-    } else {
-        *(buf->gapstart++) = rune;
-    }
-    return rcount;
-}
-
-static int range_match(Buf* buf, unsigned dbeg, unsigned dend, unsigned mbeg, unsigned mend) {
-    unsigned n1 = dend-dbeg, n2 = mend-mbeg;
-    if (n1 != n2) return n1-n2;
-    for (; n1 > 0; n1--, dbeg++, mbeg++) {
-        int cmp = buf_get(buf, dbeg) - buf_get(buf, mbeg);
-        if (cmp != 0) return cmp;
-    }
-    return 0;
-}
-
-static int rune_match(Buf* buf, unsigned mbeg, unsigned mend, Rune* runes) {
-    for (; *runes; runes++, mbeg++) {
-        int cmp = *runes - buf_get(buf, mbeg);
-        if (cmp != 0) return cmp;
-    }
-    return 0;
-}
-
-static void swaplog(Buf* buf, Log** from, Log** to, Sel* sel) {
-    /* pop the last action */
-    Log* log = *from;
-    if (!log) return;
-    *from = log->next;
-    /* invert the log type and move it to the destination */
-    Log* newlog = (Log*)calloc(sizeof(Log), 1);
-    newlog->transid = log->transid;
-    if (log->insert) {
-        sel->beg = sel->end = log->data.ins.beg;
-        newlog->insert = false;
-        size_t n = (log->data.ins.end - log->data.ins.beg);
-        newlog->data.del.off   = log->data.ins.beg;
-        newlog->data.del.len   = n;
-        newlog->data.del.runes = (Rune*)malloc(n * sizeof(Rune));
-        for (size_t i = 0; i < n; i++) {
-            newlog->data.del.runes[i] = buf_get(buf, log->data.ins.beg);
-            delete(buf, log->data.ins.beg);
-        }
-    } else {
-        newlog->insert = true;
-        sel->beg = newlog->data.ins.beg = log->data.del.off;
-        newlog->data.ins.end = newlog->data.ins.beg;
-        for (size_t i = log->data.del.len; i > 0; i--) {
-            newlog->data.ins.end += insert(buf, newlog->data.ins.beg, log->data.del.runes[i-1]);
-        }
-        sel->end = newlog->data.ins.end;
-    }
-    newlog->next = *to;
-    *to = newlog;
-}
-
-/*****************************************************************************/
+static void log_clear(Log** list);
+static void log_insert(Buf* buf, Log** list, size_t beg, size_t end);
+static void log_delete(Buf* buf, Log** list, size_t off, Rune* r, size_t len);
+static void syncgap(Buf* buf, size_t off);
+static void buf_resize(Buf* buf, size_t sz);
+static void delete(Buf* buf, size_t off);
+static size_t insert(Buf* buf, size_t off, Rune rune);
+static int range_match(Buf* buf, size_t dbeg, size_t dend, size_t mbeg, size_t mend);
+static int rune_match(Buf* buf, size_t mbeg, size_t mend, Rune* runes);
+static void swaplog(Buf* buf, Log** from, Log** to, Sel* sel);
+static size_t next_size(size_t curr);
+static Rune nextrune(Buf* buf, size_t off, int move, bool (*testfn)(Rune));
 
 void buf_init(Buf* buf, void (*errfn)(char*)) {
     /* cleanup old data if there is any */
@@ -196,18 +48,11 @@ void buf_init(Buf* buf, void (*errfn)(char*)) {
     assert(buf->bufstart);
 }
 
-static size_t next_size(size_t curr) {
-    int size = 1;
-    while(size < curr)
-        size = (size << 1);
-    return size;
-}
-
-unsigned buf_load(Buf* buf, char* path) {
+size_t buf_load(Buf* buf, char* path) {
     /* process the file path and address */
     if (path && path[0] == '.' && path[1] == '/')
         path += 2;
-    unsigned off = 0;
+    size_t off = 0;
     buf->path = stringdup(path);
     char* addr = strrchr(buf->path, ':');
     if (addr) *addr = '\0', addr++;
@@ -259,7 +104,7 @@ void buf_save(Buf* buf) {
     if (buf->path) {
         FMap file = mmap_readwrite(buf->path, buf_end(buf) * UTF_MAX);
         if (file.buf) {
-            for (unsigned i = 0, end = buf_end(buf); i < end; i++) {
+            for (size_t i = 0, end = buf_end(buf); i < end; i++) {
                 Rune r = buf_get(buf, i);
                 if (r == RUNE_CRLF) {
                     file.buf[wrlen++] = '\r';
@@ -282,9 +127,7 @@ void buf_save(Buf* buf) {
     }
 }
 
-/*****************************************************************************/
-
-Rune buf_get(Buf* buf, unsigned off) {
+Rune buf_get(Buf* buf, size_t off) {
     if (off >= buf_end(buf)) return (Rune)'\n';
     size_t bsz = (buf->gapstart - buf->bufstart);
     if (off < bsz)
@@ -293,15 +136,13 @@ Rune buf_get(Buf* buf, unsigned off) {
         return *(buf->gapend + (off - bsz));
 }
 
-unsigned buf_end(Buf* buf) {
+size_t buf_end(Buf* buf) {
     size_t bufsz = buf->bufend - buf->bufstart;
     size_t gapsz = buf->gapend - buf->gapstart;
     return (bufsz - gapsz);
 }
 
-/*****************************************************************************/
-
-unsigned buf_insert(Buf* buf, bool fmt, unsigned off, Rune rune) {
+size_t buf_insert(Buf* buf, bool fmt, size_t off, Rune rune) {
     buf->modified = true;
     if (fmt && buf->expand_tabs && rune == '\t') {
         size_t n = (TabWidth - ((off - buf_bol(buf, off)) % TabWidth));
@@ -315,7 +156,7 @@ unsigned buf_insert(Buf* buf, bool fmt, unsigned off, Rune rune) {
         }
     }
     if (fmt && buf->copy_indent && (rune == '\n' || rune == RUNE_CRLF)) {
-        unsigned beg = buf_bol(buf, off-1), end = beg;
+        size_t beg = buf_bol(buf, off-1), end = beg;
         for (; end < buf_end(buf) && (' ' == buf_get(buf, end) || '\t' == buf_get(buf, end)); end++);
         for (; beg < end; beg++)
             off = buf_insert(buf, true, off, buf_get(buf, beg));
@@ -324,10 +165,10 @@ unsigned buf_insert(Buf* buf, bool fmt, unsigned off, Rune rune) {
     return off;
 }
 
-unsigned buf_delete(Buf* buf, unsigned beg, unsigned end) {
+size_t buf_delete(Buf* buf, size_t beg, size_t end) {
     buf->modified = true;
     log_clear(&(buf->redo));
-    for (unsigned i = end-beg; i > 0; i--) {
+    for (size_t i = end-beg; i > 0; i--) {
         Rune r = buf_get(buf, beg);
         log_delete(buf, &(buf->undo), beg, &r, 1);
         delete(buf, beg);
@@ -335,9 +176,9 @@ unsigned buf_delete(Buf* buf, unsigned beg, unsigned end) {
     return beg;
 }
 
-unsigned buf_change(Buf* buf, unsigned beg, unsigned end) {
+size_t buf_change(Buf* buf, size_t beg, size_t end) {
     /* delete the range first */
-    unsigned off = buf_delete(buf, beg, end);
+    size_t off = buf_delete(buf, beg, end);
     /* now create a new insert item of length 0 witht he same transaction id as
        the delete. This will cause subsequent inserts to be coalesced into the
        same transaction */
@@ -352,8 +193,6 @@ unsigned buf_change(Buf* buf, unsigned beg, unsigned end) {
     return off;
 }
 
-/*****************************************************************************/
-
 void buf_undo(Buf* buf, Sel* sel) {
     if (!buf->undo) return;
     uint transid = buf->undo->transid;
@@ -379,42 +218,40 @@ void buf_logclear(Buf* buf) {
     log_clear(&(buf->undo));
 }
 
-/*****************************************************************************/
-
-bool buf_iseol(Buf* buf, unsigned off) {
+bool buf_iseol(Buf* buf, size_t off) {
     Rune r = buf_get(buf, off);
     return (r == '\n' || r == RUNE_CRLF);
 }
 
-unsigned buf_bol(Buf* buf, unsigned off) {
+size_t buf_bol(Buf* buf, size_t off) {
     for (; !buf_iseol(buf, off-1); off--);
     return off;
 }
 
-unsigned buf_eol(Buf* buf, unsigned off) {
+size_t buf_eol(Buf* buf, size_t off) {
     for (; !buf_iseol(buf, off); off++);
     return off;
 }
 
-unsigned buf_bow(Buf* buf, unsigned off) {
+size_t buf_bow(Buf* buf, size_t off) {
     for (; risword(buf_get(buf, off-1)); off--);
     return off;
 }
 
-unsigned buf_eow(Buf* buf, unsigned off) {
+size_t buf_eow(Buf* buf, size_t off) {
     for (; risword(buf_get(buf, off)); off++);
     return off-1;
 }
 
-unsigned buf_lscan(Buf* buf, unsigned pos, Rune r) {
-    unsigned off = pos;
+size_t buf_lscan(Buf* buf, size_t pos, Rune r) {
+    size_t off = pos;
     for (; (off > 0) && (r != buf_get(buf, off)); off--);
     return (buf_get(buf, off) == r ? off : pos);
 }
 
-unsigned buf_rscan(Buf* buf, unsigned pos, Rune r) {
-    unsigned off = pos;
-    unsigned end = buf_end(buf);
+size_t buf_rscan(Buf* buf, size_t pos, Rune r) {
+    size_t off = pos;
+    size_t end = buf_end(buf);
     for (; (off < end) && (r != buf_get(buf, off)); off++);
     return (buf_get(buf, off) == r ? off : pos);
 }
@@ -427,7 +264,7 @@ void buf_getword(Buf* buf, bool (*isword)(Rune), Sel* sel) {
 
 void buf_getblock(Buf* buf, Rune first, Rune last, Sel* sel) {
     int balance = 0, dir;
-    unsigned beg = sel->end, end = sel->end, off;
+    size_t beg = sel->end, end = sel->end, off;
 
     /* figure out which end of the block we're starting at */
     if (buf_get(buf, end) == first)
@@ -458,9 +295,7 @@ void buf_getblock(Buf* buf, Rune first, Rune last, Sel* sel) {
     sel->beg = beg, sel->end = end;
 }
 
-/*****************************************************************************/
-
-unsigned buf_byrune(Buf* buf, unsigned pos, int count) {
+size_t buf_byrune(Buf* buf, size_t pos, int count) {
     int move = (count < 0 ? -1 : 1);
     count *= move; // remove the sign if there is one
     for (; count > 0; count--)
@@ -472,17 +307,7 @@ unsigned buf_byrune(Buf* buf, unsigned pos, int count) {
     return pos;
 }
 
-static Rune nextrune(Buf* buf, unsigned off, int move, bool (*testfn)(Rune)) {
-    bool ret = false;
-    unsigned end = buf_end(buf);
-    if (move < 0 && off > 0)
-        ret = testfn(buf_get(buf, off-1));
-    else if (move > 0 && off < end)
-        ret = testfn(buf_get(buf, off+1));
-    return ret;
-}
-
-unsigned buf_byword(Buf* buf, unsigned off, int count) {
+size_t buf_byword(Buf* buf, size_t off, int count) {
     int move = (count < 0 ? -1 : 1);
 
     while (nextrune(buf, off, move, risblank))
@@ -500,7 +325,7 @@ unsigned buf_byword(Buf* buf, unsigned off, int count) {
     return off;
 }
 
-unsigned buf_byline(Buf* buf, unsigned pos, int count) {
+size_t buf_byline(Buf* buf, size_t pos, int count) {
     int move = (count < 0 ? -1 : 1);
     count *= move; // remove the sign if there is one
     for (; count > 0; count--) {
@@ -508,7 +333,7 @@ unsigned buf_byline(Buf* buf, unsigned pos, int count) {
             if (pos > buf_eol(buf, 0))
                 pos = buf_bol(buf, buf_bol(buf, pos)-1);
         } else {
-            unsigned next = buf_eol(buf, pos)+1;
+            size_t next = buf_eol(buf, pos)+1;
             if (next < buf_end(buf))
                 pos = next;
         }
@@ -516,12 +341,10 @@ unsigned buf_byline(Buf* buf, unsigned pos, int count) {
     return pos;
 }
 
-/*****************************************************************************/
-
 void buf_find(Buf* buf, int dir, size_t* beg, size_t* end) {
-    unsigned dbeg = *beg, dend = *end;
-    unsigned mbeg = dbeg+dir, mend = dend+dir;
-    unsigned mlen = dend - dbeg;
+    size_t dbeg = *beg, dend = *end;
+    size_t mbeg = dbeg+dir, mend = dend+dir;
+    size_t mlen = dend - dbeg;
     while (true) {
         if ((buf_get(buf, mbeg)   == buf_get(buf, dbeg)) &&
             (buf_get(buf, mend-1) == buf_get(buf, dend-1)) &&
@@ -541,7 +364,7 @@ void buf_findstr(Buf* buf, int dir, char* str, size_t* beg, size_t* end) {
     if (!str) return;
     Rune* runes = charstorunes(str);
     size_t rlen = rstrlen(runes);
-    unsigned start = *beg, mbeg = start+dir, mend = mbeg + rlen;
+    size_t start = *beg, mbeg = start+dir, mend = mbeg + rlen;
     while (mbeg != start) {
         if ((buf_get(buf, mbeg) == runes[0]) &&
             (buf_get(buf, mend-1) == runes[rlen-1]) &&
@@ -558,28 +381,51 @@ void buf_findstr(Buf* buf, int dir, char* str, size_t* beg, size_t* end) {
     free(runes);
 }
 
+void buf_lastins(Buf* buf, size_t* beg, size_t* end) {
+    Log* log = buf->undo;
+    size_t opbeg = *end, opend = *end;
+    if (log && log->insert)
+        opbeg = log->data.ins.end, opend = log->data.ins.end;
+
+    size_t delsize = 0;
+    for (; log; log = log->next) {
+        if (log->insert) {
+            size_t ibeg = log->data.ins.beg,
+                     iend = log->data.ins.end - delsize;
+            if (iend < ibeg || ibeg > opbeg || iend < opbeg) break;
+            if (ibeg < opbeg && iend > opend) break;
+            opbeg = ibeg, delsize = 0;
+        } else {
+            /* bail if the delete doesnt overlap */
+            if(log->data.del.off != opbeg) break;
+            delsize = log->data.del.len;
+        }
+    }
+    *beg = opbeg, *end = opend;
+}
+
 /*****************************************************************************/
 
-unsigned buf_setln(Buf* buf, unsigned line) {
-    unsigned off = 0;
+size_t buf_setln(Buf* buf, size_t line) {
+    size_t off = 0;
     while (line > 1 && off < buf_end(buf))
         line--, off = buf_byline(buf, off, DOWN);
     return off;
 }
 
-unsigned buf_getcol(Buf* buf, unsigned pos) {
-    unsigned curr = buf_bol(buf, pos);
-    unsigned col = 0;
+size_t buf_getcol(Buf* buf, size_t pos) {
+    size_t curr = buf_bol(buf, pos);
+    size_t col = 0;
     for (; curr < pos; curr = buf_byrune(buf, curr, 1))
         col += runewidth(col, buf_get(buf, curr));
     return col;
 }
 
-unsigned buf_setcol(Buf* buf, unsigned pos, unsigned col) {
-    unsigned bol  = buf_bol(buf, pos);
-    unsigned curr = bol;
-    unsigned len  = 0;
-    unsigned i    = 0;
+size_t buf_setcol(Buf* buf, size_t pos, size_t col) {
+    size_t bol  = buf_bol(buf, pos);
+    size_t curr = bol;
+    size_t len  = 0;
+    size_t i    = 0;
     /* determine the length of the line in columns */
     for (; !buf_iseol(buf, curr); curr++)
         len += runewidth(len, buf_get(buf, curr));
@@ -595,25 +441,180 @@ unsigned buf_setcol(Buf* buf, unsigned pos, unsigned col) {
     return curr;
 }
 
-void buf_lastins(Buf* buf, size_t* beg, size_t* end) {
-    Log* log = buf->undo;
-    unsigned opbeg = *end, opend = *end;
-    if (log && log->insert)
-        opbeg = log->data.ins.end, opend = log->data.ins.end;
+static void log_clear(Log** list) {
+    while (*list) {
+        Log* deadite = *list;
+        *list = (*list)->next;
+        if (!deadite->insert)
+            free(deadite->data.del.runes);
+        free(deadite);
+    }
+}
 
-    unsigned delsize = 0;
-    for (; log; log = log->next) {
-        if (log->insert) {
-            unsigned ibeg = log->data.ins.beg,
-                     iend = log->data.ins.end - delsize;
-            if (iend < ibeg || ibeg > opbeg || iend < opbeg) break;
-            if (ibeg < opbeg && iend > opend) break;
-            opbeg = ibeg, delsize = 0;
-        } else {
-            /* bail if the delete doesnt overlap */
-            if(log->data.del.off != opbeg) break;
-            delsize = log->data.del.len;
+static void log_insert(Buf* buf, Log** list, size_t beg, size_t end) {
+    Log* log = *list;
+    bool locked = (!log || log->transid != buf->transid);
+    if (locked || !log->insert || (end != log->data.ins.end+1)) {
+        buf_loglock(buf);
+        Log* newlog  = (Log*)calloc(sizeof(Log), 1);
+        newlog->transid = buf->transid;
+        newlog->insert = true;
+        newlog->data.ins.beg = beg;
+        newlog->data.ins.end = end;
+        newlog->next = *list;
+        *list = newlog;
+    } else if (beg < log->data.ins.beg) {
+        log->data.ins.beg--;
+    } else {
+        log->data.ins.end++;
+    }
+}
+
+static void log_delete(Buf* buf, Log** list, size_t off, Rune* r, size_t len) {
+    Log* log = *list;
+    bool locked = (!log || log->transid != buf->transid);
+    if (locked || log->insert || ((off != log->data.del.off) && (off+1 != log->data.del.off))) {
+        buf_loglock(buf);
+        Log* newlog = (Log*)calloc(sizeof(Log), 1);
+        newlog->transid = buf->transid;
+        newlog->insert = false;
+        newlog->data.del.off = off;
+        newlog->data.del.len = len;
+        newlog->data.del.runes = (Rune*)malloc(sizeof(Rune) * len);
+        for (size_t i = 0; i < len; i++)
+            newlog->data.del.runes[i] = r[i];
+        newlog->next = *list;
+        *list = newlog;
+    } else if (off == log->data.del.off) {
+        log->data.del.len++;
+        log->data.del.runes = (Rune*)realloc(log->data.del.runes, sizeof(Rune) * log->data.del.len);
+        log->data.del.runes[log->data.del.len-1] = *r;
+    } else {
+        size_t bytes = sizeof(Rune) * log->data.del.len;
+        log->data.del.len++;
+        log->data.del.off--;
+        log->data.del.runes = (Rune*)realloc(log->data.del.runes, bytes + sizeof(Rune));
+        memmove(log->data.del.runes+1, log->data.del.runes, bytes);
+        log->data.del.runes[0] = *r;
+    }
+}
+
+static void syncgap(Buf* buf, size_t off) {
+    assert(off <= buf_end(buf));
+    /* If the buffer is full, resize it before syncing */
+    if (0 == (buf->gapend - buf->gapstart))
+        buf_resize(buf, buf->bufsize << 1);
+    /* Move the gap to the desired offset */
+    Rune* newpos = (buf->bufstart + off);
+    if (newpos < buf->gapstart) {
+        while (newpos < buf->gapstart)
+            *(--buf->gapend) = *(--buf->gapstart);
+    } else {
+        while (newpos > buf->gapstart)
+            *(buf->gapstart++) = *(buf->gapend++);
+    }
+}
+
+static void buf_resize(Buf* buf, size_t sz) {
+    /* allocate the new buffer and gap */
+    Buf copy = *buf;
+    copy.bufsize  = sz;
+    copy.bufstart = (Rune*)malloc(copy.bufsize * sizeof(Rune));
+    copy.bufend   = copy.bufstart + copy.bufsize;
+    copy.gapstart = copy.bufstart;
+    copy.gapend   = copy.bufend;
+    /* copy the data from the old buffer to the new one */
+    for (Rune* curr = buf->bufstart; curr < buf->gapstart; curr++)
+        *(copy.gapstart++) = *(curr);
+    for (Rune* curr = buf->gapend; curr < buf->bufend; curr++)
+        *(copy.gapstart++) = *(curr);
+    /* free the buffer and commit the changes */
+    free(buf->bufstart);
+    *buf = copy;
+}
+
+static void delete(Buf* buf, size_t off) {
+    syncgap(buf, off);
+    buf->gapend++;
+}
+
+static size_t insert(Buf* buf, size_t off, Rune rune) {
+    size_t rcount = 1;
+    syncgap(buf, off);
+    if (buf->crlf && rune == '\n' && buf_get(buf, off-1) == '\r') {
+        rcount = 0;
+        *(buf->gapstart-1) = RUNE_CRLF;
+    } else if (buf->crlf && rune == '\n') {
+        *(buf->gapstart++) = RUNE_CRLF;
+    } else {
+        *(buf->gapstart++) = rune;
+    }
+    return rcount;
+}
+
+static int range_match(Buf* buf, size_t dbeg, size_t dend, size_t mbeg, size_t mend) {
+    size_t n1 = dend-dbeg, n2 = mend-mbeg;
+    if (n1 != n2) return n1-n2;
+    for (; n1 > 0; n1--, dbeg++, mbeg++) {
+        int cmp = buf_get(buf, dbeg) - buf_get(buf, mbeg);
+        if (cmp != 0) return cmp;
+    }
+    return 0;
+}
+
+static int rune_match(Buf* buf, size_t mbeg, size_t mend, Rune* runes) {
+    for (; *runes; runes++, mbeg++) {
+        int cmp = *runes - buf_get(buf, mbeg);
+        if (cmp != 0) return cmp;
+    }
+    return 0;
+}
+
+static void swaplog(Buf* buf, Log** from, Log** to, Sel* sel) {
+    /* pop the last action */
+    Log* log = *from;
+    if (!log) return;
+    *from = log->next;
+    /* invert the log type and move it to the destination */
+    Log* newlog = (Log*)calloc(sizeof(Log), 1);
+    newlog->transid = log->transid;
+    if (log->insert) {
+        sel->beg = sel->end = log->data.ins.beg;
+        newlog->insert = false;
+        size_t n = (log->data.ins.end - log->data.ins.beg);
+        newlog->data.del.off   = log->data.ins.beg;
+        newlog->data.del.len   = n;
+        newlog->data.del.runes = (Rune*)malloc(n * sizeof(Rune));
+        for (size_t i = 0; i < n; i++) {
+            newlog->data.del.runes[i] = buf_get(buf, log->data.ins.beg);
+            delete(buf, log->data.ins.beg);
         }
+    } else {
+        newlog->insert = true;
+        sel->beg = newlog->data.ins.beg = log->data.del.off;
+        newlog->data.ins.end = newlog->data.ins.beg;
+        for (size_t i = log->data.del.len; i > 0; i--) {
+            newlog->data.ins.end += insert(buf, newlog->data.ins.beg, log->data.del.runes[i-1]);
+        }
+        sel->end = newlog->data.ins.end;
     }
-    *beg = opbeg, *end = opend;
+    newlog->next = *to;
+    *to = newlog;
+}
+
+static size_t next_size(size_t curr) {
+    int size = 1;
+    while(size < curr)
+        size = (size << 1);
+    return size;
+}
+
+static Rune nextrune(Buf* buf, size_t off, int move, bool (*testfn)(Rune)) {
+    bool ret = false;
+    size_t end = buf_end(buf);
+    if (move < 0 && off > 0)
+        ret = testfn(buf_get(buf, off-1));
+    else if (move > 0 && off < end)
+        ret = testfn(buf_get(buf, off+1));
+    return ret;
 }
index 940dd57b80d68785318c7e146af246dd44ff1ff3..a6149048579146a9270b18b5e762044dce96a401 100644 (file)
 #include <ctype.h>
 #include <config.h>
 
-static void clearrow(View* view, size_t row) {
-    Row* scrrow = view_getrow(view, row);
-    if (!scrrow) return;
-    for (size_t i = 0; i < view->ncols; i++)
-        scrrow->cols[i].rune = (Rune)' ';
-    scrrow->rlen = 0;
-    scrrow->len  = 0;
-}
-
-static size_t setcell(View* view, size_t row, size_t col, uint32_t attr, Rune r) {
-    if (row >= view->nrows || col >= view->ncols) return 0;
-    Row* scrrow = view_getrow(view, row);
-    int ncols = runewidth(col, r);
-    /* write the rune to the screen buf */
-    scrrow->cols[col].attr = attr;
-    if (r == '\t' || r == '\n' || r == RUNE_CRLF)
-        scrrow->cols[col].rune = ' ';
-    else
-        scrrow->cols[col].rune = r;
-    /* Update lengths */
-    scrrow->rlen += 1;
-    for (int i = 1; i < ncols; i++) {
-        scrrow->cols[col].attr = attr;
-        scrrow->cols[col+i].rune = '\0';
-    }
-    if ((col + ncols) > scrrow->len)
-        scrrow->len = col + ncols;
-    return ncols;
-}
-
-static void selswap(Sel* sel) {
-    if (sel->end < sel->beg) {
-        size_t temp = sel->beg;
-        sel->beg = sel->end;
-        sel->end = temp;
-    }
-}
-
-static size_t num_selected(Sel sel) {
-    selswap(&sel);
-    return (sel.end - sel.beg);
-}
-
-static bool in_selection(Sel sel, size_t off) {
-    selswap(&sel);
-    return (sel.beg <= off && off < sel.end);
-}
-
-static bool selected(View* view, size_t pos) {
-    return in_selection(view->selection, pos);
-}
-
-static size_t fill_row(View* view, unsigned row, size_t pos) {
-    view_getrow(view, row)->off = pos;
-    clearrow(view, row);
-    for (size_t x = 0; x < view->ncols;) {
-        uint32_t attr = (selected(view, pos) ? CLR_SelectedText : CLR_NormalText);
-        Rune r = buf_get(&(view->buffer), pos++);
-        x += setcell(view, row, x, attr, r);
-        if (buf_iseol(&(view->buffer), pos-1)) break;
-    }
-    return pos;
-}
-
-static void reflow(View* view) {
-    if (!view->rows) return;
-    size_t pos = view->rows[0]->off;
-    for (size_t y = 0; y < view->nrows; y++)
-        pos = fill_row(view, y, pos);
-}
-
-static unsigned prev_screen_line(View* view, unsigned bol, unsigned off) {
-    unsigned pos = bol;
-    while (true) {
-        unsigned x;
-        for (x = 0; x < view->ncols && (pos + x) < off; x++)
-            x += runewidth(x, buf_get(&(view->buffer), pos+x));
-        if ((pos + x) >= off) break;
-        pos += x;
-    }
-    return pos;
-}
-
-static unsigned scroll_up(View* view) {
-    unsigned first = view->rows[0]->off;
-    unsigned bol    = buf_bol(&(view->buffer), first);
-    unsigned prevln = (first == bol ? buf_byline(&(view->buffer), bol, -1) : bol);
-    if (!first) return first;
-    prevln = prev_screen_line(view, prevln, first);
-    /* delete the last row and shift the others */
-    free(view->rows[view->nrows - 1]);
-    memmove(&view->rows[1], &view->rows[0], sizeof(Row*) * (view->nrows-1));
-    view->rows[0] = calloc(1, sizeof(Row) + (view->ncols * sizeof(UGlyph)));
-    view->rows[0]->off = prevln;
-    /* fill in row content */
-    fill_row(view, 0, view->rows[0]->off);
-    return view->rows[0]->off;
-}
-
-static unsigned scroll_dn(View* view) {
-    unsigned last  = view->rows[view->nrows-1]->off + view->rows[view->nrows-1]->rlen - 1;
-    if (last >= buf_end(&(view->buffer))) return last;
-    /* delete the first row and shift the others */
-    if (view->nrows > 1) {
-        free(view->rows[0]);
-        memmove(&view->rows[0], &view->rows[1], sizeof(Row*) * (view->nrows-1));
-        view->rows[view->nrows-1] = calloc(1, sizeof(Row) + (view->ncols * sizeof(UGlyph)));
-        view->rows[view->nrows-1]->off = (view->rows[view->nrows-2]->off + view->rows[view->nrows-2]->rlen);
-        /* fill in row content */
-        fill_row(view, view->nrows-1, view->rows[view->nrows-1]->off);
-    } else {
-        view->rows[0]->off += view->rows[0]->rlen;
-        fill_row(view, 0, view->rows[0]->off);
-    }
-    return view->rows[view->nrows-1]->off + view->rows[view->nrows-1]->rlen - 1;
-}
-
-static void sync_center(View* view, size_t csr) {
-    /* determine the screenline containing the cursor */
-    size_t scrln = 0;
-    for (; scrln < view->nrows; scrln++) {
-        unsigned first = view->rows[scrln]->off;
-        unsigned last  = first + view->rows[scrln]->rlen - 1;
-        if (csr >= first && csr <= last)
-            break;
-    }
-    /* find the middle row and scroll until the cursor is on that row */
-    unsigned midrow = view->nrows / 2;
-    int move = (scrln - midrow);
-    unsigned count = (move < 0 ? -move : move);
-    for (; count > 0; count--)
-        (move < 0 ? scroll_up : scroll_dn)(view);
-}
-
-void view_scrollto(View* view, size_t csr) {
-    if (!view->nrows) return;
-    unsigned first = view->rows[0]->off;
-    unsigned last  = view->rows[view->nrows-1]->off + view->rows[view->nrows-1]->rlen - 1;
-    while (csr < first)
-        first = scroll_up(view);
-    while (csr > last && last < buf_end(&(view->buffer)))
-        last = scroll_dn(view);
-    view->sync_needed = false;
-    if (view->sync_center) {
-        sync_center(view, csr);
-        view->sync_center = false;
-    }
-}
-
-static size_t getoffset(View* view, size_t row, size_t col) {
-    Row* scrrow = view_getrow(view, row);
-    if (!scrrow) return SIZE_MAX;
-    size_t pos = scrrow->off;
-    if (col >= scrrow->len) {
-        pos = (scrrow->off + scrrow->rlen - 1);
-    } else {
-        /* multi column runes are followed by \0 slots so if we clicked on a \0
-           slot, slide backwards to the real rune. */
-        for (; !scrrow->cols[col].rune && col > 0; col--);
-        /* now lets count the number of runes up to the one we clicked on */
-        for (unsigned i = 0; i < col; i++)
-            if (scrrow->cols[i].rune)
-                pos++;
-    }
-    if (pos >= buf_end(&(view->buffer)))
-        return buf_end(&(view->buffer));
-    return pos;
-}
+static void selcontext(View* view, bool (*isword)(Rune), Sel* sel);
+static void clearrow(View* view, size_t row);
+static size_t setcell(View* view, size_t row, size_t col, uint32_t attr, Rune r);
+static void selswap(Sel* sel);
+static size_t num_selected(Sel sel);
+static bool in_selection(Sel sel, size_t off);
+static bool selected(View* view, size_t pos);
+static size_t fill_row(View* view, unsigned row, size_t pos);
+static void reflow(View* view);
+static unsigned prev_screen_line(View* view, unsigned bol, unsigned off);
+static unsigned scroll_up(View* view);
+static unsigned scroll_dn(View* view);
+static void sync_center(View* view, size_t csr);
+static size_t getoffset(View* view, size_t row, size_t col);
+static bool selvisible(View* view);
+static void find_cursor(View* view, size_t* csrx, size_t* csry);
 
 void view_init(View* view, char* file, void (*errfn)(char*)) {
     if (view->nrows) {
@@ -231,25 +79,6 @@ void view_resize(View* view, size_t nrows, size_t ncols) {
     view->ncols = ncols;
 }
 
-static void find_cursor(View* view, size_t* csrx, size_t* csry) {
-    size_t csr = view->selection.end;
-    for (size_t y = 0; y < view->nrows; y++) {
-        size_t start = view->rows[y]->off;
-        size_t end   = view->rows[y]->off + view->rows[y]->rlen - 1;
-        if (start <= csr && csr <= end) {
-            size_t pos = start;
-            for (size_t x = 0; x < view->ncols;) {
-                if (pos == csr) {
-                    *csry = y, *csrx = x;
-                    break;
-                }
-                x += runewidth(x, buf_get(&(view->buffer),pos++));
-            }
-            break;
-        }
-    }
-}
-
 void view_update(View* view, size_t* csrx, size_t* csry) {
     if (!view->nrows) return;
     size_t csr = view->selection.end;
@@ -314,28 +143,6 @@ void view_selext(View* view, size_t row, size_t col) {
     }
 }
 
-static void selcontext(View* view, bool (*isword)(Rune), Sel* sel) {
-    Buf* buf = &(view->buffer);
-    size_t bol = buf_bol(buf, sel->end);
-    Rune r = buf_get(buf, sel->end);
-    if (r == '(' || r == ')') {
-        buf_getblock(buf, '(', ')', sel);
-    } else if (r == '[' || r == ']') {
-        buf_getblock(buf, '[', ']', sel);
-    } else if (r == '{' || r == '}') {
-        buf_getblock(buf, '{', '}', sel);
-    } else if (sel->end == bol || r == '\n' || r == RUNE_CRLF) {
-        sel->beg = bol;
-        sel->end = buf_eol(buf, sel->end);
-    } else if (risword(r)) {
-        buf_getword(buf, isword, sel);
-    } else {
-        buf_getword(buf, risbigword, sel);
-    }
-    sel->end = buf_byrune(&(view->buffer), sel->end, RIGHT);
-    sel->col = buf_getcol(&(view->buffer), sel->end);
-}
-
 void view_selword(View* view, size_t row, size_t col) {
     buf_loglock(&(view->buffer));
     if (row != SIZE_MAX && col != SIZE_MAX)
@@ -467,14 +274,6 @@ void view_setln(View* view, size_t line) {
     view->sync_center = true;
 }
 
-static bool selvisible(View* view) {
-    if (!view->nrows) return true;
-    unsigned beg = view->rows[0]->off;
-    unsigned end = view->rows[view->nrows-1]->off +
-                   view->rows[view->nrows-1]->rlen;
-    return (view->selection.beg >= beg && view->selection.end <= end);
-}
-
 void view_undo(View* view) {
     buf_undo(&(view->buffer), &(view->selection));
     view_jumpto(view, true, view->selection.end);
@@ -627,3 +426,221 @@ void view_indent(View* view, int dir) {
 Rune view_getrune(View* view) {
     return buf_get(&(view->buffer), view->selection.end);
 }
+
+void view_scrollto(View* view, size_t csr) {
+    if (!view->nrows) return;
+    unsigned first = view->rows[0]->off;
+    unsigned last  = view->rows[view->nrows-1]->off + view->rows[view->nrows-1]->rlen - 1;
+    while (csr < first)
+        first = scroll_up(view);
+    while (csr > last && last < buf_end(&(view->buffer)))
+        last = scroll_dn(view);
+    view->sync_needed = false;
+    if (view->sync_center) {
+        sync_center(view, csr);
+        view->sync_center = false;
+    }
+}
+
+static void selcontext(View* view, bool (*isword)(Rune), Sel* sel) {
+    Buf* buf = &(view->buffer);
+    size_t bol = buf_bol(buf, sel->end);
+    Rune r = buf_get(buf, sel->end);
+    if (r == '(' || r == ')') {
+        buf_getblock(buf, '(', ')', sel);
+    } else if (r == '[' || r == ']') {
+        buf_getblock(buf, '[', ']', sel);
+    } else if (r == '{' || r == '}') {
+        buf_getblock(buf, '{', '}', sel);
+    } else if (sel->end == bol || r == '\n' || r == RUNE_CRLF) {
+        sel->beg = bol;
+        sel->end = buf_eol(buf, sel->end);
+    } else if (risword(r)) {
+        buf_getword(buf, isword, sel);
+    } else {
+        buf_getword(buf, risbigword, sel);
+    }
+    sel->end = buf_byrune(&(view->buffer), sel->end, RIGHT);
+    sel->col = buf_getcol(&(view->buffer), sel->end);
+}
+
+static void clearrow(View* view, size_t row) {
+    Row* scrrow = view_getrow(view, row);
+    if (!scrrow) return;
+    for (size_t i = 0; i < view->ncols; i++)
+        scrrow->cols[i].rune = (Rune)' ';
+    scrrow->rlen = 0;
+    scrrow->len  = 0;
+}
+
+static size_t setcell(View* view, size_t row, size_t col, uint32_t attr, Rune r) {
+    if (row >= view->nrows || col >= view->ncols) return 0;
+    Row* scrrow = view_getrow(view, row);
+    int ncols = runewidth(col, r);
+    /* write the rune to the screen buf */
+    scrrow->cols[col].attr = attr;
+    if (r == '\t' || r == '\n' || r == RUNE_CRLF)
+        scrrow->cols[col].rune = ' ';
+    else
+        scrrow->cols[col].rune = r;
+    /* Update lengths */
+    scrrow->rlen += 1;
+    for (int i = 1; i < ncols; i++) {
+        scrrow->cols[col].attr = attr;
+        scrrow->cols[col+i].rune = '\0';
+    }
+    if ((col + ncols) > scrrow->len)
+        scrrow->len = col + ncols;
+    return ncols;
+}
+
+static void selswap(Sel* sel) {
+    if (sel->end < sel->beg) {
+        size_t temp = sel->beg;
+        sel->beg = sel->end;
+        sel->end = temp;
+    }
+}
+
+static size_t num_selected(Sel sel) {
+    selswap(&sel);
+    return (sel.end - sel.beg);
+}
+
+static bool in_selection(Sel sel, size_t off) {
+    selswap(&sel);
+    return (sel.beg <= off && off < sel.end);
+}
+
+static bool selected(View* view, size_t pos) {
+    return in_selection(view->selection, pos);
+}
+
+static size_t fill_row(View* view, unsigned row, size_t pos) {
+    view_getrow(view, row)->off = pos;
+    clearrow(view, row);
+    for (size_t x = 0; x < view->ncols;) {
+        uint32_t attr = (selected(view, pos) ? CLR_SelectedText : CLR_NormalText);
+        Rune r = buf_get(&(view->buffer), pos++);
+        x += setcell(view, row, x, attr, r);
+        if (buf_iseol(&(view->buffer), pos-1)) break;
+    }
+    return pos;
+}
+
+static void reflow(View* view) {
+    if (!view->rows) return;
+    size_t pos = view->rows[0]->off;
+    for (size_t y = 0; y < view->nrows; y++)
+        pos = fill_row(view, y, pos);
+}
+
+static unsigned prev_screen_line(View* view, unsigned bol, unsigned off) {
+    unsigned pos = bol;
+    while (true) {
+        unsigned x;
+        for (x = 0; x < view->ncols && (pos + x) < off; x++)
+            x += runewidth(x, buf_get(&(view->buffer), pos+x));
+        if ((pos + x) >= off) break;
+        pos += x;
+    }
+    return pos;
+}
+
+static unsigned scroll_up(View* view) {
+    unsigned first = view->rows[0]->off;
+    unsigned bol    = buf_bol(&(view->buffer), first);
+    unsigned prevln = (first == bol ? buf_byline(&(view->buffer), bol, -1) : bol);
+    if (!first) return first;
+    prevln = prev_screen_line(view, prevln, first);
+    /* delete the last row and shift the others */
+    free(view->rows[view->nrows - 1]);
+    memmove(&view->rows[1], &view->rows[0], sizeof(Row*) * (view->nrows-1));
+    view->rows[0] = calloc(1, sizeof(Row) + (view->ncols * sizeof(UGlyph)));
+    view->rows[0]->off = prevln;
+    /* fill in row content */
+    fill_row(view, 0, view->rows[0]->off);
+    return view->rows[0]->off;
+}
+
+static unsigned scroll_dn(View* view) {
+    unsigned last  = view->rows[view->nrows-1]->off + view->rows[view->nrows-1]->rlen - 1;
+    if (last >= buf_end(&(view->buffer))) return last;
+    /* delete the first row and shift the others */
+    if (view->nrows > 1) {
+        free(view->rows[0]);
+        memmove(&view->rows[0], &view->rows[1], sizeof(Row*) * (view->nrows-1));
+        view->rows[view->nrows-1] = calloc(1, sizeof(Row) + (view->ncols * sizeof(UGlyph)));
+        view->rows[view->nrows-1]->off = (view->rows[view->nrows-2]->off + view->rows[view->nrows-2]->rlen);
+        /* fill in row content */
+        fill_row(view, view->nrows-1, view->rows[view->nrows-1]->off);
+    } else {
+        view->rows[0]->off += view->rows[0]->rlen;
+        fill_row(view, 0, view->rows[0]->off);
+    }
+    return view->rows[view->nrows-1]->off + view->rows[view->nrows-1]->rlen - 1;
+}
+
+static void sync_center(View* view, size_t csr) {
+    /* determine the screenline containing the cursor */
+    size_t scrln = 0;
+    for (; scrln < view->nrows; scrln++) {
+        unsigned first = view->rows[scrln]->off;
+        unsigned last  = first + view->rows[scrln]->rlen - 1;
+        if (csr >= first && csr <= last)
+            break;
+    }
+    /* find the middle row and scroll until the cursor is on that row */
+    unsigned midrow = view->nrows / 2;
+    int move = (scrln - midrow);
+    unsigned count = (move < 0 ? -move : move);
+    for (; count > 0; count--)
+        (move < 0 ? scroll_up : scroll_dn)(view);
+}
+
+static size_t getoffset(View* view, size_t row, size_t col) {
+    Row* scrrow = view_getrow(view, row);
+    if (!scrrow) return SIZE_MAX;
+    size_t pos = scrrow->off;
+    if (col >= scrrow->len) {
+        pos = (scrrow->off + scrrow->rlen - 1);
+    } else {
+        /* multi column runes are followed by \0 slots so if we clicked on a \0
+           slot, slide backwards to the real rune. */
+        for (; !scrrow->cols[col].rune && col > 0; col--);
+        /* now lets count the number of runes up to the one we clicked on */
+        for (unsigned i = 0; i < col; i++)
+            if (scrrow->cols[i].rune)
+                pos++;
+    }
+    if (pos >= buf_end(&(view->buffer)))
+        return buf_end(&(view->buffer));
+    return pos;
+}
+
+static bool selvisible(View* view) {
+    if (!view->nrows) return true;
+    unsigned beg = view->rows[0]->off;
+    unsigned end = view->rows[view->nrows-1]->off +
+                   view->rows[view->nrows-1]->rlen;
+    return (view->selection.beg >= beg && view->selection.end <= end);
+}
+
+static void find_cursor(View* view, size_t* csrx, size_t* csry) {
+    size_t csr = view->selection.end;
+    for (size_t y = 0; y < view->nrows; y++) {
+        size_t start = view->rows[y]->off;
+        size_t end   = view->rows[y]->off + view->rows[y]->rlen - 1;
+        if (start <= csr && csr <= end) {
+            size_t pos = start;
+            for (size_t x = 0; x < view->ncols;) {
+                if (pos == csr) {
+                    *csry = y, *csrx = x;
+                    break;
+                }
+                x += runewidth(x, buf_get(&(view->buffer),pos++));
+            }
+            break;
+        }
+    }
+}