tvi: rely on line offset instead of column number - neatvi - [fork] simple vi-type editor with UTF-8 support
git clone git://src.adamsgaard.dk/neatvi
Log
Files
Refs
README
---
commit bdbc8d37c53bd7702979786c58354bbf2ef6990e
parent 2dc24f819262d48c8a7f6b2d1ce6a7fe992b4310
Author: Ali Gholami Rudi 
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(-)
---
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
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);
diff --git a/mot.c b/mot.c
t@@ -0,0 +1,186 @@
+#include 
+#include 
+#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;
+}
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)
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)
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),
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;