tvi: rely on line offset instead of column number - neatvi - [fork] simple vi-type editor with UTF-8 support
 (HTM) git clone git://src.adamsgaard.dk/neatvi
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
       ---
 (DIR) commit bdbc8d37c53bd7702979786c58354bbf2ef6990e
 (DIR) parent 2dc24f819262d48c8a7f6b2d1ce6a7fe992b4310
 (HTM) Author: Ali Gholami Rudi <ali@rudi.ir>
       Date:   Thu, 28 May 2015 19:43:35 +0430
       
       vi: rely on line offset instead of column number
       
       Diffstat:
         M Makefile                            |       2 +-
         M ex.c                                |       4 ++--
         A mot.c                               |     186 +++++++++++++++++++++++++++++++
         M ren.c                               |      68 ++++---------------------------
         M uc.c                                |      12 +++++++++---
         M vi.c                                |     477 +++++++++++--------------------
         M vi.h                                |      16 +++++++++++++---
       
       7 files changed, 382 insertions(+), 383 deletions(-)
       ---
 (DIR) diff --git a/Makefile b/Makefile
       t@@ -2,7 +2,7 @@ CC = cc
        CFLAGS = -Wall -O2
        LDFLAGS =
        
       -OBJS = vi.o ex.o lbuf.o sbuf.o ren.o dir.o syn.o reg.o led.o \
       +OBJS = vi.o ex.o lbuf.o mot.o sbuf.o ren.o dir.o syn.o reg.o led.o \
                uc.o term.o rset.o cmd.o conf.o
        
        all: vi
 (DIR) diff --git a/ex.c b/ex.c
       t@@ -17,7 +17,7 @@ int xvis;                        /* visual mode */
        int xai = 1;                        /* autoindent option */
        int xic = 1;                        /* ignorecase option */
        struct lbuf *xb;                /* current buffer */
       -int xrow, xcol, xtop;                /* current row, column, and top row */
       +int xrow, xoff, xtop;                /* current row, column, and top row */
        int xrow_alt;                        /* alternate row, column, and top row */
        int xled = 1;                        /* use the line editor */
        int xdir = +1;                        /* current direction context */
       t@@ -182,7 +182,7 @@ static void ec_edit(char *ec)
                        strcpy(xpath, xpath_tmp);
                        xrow = xrow_alt;
                        xrow_alt = xrow_tmp;
       -                xcol = 0;
       +                xoff = 0;
                        xtop = 0;
                } else {
                        strcpy(xpath_alt, xpath);
 (DIR) diff --git a/mot.c b/mot.c
       t@@ -0,0 +1,186 @@
       +#include <stdio.h>
       +#include <string.h>
       +#include "vi.h"
       +
       +int lbuf_indents(struct lbuf *lb, int r)
       +{
       +        char *ln = lbuf_get(lb, r);
       +        int o;
       +        if (!ln)
       +                return 0;
       +        for (o = 0; uc_isspace(ln); o++)
       +                ln = uc_next(ln);
       +        return o;
       +}
       +
       +static int uc_nextdir(char **s, char *beg, int dir)
       +{
       +        if (dir < 0) {
       +                if (*s == beg)
       +                        return 1;
       +                *s = uc_prev(beg, *s);
       +        } else {
       +                *s = uc_next(*s);
       +                if (!(*s)[0])
       +                        return 1;
       +        }
       +        return 0;
       +}
       +
       +int lbuf_findchar(struct lbuf *lb, char *cs, int cmd, int n, int *row, int *off)
       +{
       +        char *ln = lbuf_get(lb, *row);
       +        char *s;
       +        int dir = (cmd == 'f' || cmd == 't') ? +1 : -1;
       +        if (!ln)
       +                return 1;
       +        if (n < 0)
       +                dir = -dir;
       +        if (n < 0)
       +                n = -n;
       +        s = uc_chr(ln, *off);
       +        while (n > 0 && !uc_nextdir(&s, ln, dir))
       +                if (uc_code(s) == uc_code(cs))
       +                        n--;
       +        if (!n && (cmd == 't' || cmd == 'T'))
       +                uc_nextdir(&s, ln, -dir);
       +        if (!n)
       +                *off = uc_off(ln, s - ln);
       +        return n != 0;
       +}
       +
       +int lbuf_search(struct lbuf *lb, char *kw, int dir, int *r, int *o, int *len)
       +{
       +        int offs[2];
       +        int found = 0;
       +        int r0 = *r, o0 = *o;
       +        int i;
       +        struct rset *re = rset_make(1, &kw, xic ? RE_ICASE : 0);
       +        if (!re)
       +                return 1;
       +        for (i = r0; !found && i >= 0 && i < lbuf_len(lb); i += dir) {
       +                char *s = lbuf_get(lb, i);
       +                int off = dir > 0 && r0 == i ? uc_chr(s, o0 + 1) - s : 0;
       +                int flg = off ? RE_NOTBOL : 0;
       +                while (rset_find(re, s + off, 1, offs, flg) >= 0) {
       +                        if (dir < 0 && r0 == i && off + offs[0] >= o0)
       +                                break;
       +                        found = 1;
       +                        *o = uc_off(s, off + offs[0]);
       +                        *r = i;
       +                        *len = offs[1] - offs[0];
       +                        off += offs[1];
       +                        if (dir > 0)
       +                                break;
       +                }
       +        }
       +        rset_free(re);
       +        return !found;
       +}
       +
       +int lbuf_paragraphbeg(struct lbuf *lb, int dir, int *row, int *off)
       +{
       +        while (*row >= 0 && *row < lbuf_len(lb) && !strcmp("\n", lbuf_get(lb, *row)))
       +                *row += dir;
       +        while (*row >= 0 && *row < lbuf_len(lb) && strcmp("\n", lbuf_get(lb, *row)))
       +                *row += dir;
       +        *row = MAX(0, MIN(*row, lbuf_len(lb) - 1));
       +        *off = 0;
       +        return 0;
       +}
       +
       +int lbuf_sectionbeg(struct lbuf *lb, int dir, int *row, int *off)
       +{
       +        *row += dir;
       +        while (*row >= 0 && *row < lbuf_len(lb) && lbuf_get(lb, *row)[0] != '{')
       +                *row += dir;
       +        *row = MAX(0, MIN(*row, lbuf_len(lb) - 1));
       +        *off = 0;
       +        return 0;
       +}
       +
       +static int lbuf_lnnext(struct lbuf *lb, int dir, int *r, int *o)
       +{
       +        int off = *o + dir;
       +        if (off < 0 || !lbuf_get(lb, *r) || off >= uc_slen(lbuf_get(lb, *r)))
       +                return 1;
       +        *o = off;
       +        return 0;
       +}
       +
       +int lbuf_eol(struct lbuf *lb, int row)
       +{
       +        int len = lbuf_get(lb, row) ? uc_slen(lbuf_get(lb, row)) : 0;
       +        return len ? len - 1 : 0;
       +}
       +
       +static int lbuf_next(struct lbuf *lb, int dir, int *r, int *o)
       +{
       +        if (dir < 0 && *r >= lbuf_len(lb))
       +                *r = MAX(0, lbuf_len(lb) - 1);
       +        if (lbuf_lnnext(lb, dir, r, o)) {
       +                if (!lbuf_get(lb, *r + dir))
       +                        return -1;
       +                *r += dir;
       +                *o = dir > 0 ? 0 : lbuf_eol(lb, *r);
       +                return 0;
       +        }
       +        return 0;
       +}
       +
       +/* return a pointer to the character at visual position c of line r */
       +static char *lbuf_chr(struct lbuf *lb, int r, int c)
       +{
       +        char *ln = lbuf_get(lb, r);
       +        return ln ? uc_chr(ln, c) : "";
       +}
       +
       +/* move to the last character of the word */
       +static int lbuf_wordlast(struct lbuf *lb, int kind, int dir, int *row, int *off)
       +{
       +        if (!kind || !(uc_kind(lbuf_chr(lb, *row, *off)) & kind))
       +                return 0;
       +        while (uc_kind(lbuf_chr(lb, *row, *off)) & kind)
       +                if (lbuf_next(lb, dir, row, off))
       +                        return 1;
       +        if (!(uc_kind(lbuf_chr(lb, *row, *off)) & kind))
       +                lbuf_next(lb, -dir, row, off);
       +        return 0;
       +}
       +
       +int lbuf_wordbeg(struct lbuf *lb, int big, int dir, int *row, int *off)
       +{
       +        int nl = 0;
       +        lbuf_wordlast(lb, big ? 3 : uc_kind(lbuf_chr(lb, *row, *off)), dir, row, off);
       +        if (lbuf_next(lb, dir, row, off))
       +                return 1;
       +        while (uc_isspace(lbuf_chr(lb, *row, *off))) {
       +                nl = uc_code(lbuf_chr(lb, *row, *off)) == '\n' ? nl + 1 : 0;
       +                if (nl == 2)
       +                        return 0;
       +                if (lbuf_next(lb, dir, row, off))
       +                        return 1;
       +        }
       +        return 0;
       +}
       +
       +int lbuf_wordend(struct lbuf *lb, int big, int dir, int *row, int *off)
       +{
       +        int nl = uc_code(lbuf_chr(lb, *row, *off)) == '\n' ? -1 : 0;
       +        if (!uc_isspace(lbuf_chr(lb, *row, *off)))
       +                if (lbuf_next(lb, dir, row, off))
       +                        return 1;
       +        while (uc_isspace(lbuf_chr(lb, *row, *off))) {
       +                nl = uc_code(lbuf_chr(lb, *row, *off)) == '\n' ? nl + 1 : 0;
       +                if (nl == 2) {
       +                        if (dir < 0)
       +                                lbuf_next(lb, -dir, row, off);
       +                        return 0;
       +                }
       +                if (lbuf_next(lb, dir, row, off))
       +                        return 1;
       +        }
       +        if (lbuf_wordlast(lb, big ? 3 : uc_kind(lbuf_chr(lb, *row, *off)), dir, row, off))
       +                return 1;
       +        return 0;
       +}
 (DIR) diff --git a/ren.c b/ren.c
       t@@ -102,20 +102,13 @@ int ren_cursor(char *s, int p)
                return p >= 0 ? p : 0;
        }
        
       -/* real cursor position; never past EOL */
       -int ren_noeol(char *s, int p)
       +/* return an offset before EOL */
       +int ren_noeol(char *s, int o)
        {
       -        int n;
       -        int *pos;
       -        if (!s)
       -                return 0;
       -        n = uc_slen(s);
       -        pos = ren_position(s);
       -        p = pos_prev(pos, n, p, 1);
       -        if (uc_code(uc_chr(s, ren_off(s, p))) == '\n')
       -                p = pos_prev(pos, n, p, 0);
       -        free(pos);
       -        return p >= 0 ? p : 0;
       +        int n = s ? uc_slen(s) : 0;
       +        if (o >= n)
       +                o = MAX(0, n - 1);
       +        return o > 0 && uc_chr(s, o)[0] == '\n' ? o - 1 : o;
        }
        
        /* the position of the next character */
       t@@ -129,54 +122,7 @@ int ren_next(char *s, int p, int dir)
                else
                        p = pos_prev(pos, n, p, 0);
                free(pos);
       -        return p;
       -}
       -
       -static void swap(int *i1, int *i2)
       -{
       -        int t = *i1;
       -        *i1 = *i2;
       -        *i2 = t;
       -}
       -
       -/* the region specified by two visual positions */
       -int ren_region(char *s, int c1, int c2, int *l1, int *l2, int closed)
       -{
       -        int *ord;                /* ord[i]: the order of the i-th char on the screen */
       -        int o1, o2;
       -        int beg, end;
       -        int n = uc_slen(s);
       -        int i;
       -        if (c1 == c2 && !closed) {
       -                *l1 = ren_off(s, c1);
       -                *l2 = ren_off(s, c2);
       -                return 0;
       -        }
       -        ord = malloc(n * sizeof(ord[0]));
       -        for (i = 0; i < n; i++)
       -                ord[i] = i;
       -        if (xorder)
       -                dir_reorder(s, ord);
       -
       -        if (c2 < c1)
       -                swap(&c1, &c2);
       -        if (!closed)
       -                c2 = ren_next(s, c2, -1);
       -        beg = ren_off(s, c1);
       -        end = ren_off(s, c2);
       -        if (end < beg)
       -                swap(&beg, &end);
       -        o1 = ord[beg];
       -        o2 = ord[end];
       -        if (o2 < o1)
       -                swap(&o1, &o2);
       -        for (i = beg; i <= end; i++)
       -                if (ord[i] < o1 || ord[i] > o2)
       -                        break;
       -        *l1 = beg;
       -        *l2 = i;
       -        free(ord);
       -        return 0;
       +        return s && uc_chr(s, ren_off(s, p))[0] != '\n' ? p : -1;
        }
        
        static char *ren_placeholder(char *s)
 (DIR) diff --git a/uc.c b/uc.c
       t@@ -57,7 +57,7 @@ char *uc_beg(char *beg, char *s)
        }
        
        /* find the end of the character at s[i] */
       -char *uc_end(char *beg, char *s)
       +char *uc_end(char *s)
        {
                if (!*s || !((unsigned char) *s & 0x80))
                        return s;
       t@@ -71,10 +71,16 @@ char *uc_end(char *beg, char *s)
        /* return a pointer to the character following s */
        char *uc_next(char *s)
        {
       -        s = uc_end(s, s);
       +        s = uc_end(s);
                return *s ? s + 1 : s;
        }
        
       +/* return a pointer to the character preceding s */
       +char *uc_prev(char *beg, char *s)
       +{
       +        return s == beg ? beg : uc_beg(beg, s - 1);
       +}
       +
        int uc_wid(char *s)
        {
                return 1;
       t@@ -153,7 +159,7 @@ int uc_isprint(char *s)
        int uc_isalpha(char *s)
        {
                int c = s ? (unsigned char) *s : 0;
       -        return c <= 0x7f && isalpha(c);
       +        return c > 0x7f || isalpha(c);
        }
        
        int uc_isdigit(char *s)
 (DIR) diff --git a/vi.c b/vi.c
       t@@ -20,6 +20,7 @@ static int vi_charcmd;                /* the character finding command */
        static int vi_arg1, vi_arg2;        /* the first and second arguments */
        static int vi_ybuf;                /* current yank buffer */
        static char *vi_kmap;                /* current insertion keymap */
       +static int vi_pcol;                /* the column requested by | command */
        
        static void vi_drawmsg(void)
        {
       t@@ -27,7 +28,7 @@ static void vi_drawmsg(void)
                vi_msg[0] = '\0';
        }
        
       -static void vi_draw(void)
       +static void vi_draw(int xcol)
        {
                int i;
                term_record();
       t@@ -121,108 +122,53 @@ static int vi_prefix(void)
                return n;
        }
        
       -static int lbuf_lnnext(struct lbuf *lb, int *r, int *c, int dir)
       +static int vi_col2off(struct lbuf *lb, int row, int col)
        {
       -        char *ln = lbuf_get(lb, *r);
       -        int col = ln ? ren_next(ln, *c, dir) : -1;
       -        if (col < 0)
       -                return -1;
       -        *c = col;
       -        return 0;
       +        char *ln = lbuf_get(lb, row);
       +        return ln ? ren_off(ln, col) : 0;
        }
        
       -static void lbuf_eol(struct lbuf *lb, int *r, int *c, int dir)
       +static int vi_off2col(struct lbuf *lb, int row, int off)
        {
       -        char *ln = lbuf_get(lb, *r);
       -        *c = dir < 0 ? 0 : MAX(0, ren_wid(ln ? ln : "") - 1);
       +        char *ln = lbuf_get(lb, row);
       +        return ln ? ren_pos(ln, off) : 0;
        }
        
       -static int lbuf_next(struct lbuf *lb, int *r, int *c, int dir)
       +static int vi_nextoff(struct lbuf *lb, int dir, int *row, int *off)
        {
       -        if (dir < 0 && *r >= lbuf_len(lb))
       -                *r = MAX(0, lbuf_len(lb) - 1);
       -        if (lbuf_lnnext(lb, r, c, dir)) {
       -                if (!lbuf_get(lb, *r + dir))
       -                        return -1;
       -                *r += dir;
       -                lbuf_eol(lb, r, c, -dir);
       -                return 0;
       -        }
       +        int o = *off + dir;
       +        if (o < 0 || !lbuf_get(lb, *row) || o >= uc_slen(lbuf_get(lb, *row)))
       +                return 1;
       +        *off = o;
                return 0;
        }
        
       -/* return a pointer to the character at visual position c of line r */
       -static char *lbuf_chr(struct lbuf *lb, int r, int c)
       -{
       -        char *ln = lbuf_get(lb, r);
       -        return ln ? uc_chr(ln, ren_off(ln, c)) : "";
       -}
       -
       -static void lbuf_postindents(struct lbuf *lb, int *r, int *c)
       +static int vi_nextcol(struct lbuf *lb, int dir, int *row, int *off)
        {
       -        lbuf_eol(lb, r, c, -1);
       -        while (uc_isspace(lbuf_chr(lb, *r, *c)))
       -                if (lbuf_lnnext(lb, r, c, +1))
       -                        break;
       +        char *ln = lbuf_get(lb, *row);
       +        int col = ln ? ren_pos(ln, *off) : 0;
       +        int o = ln ? ren_next(ln, col, dir) : -1;
       +        if (o < 0)
       +                return -1;
       +        *off = ren_off(ln, o);
       +        return 0;
        }
        
       -static int lbuf_findchar(struct lbuf *lb, int *row, int *col, char *cs, int cmd, int n)
       +static int vi_findchar(struct lbuf *lb, char *cs, int cmd, int n, int *row, int *off)
        {
       -        int dir = (cmd == 'f' || cmd == 't') ? +1 : -1;
       -        int c = *col;
       -        if (n < 0)
       -                dir = -dir;
       -        if (n < 0)
       -                n = -n;
                strcpy(vi_charlast, cs);
                vi_charcmd = cmd;
       -        while (n > 0 && !lbuf_lnnext(lb, row, &c, dir))
       -                if (uc_code(lbuf_chr(lb, *row, c)) == uc_code(cs))
       -                        n--;
       -        if (!n)
       -                *col = c;
       -        if (!n && (cmd == 't' || cmd == 'T'))
       -                lbuf_lnnext(lb, row, col, -dir);
       -        return n != 0;
       +        return lbuf_findchar(lb, cs, cmd, n, row, off);
        }
        
       -static int lbuf_search(struct lbuf *lb, char *kw, int dir, int *r, int *c, int *len)
       -{
       -        int offs[2];
       -        int found = 0;
       -        int row = *r, col = *c;
       -        int i;
       -        struct rset *re = rset_make(1, &kw, xic ? RE_ICASE : 0);
       -        if (!re)
       -                return 1;
       -        for (i = row; !found && i >= 0 && i < lbuf_len(lb); i += dir) {
       -                char *s = lbuf_get(lb, i);
       -                int off = dir > 0 && row == i ? uc_chr(s, col + 1) - s : 0;
       -                int flg = off ? RE_NOTBOL : 0;
       -                while (rset_find(re, s + off, 1, offs, flg) >= 0) {
       -                        if (dir < 0 && row == i && off + offs[0] >= col)
       -                                break;
       -                        found = 1;
       -                        *c = uc_off(s, off + offs[0]);
       -                        *r = i;
       -                        *len = offs[1] - offs[0];
       -                        off += offs[1];
       -                        if (dir > 0)
       -                                break;
       -                }
       -        }
       -        rset_free(re);
       -        return !found;
       -}
       -
       -static int vi_search(int cmd, int cnt, int *row, int *col)
       +static int vi_search(int cmd, int cnt, int *row, int *off)
        {
                int r = *row;
       -        int c = *col;
       +        int o = *off;
                int failed = 0;
                int len = 0;
                int i, dir;
       -        char *off = "";
       +        char *soff = "";
                if (cmd == '/' || cmd == '?') {
                        char sign[4] = {cmd};
                        char *kw = vi_prompt(sign, &vi_kmap);
       t@@ -232,7 +178,7 @@ static int vi_search(int cmd, int cnt, int *row, int *col)
                        if (kw[0])
                                snprintf(vi_findlast, sizeof(vi_findlast), "%s", kw);
                        if (strchr(vi_findlast, cmd)) {
       -                        off = strchr(vi_findlast, cmd) + 1;
       +                        soff = strchr(vi_findlast, cmd) + 1;
                                *strchr(vi_findlast, cmd) = '\0';
                        }
                        free(kw);
       t@@ -240,26 +186,26 @@ static int vi_search(int cmd, int cnt, int *row, int *col)
                dir = cmd == 'N' ? -vi_finddir : vi_finddir;
                if (!vi_findlast[0] || !lbuf_len(xb))
                        return 1;
       -        c = ren_off(lbuf_get(xb, *row), *col);
       +        o = *off;
                for (i = 0; i < cnt; i++) {
       -                if (lbuf_search(xb, vi_findlast, dir, &r, &c, &len)) {
       +                if (lbuf_search(xb, vi_findlast, dir, &r, &o, &len)) {
                                failed = 1;
                                break;
                        }
                        if (i + 1 < cnt && cmd == '/')
       -                        c += len;
       +                        o += len;
                }
                if (!failed) {
                        *row = r;
       -                *col = ren_pos(lbuf_get(xb, r), c);
       -                while (off[0] && isspace((unsigned char) off[0]))
       -                        off++;
       -                if (off[0]) {
       -                        *col = -1;
       -                        if (*row + atoi(off) < 0 || *row + atoi(off) >= lbuf_len(xb))
       +                *off = o;
       +                while (soff[0] && isspace((unsigned char) soff[0]))
       +                        soff++;
       +                if (soff[0]) {
       +                        *off = -1;
       +                        if (*row + atoi(soff) < 0 || *row + atoi(soff) >= lbuf_len(xb))
                                        failed = 1;
                                else
       -                                *row += atoi(off);
       +                                *row += atoi(soff);
                        }
                }
                if (failed)
       t@@ -267,77 +213,6 @@ static int vi_search(int cmd, int cnt, int *row, int *col)
                return failed;
        }
        
       -/* move to the last character of the word */
       -static int lbuf_wordlast(struct lbuf *lb, int *row, int *col, int kind, int dir)
       -{
       -        if (!kind || !(uc_kind(lbuf_chr(lb, *row, *col)) & kind))
       -                return 0;
       -        while (uc_kind(lbuf_chr(lb, *row, *col)) & kind)
       -                if (lbuf_next(lb, row, col, dir))
       -                        return 1;
       -        if (!(uc_kind(lbuf_chr(lb, *row, *col)) & kind))
       -                lbuf_next(lb, row, col, -dir);
       -        return 0;
       -}
       -
       -static int lbuf_wordbeg(struct lbuf *lb, int *row, int *col, int big, int dir)
       -{
       -        int nl = 0;
       -        lbuf_wordlast(lb, row, col, big ? 3 : uc_kind(lbuf_chr(lb, *row, *col)), dir);
       -        if (lbuf_next(lb, row, col, dir))
       -                return 1;
       -        while (uc_isspace(lbuf_chr(lb, *row, *col))) {
       -                nl = uc_code(lbuf_chr(lb, *row, *col)) == '\n' ? nl + 1 : 0;
       -                if (nl == 2)
       -                        return 0;
       -                if (lbuf_next(lb, row, col, dir))
       -                        return 1;
       -        }
       -        return 0;
       -}
       -
       -static int lbuf_wordend(struct lbuf *lb, int *row, int *col, int big, int dir)
       -{
       -        int nl = uc_code(lbuf_chr(lb, *row, *col)) == '\n' ? -1 : 0;
       -        if (!uc_isspace(lbuf_chr(lb, *row, *col)))
       -                if (lbuf_next(lb, row, col, dir))
       -                        return 1;
       -        while (uc_isspace(lbuf_chr(lb, *row, *col))) {
       -                nl = uc_code(lbuf_chr(lb, *row, *col)) == '\n' ? nl + 1 : 0;
       -                if (nl == 2) {
       -                        if (dir < 0)
       -                                lbuf_next(lb, row, col, -dir);
       -                        return 0;
       -                }
       -                if (lbuf_next(lb, row, col, dir))
       -                        return 1;
       -        }
       -        if (lbuf_wordlast(lb, row, col, big ? 3 : uc_kind(lbuf_chr(lb, *row, *col)), dir))
       -                return 1;
       -        return 0;
       -}
       -
       -static int lbuf_paragraphbeg(struct lbuf *lb, int *row, int *col, int dir)
       -{
       -        while (*row >= 0 && *row < lbuf_len(lb) && !strcmp("\n", lbuf_get(lb, *row)))
       -                *row += dir;
       -        while (*row >= 0 && *row < lbuf_len(lb) && strcmp("\n", lbuf_get(lb, *row)))
       -                *row += dir;
       -        *row = MAX(0, MIN(*row, lbuf_len(lb) - 1));
       -        lbuf_eol(lb, row, col, -1);
       -        return 0;
       -}
       -
       -static int lbuf_sectionbeg(struct lbuf *lb, int *row, int *col, int dir)
       -{
       -        *row += dir;
       -        while (*row >= 0 && *row < lbuf_len(lb) && lbuf_get(lb, *row)[0] != '{')
       -                *row += dir;
       -        *row = MAX(0, MIN(*row, lbuf_len(lb) - 1));
       -        lbuf_eol(lb, row, col, -1);
       -        return 0;
       -}
       -
        /* read a line motion */
        static int vi_motionln(int *row, int cmd)
        {
       t@@ -356,9 +231,11 @@ static int vi_motionln(int *row, int cmd)
                        *row = MIN(*row + cnt - 1, lbuf_len(xb) - 1);
                        break;
                case '\'':
       -                if ((mark = vi_read()) > 0 && (isalpha(mark) || mark == '\''))
       -                        if (lbuf_markpos(xb, mark) >= 0)
       -                                *row = lbuf_markpos(xb, mark);
       +                if ((mark = vi_read()) <= 0 || (!isalpha(mark) && mark != '\''))
       +                        return -1;
       +                if (lbuf_markpos(xb, mark) < 0)
       +                        return -1;
       +                *row = lbuf_markpos(xb, mark);
                        break;
                case 'j':
                        *row = MIN(*row + cnt, lbuf_len(xb) - 1);
       t@@ -398,14 +275,14 @@ static int vi_motionln(int *row, int cmd)
                return c;
        }
        
       -static char *lbuf_curword(struct lbuf *lb, int row, int col)
       +static char *vi_curword(struct lbuf *lb, int row, int off)
        {
                struct sbuf *sb;
                char *ln = lbuf_get(lb, row);
                char *beg, *end;
                if (!ln)
                        return NULL;
       -        beg = uc_chr(ln, ren_off(ln, ren_noeol(ln, col)));
       +        beg = uc_chr(ln, ren_noeol(ln, off));
                end = beg;
                while (*end && uc_kind(end) == 1)
                        end = uc_next(end);
       t@@ -421,7 +298,7 @@ static char *lbuf_curword(struct lbuf *lb, int row, int col)
        }
        
        /* read a motion */
       -static int vi_motion(int *row, int *col)
       +static int vi_motion(int *row, int *off)
        {
                int cnt = (vi_arg1 ? vi_arg1 : 1) * (vi_arg2 ? vi_arg2 : 1);
                char *ln = lbuf_get(xb, *row);
       t@@ -430,157 +307,158 @@ static int vi_motion(int *row, int *col)
                int mv;
                int i;
                if ((mv = vi_motionln(row, 0))) {
       -                *col = -1;
       +                *off = -1;
                        return mv;
                }
                mv = vi_read();
                switch (mv) {
       -        case ' ':
       -                for (i = 0; i < cnt; i++)
       -                        if (lbuf_lnnext(xb, row, col, 1))
       -                                break;
       -                break;
                case 'f':
                        if (!(cs = vi_char()))
                                return -1;
       -                if (lbuf_findchar(xb, row, col, cs, mv, cnt))
       +                if (vi_findchar(xb, cs, mv, cnt, row, off))
                                return -1;
                        break;
                case 'F':
                        if (!(cs = vi_char()))
                                return -1;
       -                if (lbuf_findchar(xb, row, col, cs, mv, cnt))
       +                if (vi_findchar(xb, cs, mv, cnt, row, off))
                                return -1;
                        break;
                case ';':
                        if (!vi_charlast[0])
                                return -1;
       -                if (lbuf_findchar(xb, row, col, vi_charlast, vi_charcmd, cnt))
       +                if (vi_findchar(xb, vi_charlast, vi_charcmd, cnt, row, off))
                                return -1;
                        break;
                case ',':
                        if (!vi_charlast[0])
                                return -1;
       -                if (lbuf_findchar(xb, row, col, vi_charlast, vi_charcmd, -cnt))
       +                if (vi_findchar(xb, vi_charlast, vi_charcmd, -cnt, row, off))
                                return -1;
                        break;
                case 'h':
                        for (i = 0; i < cnt; i++)
       -                        if (lbuf_lnnext(xb, row, col, -1 * dir))
       +                        if (vi_nextcol(xb, -1 * dir, row, off))
                                        break;
                        break;
                case 'l':
                        for (i = 0; i < cnt; i++)
       -                        if (lbuf_lnnext(xb, row, col, +1 * dir))
       +                        if (vi_nextcol(xb, +1 * dir, row, off))
                                        break;
                        break;
                case 't':
                        if (!(cs = vi_char()))
                                return -1;
       -                if (lbuf_findchar(xb, row, col, cs, mv, cnt))
       +                if (vi_findchar(xb, cs, mv, cnt, row, off))
                                return -1;
                        break;
                case 'T':
                        if (!(cs = vi_char()))
                                return -1;
       -                if (lbuf_findchar(xb, row, col, cs, mv, cnt))
       +                if (vi_findchar(xb, cs, mv, cnt, row, off))
                                return -1;
                        break;
                case 'B':
                        for (i = 0; i < cnt; i++)
       -                        if (lbuf_wordend(xb, row, col, 1, -1))
       +                        if (lbuf_wordend(xb, 1, -1, row, off))
                                        break;
                        break;
                case 'E':
                        for (i = 0; i < cnt; i++)
       -                        if (lbuf_wordend(xb, row, col, 1, +1))
       +                        if (lbuf_wordend(xb, 1, +1, row, off))
                                        break;
                        break;
                case 'W':
                        for (i = 0; i < cnt; i++)
       -                        if (lbuf_wordbeg(xb, row, col, 1, +1))
       +                        if (lbuf_wordbeg(xb, 1, +1, row, off))
                                        break;
                        break;
                case 'b':
                        for (i = 0; i < cnt; i++)
       -                        if (lbuf_wordend(xb, row, col, 0, -1))
       +                        if (lbuf_wordend(xb, 0, -1, row, off))
                                        break;
                        break;
                case 'e':
                        for (i = 0; i < cnt; i++)
       -                        if (lbuf_wordend(xb, row, col, 0, +1))
       +                        if (lbuf_wordend(xb, 0, +1, row, off))
                                        break;
                        break;
                case 'w':
                        for (i = 0; i < cnt; i++)
       -                        if (lbuf_wordbeg(xb, row, col, 0, +1))
       +                        if (lbuf_wordbeg(xb, 0, +1, row, off))
                                        break;
                        break;
                case '{':
                        for (i = 0; i < cnt; i++)
       -                        if (lbuf_paragraphbeg(xb, row, col,  -1))
       +                        if (lbuf_paragraphbeg(xb, -1, row, off))
                                        break;
                        break;
                case '}':
                        for (i = 0; i < cnt; i++)
       -                        if (lbuf_paragraphbeg(xb, row, col, +1))
       +                        if (lbuf_paragraphbeg(xb, +1, row, off))
                                        break;
                        break;
                case '[':
                        if (vi_read() != '[')
                                return -1;
                        for (i = 0; i < cnt; i++)
       -                        if (lbuf_sectionbeg(xb, row, col, -1))
       +                        if (lbuf_sectionbeg(xb, -1, row, off))
                                        break;
                        break;
                case ']':
                        if (vi_read() != ']')
                                return -1;
                        for (i = 0; i < cnt; i++)
       -                        if (lbuf_sectionbeg(xb, row, col, +1))
       +                        if (lbuf_sectionbeg(xb, +1, row, off))
                                        break;
                        break;
                case '0':
       -                lbuf_eol(xb, row, col, -1);
       +                *off = 0;
                        break;
                case '^':
       -                lbuf_postindents(xb, row, col);
       +                *off = lbuf_indents(xb, *row);
                        break;
                case '$':
       -                *col = 1024;
       +                *off = lbuf_eol(xb, *row);
                        break;
                case '|':
       -                *col = cnt - 1;
       +                *off = vi_col2off(xb, *row, cnt - 1);
       +                vi_pcol = cnt - 1;
                        break;
                case '/':
       -                if (vi_search(mv, cnt, row, col))
       +                if (vi_search(mv, cnt, row, off))
                                return -1;
                        break;
                case '?':
       -                if (vi_search(mv, cnt, row, col))
       +                if (vi_search(mv, cnt, row, off))
                                return -1;
                        break;
                case 'n':
       -                if (vi_search(mv, cnt, row, col))
       +                if (vi_search(mv, cnt, row, off))
                                return -1;
                        break;
                case 'N':
       -                if (vi_search(mv, cnt, row, col))
       +                if (vi_search(mv, cnt, row, off))
                                return -1;
                        break;
                case TK_CTL('a'):
       -                if (!(cs = lbuf_curword(xb, *row, *col)))
       +                if (!(cs = vi_curword(xb, *row, *off)))
                                return -1;
                        strcpy(vi_findlast, cs);
                        free(cs);
                        vi_finddir = +1;
       -                if (vi_search('n', cnt, row, col))
       +                if (vi_search('n', cnt, row, off))
                                return -1;
                        break;
       +        case ' ':
       +                for (i = 0; i < cnt; i++)
       +                        if (vi_nextoff(xb, +1, row, off))
       +                                break;
       +                break;
                case 127:
                case TK_CTL('h'):
                        for (i = 0; i < cnt; i++)
       -                        if (lbuf_lnnext(xb, row, col, -1))
       +                        if (vi_nextoff(xb, -1, row, off))
                                        break;
                        break;
                default:
       t@@ -597,15 +475,15 @@ static void swap(int *a, int *b)
                *b = t;
        }
        
       -static char *lbuf_region(struct lbuf *lb, int r1, int l1, int r2, int l2)
       +static char *lbuf_region(struct lbuf *lb, int r1, int o1, int r2, int o2)
        {
                struct sbuf *sb;
                char *s1, *s2, *s3;
                if (r1 == r2)
       -                return uc_sub(lbuf_get(lb, r1), l1, l2);
       +                return uc_sub(lbuf_get(lb, r1), o1, o2);
                sb = sbuf_make();
       -        s1 = uc_sub(lbuf_get(lb, r1), l1, -1);
       -        s3 = uc_sub(lbuf_get(lb, r2), 0, l2);
       +        s1 = uc_sub(lbuf_get(lb, r1), o1, -1);
       +        s3 = uc_sub(lbuf_get(lb, r2), 0, o2);
                s2 = lbuf_cp(lb, r1 + 1, r2);
                sbuf_str(sb, s1);
                sbuf_str(sb, s2);
       t@@ -616,53 +494,25 @@ static char *lbuf_region(struct lbuf *lb, int r1, int l1, int r2, int l2)
                return sbuf_done(sb);
        }
        
       -/* insertion offset before or after the given visual position */
       -static int vi_insertionoffset(char *s, int c1, int before)
       -{
       -        int l;
       -        if (!s || !*s)
       -                return 0;
       -        l = ren_off(s, c1);
       -        return before || s[l] == '\n' ? l : l + 1;
       -}
       -
       -static void vi_commandregion(int *r1, int *r2, int *c1, int *c2, int *l1, int *l2, int closed)
       -{
       -        if (*r2 < *r1 || (*r2 == *r1 && *c2 < *c1)) {
       -                swap(r1, r2);
       -                swap(c1, c2);
       -        }
       -        *l1 = vi_insertionoffset(lbuf_get(xb, *r1), *c1, 1);
       -        *l2 = vi_insertionoffset(lbuf_get(xb, *r2), *c2, !closed);
       -        if (*r1 == *r2 && lbuf_get(xb, *r1))
       -                ren_region(lbuf_get(xb, *r1), *c1, *c2, l1, l2, closed);
       -        if (*r1 == *r2 && *l2 < *l1)
       -                swap(l1, l2);
       -}
       -
       -static void vi_yank(int r1, int c1, int r2, int c2, int lnmode, int closed)
       +static void vi_yank(int r1, int o1, int r2, int o2, int lnmode)
        {
                char *region;
       -        int l1, l2;
       -        vi_commandregion(&r1, &r2, &c1, &c2, &l1, &l2, closed);
       -        region = lbuf_region(xb, r1, lnmode ? 0 : l1, r2, lnmode ? -1 : l2);
       +        region = lbuf_region(xb, r1, lnmode ? 0 : o1, r2, lnmode ? -1 : o2);
                reg_put(vi_ybuf, region, lnmode);
                free(region);
                xrow = r1;
       -        xcol = lnmode ? xcol : c1;
       +        xoff = lnmode ? xoff : o1;
        }
        
       -static void vi_delete(int r1, int c1, int r2, int c2, int lnmode, int closed)
       +static void vi_delete(int r1, int o1, int r2, int o2, int lnmode)
        {
                char *pref, *post;
                char *region;
       -        int l1, l2;
       -        vi_commandregion(&r1, &r2, &c1, &c2, &l1, &l2, closed);
       -        region = lbuf_region(xb, r1, lnmode ? 0 : l1, r2, lnmode ? -1 : l2);
       +        region = lbuf_region(xb, r1, lnmode ? 0 : o1, r2, lnmode ? -1 : o2);
                reg_put(vi_ybuf, region, lnmode);
                free(region);
       -        pref = lnmode ? uc_dup("") : uc_sub(lbuf_get(xb, r1), 0, l1);
       -        post = lnmode ? uc_dup("\n") : uc_sub(lbuf_get(xb, r2), l2, -1);
       +        pref = lnmode ? uc_dup("") : uc_sub(lbuf_get(xb, r1), 0, o1);
       +        post = lnmode ? uc_dup("\n") : uc_sub(lbuf_get(xb, r2), o2, -1);
                lbuf_rm(xb, r1, r2 + 1);
                if (!lnmode) {
                        struct sbuf *sb = sbuf_make();
       t@@ -672,9 +522,7 @@ static void vi_delete(int r1, int c1, int r2, int c2, int lnmode, int closed)
                        sbuf_free(sb);
                }
                xrow = r1;
       -        xcol = c1;
       -        if (lnmode)
       -                lbuf_postindents(xb, &xrow, &xcol);
       +        xoff = lnmode ? lbuf_indents(xb, xrow) : o1;
                free(pref);
                free(post);
        }
       t@@ -697,12 +545,12 @@ static int indentscopy(char *d, char *s, int len)
                return i;
        }
        
       -static char *vi_input(char *pref, char *post, int *row, int *col)
       +static char *vi_input(char *pref, char *post, int *row, int *off)
        {
                char ai[64] = "";
                char *rep, *s;
                struct sbuf *sb;
       -        int last, off;
       +        int last;
                if (xai)
                        pref += indentscopy(ai, pref, sizeof(ai));
                rep = led_input(pref, post, ai, xai ? sizeof(ai) - 1 : 0, &vi_kmap);
       t@@ -714,13 +562,12 @@ static char *vi_input(char *pref, char *post, int *row, int *col)
                sbuf_str(sb, rep);
                s = sbuf_buf(sb);
                last = uc_lastline(s) - s;
       -        off = uc_slen(sbuf_buf(sb) + last);
       +        *off = MAX(0, uc_slen(sbuf_buf(sb) + last) - 1);
                if (last)
                        while (xai && (post[0] == ' ' || post[0] == '\t'))
                                post++;
                sbuf_str(sb, post);
                *row = linecount(sbuf_buf(sb)) - 1;
       -        *col = ren_pos(sbuf_buf(sb) + last, MAX(0, off - 1));
                free(rep);
                return sbuf_done(sb);
        }
       t@@ -733,25 +580,23 @@ static char *vi_indents(char *ln)
                return sbuf_done(sb);
        }
        
       -static void vi_change(int r1, int c1, int r2, int c2, int lnmode, int closed)
       +static void vi_change(int r1, int o1, int r2, int o2, int lnmode)
        {
                char *region;
       -        int l1, l2;
       -        int row, col;
       +        int row, off;
                char *rep;
                char *pref, *post;
       -        vi_commandregion(&r1, &r2, &c1, &c2, &l1, &l2, closed);
       -        region = lbuf_region(xb, r1, lnmode ? 0 : l1, r2, lnmode ? -1 : l2);
       +        region = lbuf_region(xb, r1, lnmode ? 0 : o1, r2, lnmode ? -1 : o2);
                reg_put(vi_ybuf, region, lnmode);
                free(region);
       -        pref = lnmode ? vi_indents(lbuf_get(xb, r1)) : uc_sub(lbuf_get(xb, r1), 0, l1);
       -        post = lnmode ? uc_dup("\n") : uc_sub(lbuf_get(xb, r2), l2, -1);
       -        rep = vi_input(pref, post, &row, &col);
       +        pref = lnmode ? vi_indents(lbuf_get(xb, r1)) : uc_sub(lbuf_get(xb, r1), 0, o1);
       +        post = lnmode ? uc_dup("\n") : uc_sub(lbuf_get(xb, r2), o2, -1);
       +        rep = vi_input(pref, post, &row, &off);
                if (rep) {
                        lbuf_rm(xb, r1, r2 + 1);
                        lbuf_put(xb, r1, rep);
                        xrow = r1 + row - 1;
       -                xcol = col;
       +                xoff = off;
                        free(rep);
                }
                free(pref);
       t@@ -766,8 +611,6 @@ static void vi_pipe(int r1, int r2)
                char *cmd = vi_prompt("!", &kmap);
                if (!cmd)
                        return;
       -        if (r2 < r1)
       -                swap(&r1, &r2);
                text = lbuf_cp(xb, r1, r2 + 1);
                rep = cmd_pipe(cmd, text);
                if (rep) {
       t@@ -784,8 +627,6 @@ static void vi_shift(int r1, int r2, int dir)
                struct sbuf *sb;
                char *ln;
                int i;
       -        if (r2 < r1)
       -                swap(&r1, &r2);
                for (i = r1; i <= r2; i++) {
                        if (!(ln = lbuf_get(xb, i)))
                                continue;
       t@@ -800,42 +641,49 @@ static void vi_shift(int r1, int r2, int dir)
                        sbuf_free(sb);
                }
                xrow = r1;
       -        lbuf_postindents(xb, &xrow, &xcol);
       +        xoff = lbuf_indents(xb, xrow);
        }
        
        static int vc_motion(int cmd)
        {
                int r1 = xrow, r2 = xrow;        /* region rows */
       -        int c1 = xcol, c2 = xcol;        /* visual region columns */
       +        int o1 = xoff, o2 = xoff;        /* visual region columns */
                int lnmode = 0;                        /* line-based region */
       -        int closed = 1;                        /* include the last character */
                int mv;
                vi_arg2 = vi_prefix();
                if (vi_arg2 < 0)
                        return 1;
       -        c1 = ren_noeol(lbuf_get(xb, r1), xcol);
       -        c2 = c1;
       +        o1 = ren_noeol(lbuf_get(xb, r1), o1);
       +        o2 = o1;
                if ((mv = vi_motionln(&r2, cmd))) {
       -                c2 = -1;
       -        } else if (!(mv = vi_motion(&r2, &c2))) {
       +                o2 = -1;
       +        } else if (!(mv = vi_motion(&r2, &o2))) {
                        vi_read();
                        return 1;
                }
                if (mv < 0)
                        return 1;
       -        if (!strchr("fFtTeE", mv))
       -                closed = 0;
       -        lnmode = c2 < 0;
       +        lnmode = o2 < 0;
                if (lnmode) {
       -                lbuf_eol(xb, &r1, &c1, -1);
       -                lbuf_eol(xb, &r2, &c2, +1);
       +                o1 = 0;
       +                o2 = lbuf_eol(xb, r2);
                }
       +        if (r1 > r2) {
       +                swap(&r1, &r2);
       +                swap(&o1, &o2);
       +        }
       +        if (r1 == r2 && o1 > o2)
       +                swap(&o1, &o2);
       +        o1 = ren_noeol(lbuf_get(xb, r1), o1);
       +        if (!lnmode && strchr("fFtTeE", mv))
       +                if (o2 < lbuf_eol(xb, r2))
       +                        o2 = ren_noeol(lbuf_get(xb, r2), o2) + 1;
                if (cmd == 'y')
       -                vi_yank(r1, c1, r2, c2, lnmode, closed);
       +                vi_yank(r1, o1, r2, o2, lnmode);
                if (cmd == 'd')
       -                vi_delete(r1, c1, r2, c2, lnmode, closed);
       +                vi_delete(r1, o1, r2, o2, lnmode);
                if (cmd == 'c')
       -                vi_change(r1, c1, r2, c2, lnmode, closed);
       +                vi_change(r1, o1, r2, o2, lnmode);
                if (cmd == '!')
                        vi_pipe(r1, r2);
                if (cmd == '>' || cmd == '<')
       t@@ -847,24 +695,22 @@ static int vc_insert(int cmd)
        {
                char *pref, *post;
                char *ln = lbuf_get(xb, xrow);
       -        int row, col, off = 0;
       +        int row, off = 0;
                char *rep;
                if (cmd == 'I')
       -                lbuf_postindents(xb, &xrow, &xcol);
       -        if (cmd == 'A') {
       -                lbuf_eol(xb, &xrow, &xcol, +1);
       -                lbuf_lnnext(xb, &xrow, &xcol, -1);
       -        }
       -        xcol = ren_noeol(ln, xcol);
       +                xoff = lbuf_indents(xb, xrow);
       +        if (cmd == 'A')
       +                xoff = lbuf_eol(xb, xrow);
       +        xoff = ren_noeol(ln, xoff);
                if (cmd == 'o')
                        xrow += 1;
                if (cmd == 'i' || cmd == 'I')
       -                off = vi_insertionoffset(ln, xcol, 1);
       +                off = xoff;
                if (cmd == 'a' || cmd == 'A')
       -                off = vi_insertionoffset(ln, xcol, 0);
       +                off = xoff + 1;
                pref = ln && cmd != 'o' && cmd != 'O' ? uc_sub(ln, 0, off) : vi_indents(ln);
                post = ln && cmd != 'o' && cmd != 'O' ? uc_sub(ln, off, -1) : uc_dup("\n");
       -        rep = vi_input(pref, post, &row, &col);
       +        rep = vi_input(pref, post, &row, &off);
                if ((cmd == 'o' || cmd == 'O') && !lbuf_len(xb))
                        lbuf_put(xb, 0, "\n");
                if (rep) {
       t@@ -872,7 +718,7 @@ static int vc_insert(int cmd)
                                lbuf_rm(xb, xrow, xrow + 1);
                        lbuf_put(xb, xrow, rep);
                        xrow += row - 1;
       -                xcol = col;
       +                xoff = off;
                        free(rep);
                }
                free(pref);
       t@@ -892,7 +738,7 @@ static int vc_put(int cmd)
                if (!buf)
                        return 1;
                ln = lnmode ? NULL : lbuf_get(xb, xrow);
       -        off = vi_insertionoffset(ln, ren_noeol(ln, xcol), cmd == 'P');
       +        off = ren_noeol(ln, xoff) + (cmd == 'p');
                if (cmd == 'p' && !ln)
                        xrow++;
                sb = sbuf_make();
       t@@ -914,9 +760,9 @@ static int vc_put(int cmd)
                        lbuf_rm(xb, xrow, xrow + 1);
                lbuf_put(xb, xrow, sbuf_buf(sb));
                if (ln)
       -                xcol = ren_pos(lbuf_get(xb, xrow), off + uc_slen(buf) * cnt - 1);
       +                xoff = off + uc_slen(buf) * cnt - 1;
                else
       -                lbuf_postindents(xb, &xrow, &xcol);
       +                xoff = lbuf_indents(xb, xrow);
                sbuf_free(sb);
                return 0;
        }
       t@@ -958,7 +804,7 @@ static int vc_join(void)
                sbuf_chr(sb, '\n');
                lbuf_rm(xb, beg, end);
                lbuf_put(xb, beg, sbuf_buf(sb));
       -        xcol = ren_pos(sbuf_buf(sb), off);
       +        xoff = off;
                sbuf_free(sb);
                return 0;
        }
       t@@ -983,10 +829,10 @@ static int vi_scrollbackward(int cnt)
        
        static void vc_status(void)
        {
       -        int pos = ren_noeol(lbuf_get(xb, xrow), xcol);
       +        int col = vi_off2col(xb, xrow, xoff);
                snprintf(vi_msg, sizeof(vi_msg), "\"%s\" line %d of %d, col %d\n",
                        xpath[0] ? xpath : "unnamed", xrow + 1, lbuf_len(xb),
       -                ren_cursor(lbuf_get(xb, xrow), pos) + 1);
       +                ren_cursor(lbuf_get(xb, xrow), col) + 1);
        }
        
        static int vc_replace(void)
       t@@ -1000,7 +846,7 @@ static int vc_replace(void)
                int off, i;
                if (!ln || !cs)
                        return 1;
       -        off = ren_off(ln, ren_noeol(ln, xcol));
       +        off = ren_noeol(ln, xoff);
                s = uc_chr(ln, off);
                for (i = 0; s[0] != '\n' && i < cnt; i++)
                        s = uc_next(s);
       t@@ -1016,7 +862,7 @@ static int vc_replace(void)
                lbuf_rm(xb, xrow, xrow + 1);
                lbuf_put(xb, xrow, sbuf_buf(sb));
                off += cnt - 1;
       -        xcol = ren_pos(sbuf_buf(sb), off);
       +        xoff = off;
                sbuf_free(sb);
                free(pref);
                free(post);
       t@@ -1025,19 +871,21 @@ static int vc_replace(void)
        
        static void vi(void)
        {
       +        int xcol;
                int mark;
                char *ln;
                char *kmap = NULL;
                term_init();
                xtop = 0;
                xrow = 0;
       -        lbuf_eol(xb, &xrow, &xcol, -1);
       -        vi_draw();
       +        xoff = 0;
       +        xcol = vi_off2col(xb, xrow, xoff);
       +        vi_draw(xcol);
                term_pos(xrow, led_pos(lbuf_get(xb, xrow), xcol));
                while (!xquit) {
                        int redraw = 0;
                        int nrow = xrow;
       -                int ncol = ren_noeol(lbuf_get(xb, xrow), xcol);
       +                int noff = ren_noeol(lbuf_get(xb, xrow), xoff);
                        int otop = xtop;
                        int mv, n;
                        vi_arg2 = 0;
       t@@ -1045,20 +893,20 @@ static void vi(void)
                        vi_arg1 = vi_prefix();
                        if (!vi_ybuf)
                                vi_ybuf = vi_yankbuf();
       -                mv = vi_motion(&nrow, &ncol);
       +                mv = vi_motion(&nrow, &noff);
                        if (mv > 0) {
       -                        if (strchr("\'GHML/?{}[]", mv))
       +                        if (strchr("\'GHML/?{}[]nN", mv))
                                        lbuf_mark(xb, '\'', xrow);
                                xrow = nrow;
       -                        if (ncol < 0) {
       -                                if (!strchr("jk", mv))
       -                                        lbuf_postindents(xb, &xrow, &xcol);
       -                        } else {
       -                                if (strchr("|$", mv))
       -                                        xcol = ncol;
       -                                else
       -                                        xcol = ren_noeol(lbuf_get(xb, xrow), ncol);
       -                        }
       +                        if (noff < 0 && !strchr("jk", mv))
       +                                noff = lbuf_indents(xb, xrow);
       +                        if (strchr("jk", mv))
       +                                noff = vi_col2off(xb, xrow, xcol);
       +                        xoff = noff;
       +                        if (!strchr("|jk", mv))
       +                                xcol = vi_off2col(xb, xrow, noff);
       +                        if (mv == '|')
       +                                xcol = vi_pcol;
                        } else if (mv == 0) {
                                int c = vi_read();
                                int z;
       t@@ -1068,13 +916,13 @@ static void vi(void)
                                case TK_CTL('b'):
                                        if (vi_scrollbackward(MAX(1, vi_arg1) * (xrows - 1)))
                                                break;
       -                                lbuf_postindents(xb, &xrow, &xcol);
       +                                xoff = lbuf_indents(xb, xrow);
                                        redraw = 1;
                                        break;
                                case TK_CTL('f'):
                                        if (vi_scrollforeward(MAX(1, vi_arg1) * (xrows - 1)))
                                                break;
       -                                lbuf_postindents(xb, &xrow, &xcol);
       +                                xoff = lbuf_indents(xb, xrow);
                                        redraw = 1;
                                        break;
                                case TK_CTL('e'):
       t@@ -1219,8 +1067,11 @@ static void vi(void)
                        if (xtop + xrows <= xrow)
                                xtop = xtop + xrows + xrows / 2 <= xrow ?
                                                xrow - xrows / 2 : xrow - xrows + 1;
       +                xoff = ren_noeol(lbuf_get(xb, xrow), xoff);
       +                if (redraw)
       +                        xcol = vi_off2col(xb, xrow, xoff);
                        if (redraw || xtop != otop)
       -                        vi_draw();
       +                        vi_draw(xcol);
                        if (vi_msg[0])
                                vi_drawmsg();
                        term_pos(xrow - xtop, led_pos(lbuf_get(xb, xrow),
 (DIR) diff --git a/vi.h b/vi.h
       t@@ -21,6 +21,15 @@ void lbuf_undo(struct lbuf *lbuf);
        void lbuf_redo(struct lbuf *lbuf);
        void lbuf_undomark(struct lbuf *lbuf);
        void lbuf_undofree(struct lbuf *lbuf);
       +int lbuf_indents(struct lbuf *lb, int r);
       +int lbuf_eol(struct lbuf *lb, int r);
       +/* motions */
       +int lbuf_findchar(struct lbuf *lb, char *cs, int cmd, int n, int *r, int *o);
       +int lbuf_search(struct lbuf *lb, char *kw, int dir, int *r, int *o, int *len);
       +int lbuf_paragraphbeg(struct lbuf *lb, int dir, int *row, int *off);
       +int lbuf_sectionbeg(struct lbuf *lb, int dir, int *row, int *off);
       +int lbuf_wordbeg(struct lbuf *lb, int big, int dir, int *row, int *off);
       +int lbuf_wordend(struct lbuf *lb, int big, int dir, int *row, int *off);
        
        /* string buffer, variable-sized string */
        struct sbuf *sbuf_make(void);
       t@@ -83,8 +92,9 @@ int uc_isalpha(char *s);
        int uc_kind(char *c);
        char **uc_chop(char *s, int *n);
        char *uc_next(char *s);
       +char *uc_prev(char *beg, char *s);
        char *uc_beg(char *beg, char *s);
       -char *uc_end(char *beg, char *s);
       +char *uc_end(char *s);
        char *uc_shape(char *beg, char *s);
        char *uc_lastline(char *s);
        
       t@@ -130,7 +140,7 @@ char *cmd_pipe(char *cmd, char *s);
        #define SYN_BD                0x100
        #define SYN_IT                0x200
        #define SYN_RV                0x400
       -#define SYN_ATTR(f, b)        (((b) << 16) | (f))
       +#define SYN_BGMK(b)        ((b) << 16)
        #define SYN_FG(a)        ((a) & 0xffff)
        #define SYN_BG(a)        ((a) >> 16)
        
       t@@ -153,7 +163,7 @@ int conf_filetype(int idx, char **ft, char **pat);
        extern int xvis;
        extern struct lbuf *xb;
        extern int xrow;
       -extern int xcol;
       +extern int xoff;
        extern int xtop;
        extern int xled;
        extern int xrow_alt;