tneatvi: the skeleton - 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 b1c10bc71e9c7d3c7db8cbd85adf17aa63f9736b
 (HTM) Author: Ali Gholami Rudi <ali@rudi.ir>
       Date:   Fri,  1 May 2015 17:56:55 +0430
       
       neatvi: the skeleton
       
       Diffstat:
         A Makefile                            |      13 +++++++++++++
         A ex.c                                |     438 +++++++++++++++++++++++++++++++
         A kmap.h                              |      98 +++++++++++++++++++++++++++++++
         A lbuf.c                              |     236 +++++++++++++++++++++++++++++++
         A led.c                               |     144 +++++++++++++++++++++++++++++++
         A ren.c                               |     270 +++++++++++++++++++++++++++++++
         A sbuf.c                              |      94 +++++++++++++++++++++++++++++++
         A term.c                              |     105 +++++++++++++++++++++++++++++++
         A uc.c                                |     303 +++++++++++++++++++++++++++++++
         A vi.c                                |     548 +++++++++++++++++++++++++++++++
         A vi.h                                |     102 +++++++++++++++++++++++++++++++
       
       11 files changed, 2351 insertions(+), 0 deletions(-)
       ---
 (DIR) diff --git a/Makefile b/Makefile
       t@@ -0,0 +1,13 @@
       +CC = cc
       +CFLAGS = -Wall -O2
       +LDFLAGS =
       +
       +OBJS = vi.o ex.o lbuf.o sbuf.o ren.o led.o uc.o term.o
       +
       +all: vi
       +%.o: %.c
       +        $(CC) -c $(CFLAGS) $<
       +vi: $(OBJS)
       +        $(CC) -o $@ $(OBJS) $(LDFLAGS)
       +clean:
       +        rm -f *.o vi
 (DIR) diff --git a/ex.c b/ex.c
       t@@ -0,0 +1,438 @@
       +#include <ctype.h>
       +#include <fcntl.h>
       +#include <regex.h>
       +#include <stdio.h>
       +#include <stdlib.h>
       +#include <string.h>
       +#include <sys/stat.h>
       +#include <unistd.h>
       +#include "vi.h"
       +
       +#define EXLEN                512
       +
       +/* read an input line; ex's input function */
       +static char *ex_read(char *msg)
       +{
       +        struct sbuf *sb;
       +        char c;
       +        if (xled) {
       +                char *s = led_prompt(msg, "");
       +                printf("\n");
       +                return s;
       +        }
       +        sb = sbuf_make();
       +        while ((c = getchar()) != EOF) {
       +                if (c == '\n')
       +                        break;
       +                sbuf_chr(sb, c);
       +        }
       +        if (c == EOF) {
       +                sbuf_free(sb);
       +                return NULL;
       +        }
       +        return sbuf_done(sb);
       +}
       +
       +/* print an output line; ex's output function */
       +static void ex_show(char *msg)
       +{
       +        if (xled)
       +                led_print(msg, -1);
       +        else
       +                printf("%s", msg);
       +}
       +
       +/* read ex command location */
       +static char *ex_loc(char *s, char *loc)
       +{
       +        while (*s == ':' || isspace((unsigned char) *s))
       +                s++;
       +        while (*s && !isalpha((unsigned char) *s) && *s != '=') {
       +                if (*s == '\'')
       +                        *loc++ = *s++;
       +                if (*s == '/' || *s == '?') {
       +                        int d = *s;
       +                        *loc++ = *s++;
       +                        while (*s && *s != d) {
       +                                if (*s == '\\' && s[1])
       +                                        *loc++ = *s++;
       +                                *loc++ = *s++;
       +                        }
       +                }
       +                *loc++ = *s++;
       +        }
       +        *loc = '\0';
       +        return s;
       +}
       +
       +/* read ex command name */
       +static char *ex_cmd(char *s, char *cmd)
       +{
       +        char *cmd0 = cmd;
       +        s = ex_loc(s, cmd);
       +        while (isspace((unsigned char) *s))
       +                s++;
       +        while (isalpha((unsigned char) *s) || *s == '=' || *s == '!')
       +                if ((*cmd++ = *s++) == 'k' && cmd == cmd0 + 1)
       +                        break;
       +        *cmd = '\0';
       +        return s;
       +}
       +
       +/* read ex command argument */
       +static char *ex_arg(char *s, char *arg)
       +{
       +        s = ex_cmd(s, arg);
       +        while (isspace((unsigned char) *s))
       +                s++;
       +        while (*s && !isspace((unsigned char) *s))
       +                *arg++ = *s++;
       +        *arg = '\0';
       +        return s;
       +}
       +
       +static int ex_search(char *pat)
       +{
       +        struct sbuf *kwd;
       +        int dir = *pat == '/' ? 1 : -1;
       +        char *b = pat;
       +        char *e = b;
       +        int i = xrow;
       +        regex_t re;
       +        kwd = sbuf_make();
       +        while (*++e) {
       +                if (*e == *pat)
       +                        break;
       +                sbuf_chr(kwd, (unsigned char) *e);
       +                if (*e == '\\' && e[1])
       +                        e++;
       +        }
       +        regcomp(&re, sbuf_buf(kwd), 0);
       +        while (i >= 0 && i < lbuf_len(xb)) {
       +                if (!regexec(&re, lbuf_get(xb, i), 0, NULL, 0))
       +                        break;
       +                i += dir;
       +        }
       +        regfree(&re);
       +        sbuf_free(kwd);
       +        return i;
       +}
       +
       +static int ex_lineno(char *num)
       +{
       +        int n = xrow;
       +        if (!num[0] || num[0] == '.')
       +                n = xrow;
       +        if (isdigit(num[0]))
       +                n = atoi(num) - 1;
       +        if (num[0] == '$')
       +                n = lbuf_len(xb) - 1;
       +        if (num[0] == '-')
       +                n = xrow - (num[1] ? ex_lineno(num + 1) : 1);
       +        if (num[0] == '+')
       +                n = xrow + (num[1] ? ex_lineno(num + 1) : 1);
       +        if (num[0] == '\'')
       +                n = lbuf_markpos(xb, num[1]);
       +        if (num[0] == '/' && num[1])
       +                n = ex_search(num);
       +        if (num[0] == '?' && num[1])
       +                n = ex_search(num);
       +        return n;
       +}
       +
       +/* parse ex command location */
       +static int ex_region(char *loc, int *beg, int *end)
       +{
       +        int naddr = 0;
       +        if (!strcmp("%", loc)) {
       +                *beg = 0;
       +                *end = MAX(0, lbuf_len(xb));
       +                return 0;
       +        }
       +        if (!*loc) {
       +                *beg = xrow;
       +                *end = xrow == lbuf_len(xb) ? xrow : xrow + 1;
       +                return 0;
       +        }
       +        while (*loc) {
       +                char *r = loc;
       +                while (*loc && *loc != ';' && *loc != ',')
       +                        loc++;
       +                *beg = *end;
       +                *end = ex_lineno(r) + 1;
       +                if (!naddr++)
       +                        *beg = *end - 1;
       +                if (!*loc)
       +                        break;
       +                if (*loc == ';')
       +                        xrow = *end - 1;
       +                loc++;
       +        }
       +        if (*beg < 0 || *beg >= lbuf_len(xb))
       +                return 1;
       +        if (*end < *beg || *end > lbuf_len(xb))
       +                return 1;
       +        return 0;
       +}
       +
       +static void ec_edit(char *ec)
       +{
       +        char arg[EXLEN];
       +        int fd;
       +        ex_arg(ec, arg);
       +        fd = open(arg, O_RDONLY);
       +        if (fd >= 0) {
       +                lbuf_rm(xb, 0, lbuf_len(xb));
       +                lbuf_rd(xb, fd, 0);
       +                lbuf_undofree(xb);
       +                close(fd);
       +                xrow = MAX(0, lbuf_len(xb) - 1);
       +        }
       +        snprintf(xpath, PATHLEN, "%s", arg);
       +}
       +
       +static void ec_read(char *ec)
       +{
       +        char arg[EXLEN], loc[EXLEN];
       +        int fd;
       +        int beg, end;
       +        int n = lbuf_len(xb);
       +        ex_arg(ec, arg);
       +        ex_loc(ec, loc);
       +        fd = open(arg[0] ? arg : xpath, O_RDONLY);
       +        if (fd >= 0 && !ex_region(loc, &beg, &end)) {
       +                lbuf_rd(xb, fd, lbuf_len(xb) ? end : 0);
       +                close(fd);
       +                xrow = end + lbuf_len(xb) - n;
       +        }
       +}
       +
       +static void ec_write(char *ec)
       +{
       +        char arg[EXLEN], loc[EXLEN];
       +        char *path;
       +        int beg, end;
       +        int fd;
       +        ex_arg(ec, arg);
       +        ex_loc(ec, loc);
       +        path = arg[0] ? arg : xpath;
       +        if (ex_region(loc, &beg, &end))
       +                return;
       +        if (!loc[0]) {
       +                beg = 0;
       +                end = lbuf_len(xb);
       +        }
       +        fd = open(path, O_WRONLY | O_CREAT, 0600);
       +        if (fd >= 0) {
       +                lbuf_wr(xb, fd, beg, end);
       +                close(fd);
       +        }
       +}
       +
       +static void ec_insert(char *ec)
       +{
       +        char arg[EXLEN], cmd[EXLEN], loc[EXLEN];
       +        struct sbuf *sb;
       +        char *s;
       +        int beg, end;
       +        int n;
       +        ex_arg(ec, arg);
       +        ex_cmd(ec, cmd);
       +        ex_loc(ec, loc);
       +        if (ex_region(loc, &beg, &end) && (beg != 0 || end != 0))
       +                return;
       +        if (cmd[0] == 'c') {
       +                if (lbuf_len(xb))
       +                        lbuf_rm(xb, beg, end);
       +                end = beg + 1;
       +        }
       +        sb = sbuf_make();
       +        while ((s = ex_read(""))) {
       +                if (!strcmp(".", s)) {
       +                        free(s);
       +                        break;
       +                }
       +                sbuf_str(sb, s);
       +                sbuf_chr(sb, '\n');
       +                free(s);
       +        }
       +        if (cmd[0] == 'a')
       +                if (end > lbuf_len(xb))
       +                        end = lbuf_len(xb);
       +        n = lbuf_len(xb);
       +        lbuf_put(xb, end, sbuf_buf(sb));
       +        xrow = MIN(lbuf_len(xb) - 1, end + lbuf_len(xb) - n - 1);
       +        sbuf_free(sb);
       +}
       +
       +static void ec_print(char *ec)
       +{
       +        char cmd[EXLEN], loc[EXLEN];
       +        int beg, end;
       +        int i;
       +        ex_cmd(ec, cmd);
       +        ex_loc(ec, loc);
       +        if (!cmd[0] && !loc[0]) {
       +                if (xrow >= lbuf_len(xb) - 1)
       +                        return;
       +                xrow = xrow + 1;
       +        }
       +        if (!ex_region(loc, &beg, &end)) {
       +                for (i = beg; i < end; i++)
       +                        ex_show(lbuf_get(xb, i));
       +                xrow = end;
       +        }
       +}
       +
       +static void ec_delete(char *ec)
       +{
       +        char loc[EXLEN];
       +        int beg, end;
       +        ex_loc(ec, loc);
       +        if (!ex_region(loc, &beg, &end) && lbuf_len(xb)) {
       +                lbuf_rm(xb, beg, end);
       +                xrow = beg;
       +        }
       +}
       +
       +static void ec_lnum(char *ec)
       +{
       +        char loc[EXLEN];
       +        char msg[128];
       +        int beg, end;
       +        ex_loc(ec, loc);
       +        if (ex_region(loc, &beg, &end))
       +                return;
       +        sprintf(msg, "%d\n", end);
       +        ex_show(msg);
       +}
       +
       +static void ec_undo(char *ec)
       +{
       +        lbuf_undo(xb);
       +}
       +
       +static void ec_redo(char *ec)
       +{
       +        lbuf_redo(xb);
       +}
       +
       +static void ec_mark(char *ec)
       +{
       +        char loc[EXLEN], arg[EXLEN];
       +        int beg, end;
       +        ex_arg(ec, arg);
       +        ex_loc(ec, loc);
       +        if (ex_region(loc, &beg, &end))
       +                return;
       +        lbuf_mark(xb, arg[0], end - 1);
       +}
       +
       +static char *readuntil(char **src, int delim)
       +{
       +        struct sbuf *sbuf = sbuf_make();
       +        char *s = *src;
       +        /* reading the pattern */
       +        while (*s && *s != delim) {
       +                if (s[0] == '\\' && s[1])
       +                        sbuf_chr(sbuf, (unsigned char) *s++);
       +                sbuf_chr(sbuf, (unsigned char) *s++);
       +        }
       +        if (*s)                        /* skipping the delimiter */
       +                s++;
       +        *src = s;
       +        return sbuf_done(sbuf);
       +}
       +
       +static void ec_substitute(char *ec)
       +{
       +        char loc[EXLEN], arg[EXLEN];
       +        regmatch_t subs[16];
       +        regex_t re;
       +        int beg, end;
       +        char *pat, *rep;
       +        char *s = arg;
       +        int delim;
       +        int i;
       +        ex_arg(ec, arg);
       +        ex_loc(ec, loc);
       +        if (ex_region(loc, &beg, &end))
       +                return;
       +        delim = (unsigned char) *s++;
       +        pat = readuntil(&s, delim);
       +        rep = readuntil(&s, delim);
       +        regcomp(&re, pat, 0);
       +        for (i = beg; i < end; i++) {
       +                char *ln = lbuf_get(xb, i);
       +                if (!regexec(&re, ln, LEN(subs), subs, 0)) {
       +                        struct sbuf *r = sbuf_make();
       +                        sbuf_mem(r, ln, subs[0].rm_so);
       +                        sbuf_str(r, rep);
       +                        sbuf_str(r, ln + subs[0].rm_eo);
       +                        lbuf_put(xb, i, sbuf_buf(r));
       +                        lbuf_rm(xb, i + 1, i + 2);
       +                        sbuf_free(r);
       +                }
       +        }
       +        regfree(&re);
       +        free(pat);
       +        free(rep);
       +}
       +
       +static void ec_quit(char *ec)
       +{
       +        xquit = 1;
       +}
       +
       +static struct excmd {
       +        char *abbr;
       +        char *name;
       +        void (*ec)(char *s);
       +} excmds[] = {
       +        {"p", "print", ec_print},
       +        {"a", "append", ec_insert},
       +        {"i", "insert", ec_insert},
       +        {"d", "delete", ec_delete},
       +        {"c", "change", ec_insert},
       +        {"e", "edit", ec_edit},
       +        {"=", "=", ec_lnum},
       +        {"k", "mark", ec_mark},
       +        {"q", "quit", ec_quit},
       +        {"r", "read", ec_read},
       +        {"w", "write", ec_write},
       +        {"u", "undo", ec_undo},
       +        {"r", "redo", ec_redo},
       +        {"s", "substitute", ec_substitute},
       +        {"", "", ec_print},
       +};
       +
       +/* execute a single ex command */
       +void ex_command(char *ln0)
       +{
       +        char cmd[EXLEN];
       +        char *ln = ln0 ? ln0 : ex_read(":");
       +        int i;
       +        if (!ln)
       +                return;
       +        ex_cmd(ln, cmd);
       +        for (i = 0; i < LEN(excmds); i++) {
       +                if (!strcmp(excmds[i].abbr, cmd) || !strcmp(excmds[i].name, cmd)) {
       +                        excmds[i].ec(ln);
       +                        break;
       +                }
       +        }
       +        if (!ln0)
       +                free(ln);
       +        lbuf_undomark(xb);
       +}
       +
       +/* ex main loop */
       +void ex(void)
       +{
       +        if (xled)
       +                term_init();
       +        while (!xquit)
       +                ex_command(NULL);
       +        if (xled)
       +                term_done();
       +}
 (DIR) diff --git a/kmap.h b/kmap.h
       t@@ -0,0 +1,98 @@
       +static char *kmap_def[256];
       +
       +static char *kmap_farsi[256] = {
       +        ['`'] = "‍",
       +        ['1'] = "۱",
       +        ['2'] = "۲",
       +        ['3'] = "۳",
       +        ['4'] = "۴",
       +        ['5'] = "۵",
       +        ['6'] = "۶",
       +        ['7'] = "۷",
       +        ['8'] = "۸",
       +        ['9'] = "۹",
       +        ['0'] = "۰",
       +        ['-'] = "-",
       +        ['='] = "=",
       +        ['q'] = "ض",
       +        ['w'] = "ص",
       +        ['e'] = "ث",
       +        ['r'] = "ق",
       +        ['t'] = "ف",
       +        ['y'] = "غ",
       +        ['u'] = "ع",
       +        ['i'] = "ه",
       +        ['o'] = "خ",
       +        ['p'] = "ح",
       +        ['['] = "ج",
       +        [']'] = "چ",
       +        ['a'] = "ش",
       +        ['s'] = "س",
       +        ['d'] = "ی",
       +        ['f'] = "ب",
       +        ['g'] = "ل",
       +        ['h'] = "ا",
       +        ['j'] = "ت",
       +        ['k'] = "ن",
       +        ['l'] = "م",
       +        [';'] = "ک",
       +        ['\''] = "گ",
       +        ['z'] = "ظ",
       +        ['x'] = "ط",
       +        ['c'] = "ز",
       +        ['v'] = "ر",
       +        ['b'] = "ذ",
       +        ['n'] = "د",
       +        ['m'] = "پ",
       +        [','] = "و",
       +        ['.'] = ".",
       +        ['/'] = "/",
       +        ['\\'] = "\\",
       +        ['~'] = "÷",
       +        ['!'] = "!",
       +        ['@'] = "٬",
       +        ['#'] = "٫",
       +        ['$'] = "﷼",
       +        ['%'] = "٪",
       +        ['^'] = "×",
       +        ['&'] = "،",
       +        ['*'] = "*",
       +        ['('] = ")",
       +        [')'] = "(",
       +        ['_'] = "ـ",
       +        ['+'] = "+",
       +        ['Q'] = "ْ",
       +        ['W'] = "ٌ",
       +        ['E'] = "ٍ",
       +        ['R'] = "ً",
       +        ['T'] = "ُ",
       +        ['Y'] = "ِ",
       +        ['U'] = "َ",
       +        ['I'] = "ّ",
       +        ['O'] = "]",
       +        ['P'] = "[",
       +        ['{'] = "}",
       +        ['}'] = "{",
       +        ['A'] = "ؤ",
       +        ['S'] = "ئ",
       +        ['D'] = "ي",
       +        ['F'] = "إ",
       +        ['G'] = "أ",
       +        ['H'] = "آ",
       +        ['J'] = "ة",
       +        ['K'] = "»",
       +        ['L'] = "«",
       +        [':'] = ":",
       +        ['"'] = "؛",
       +        ['Z'] = "ك",
       +        ['X'] = "ٓ",
       +        ['C'] = "ژ",
       +        ['V'] = "ٰ",
       +        ['B'] = "‌",
       +        ['N'] = "ٔ",
       +        ['M'] = "ء",
       +        ['<'] = ">",
       +        ['>'] = "<",
       +        ['?'] = "؟",
       +        ['|'] = "|",
       +};
 (DIR) diff --git a/lbuf.c b/lbuf.c
       t@@ -0,0 +1,236 @@
       +#include <stdlib.h>
       +#include <stdio.h>
       +#include <string.h>
       +#include <unistd.h>
       +#include "vi.h"
       +
       +#define MARK(c)                ((c) >= 'a' && (c) <= 'z' ? (c) - 'a' : 30)
       +
       +/* line operations */
       +struct lopt {
       +        char *buf;                /* text inserted or deleted */
       +        int ins;                /* insertion operation if non-zero */
       +        int beg, end;
       +        int seq;                /* operation number */
       +};
       +
       +/* line buffers */
       +struct lbuf {
       +        int mark[32];                /* buffer marks */
       +        struct lopt hist[128];        /* buffer history */
       +        int undo;                /* current index into hist[] */
       +        int useq;                /* current operation sequence */
       +        char **ln;                /* lines */
       +        int ln_n;                /* number of lbuf in l[] */
       +        int ln_sz;                /* size of l[] */
       +};
       +
       +struct lbuf *lbuf_make(void)
       +{
       +        struct lbuf *lb = malloc(sizeof(*lb));
       +        int i;
       +        memset(lb, 0, sizeof(*lb));
       +        for (i = 0; i < LEN(lb->mark); i++)
       +                lb->mark[i] = -1;
       +        return lb;
       +}
       +
       +void lbuf_free(struct lbuf *lb)
       +{
       +        int i;
       +        for (i = 0; i < lb->ln_n; i++)
       +                free(lb->ln[i]);
       +        for (i = 0; i < LEN(lb->hist); i++)
       +                free(lb->hist[i].buf);
       +        free(lb->ln);
       +        free(lb);
       +}
       +
       +/* insert a line at pos */
       +static void lbuf_insertline(struct lbuf *lb, int pos, char *s)
       +{
       +        if (lb->ln_n == lb->ln_sz) {
       +                int nsz = lb->ln_sz + 512;
       +                char **nln = malloc(nsz * sizeof(nln[0]));
       +                memcpy(nln, lb->ln, lb->ln_n * sizeof(lb->ln[0]));
       +                free(lb->ln);
       +                lb->ln = nln;
       +                lb->ln_sz = nsz;
       +        }
       +        lb->ln_n++;
       +        memmove(lb->ln + pos + 1, lb->ln + pos,
       +                (lb->ln_n - pos) * sizeof(lb->ln[0]));
       +        lb->ln[pos] = s;
       +}
       +
       +/* low-level insertion */
       +static void lbuf_insert(struct lbuf *lb, int pos, char *s)
       +{
       +        int len = strlen(s);
       +        struct sbuf *sb;
       +        int lb_len = lbuf_len(lb);
       +        int i;
       +        sb = sbuf_make();
       +        for (i = 0; i < len; i++) {
       +                sbuf_chr(sb, (unsigned char) s[i]);
       +                if (s[i] == '\n') {
       +                        lbuf_insertline(lb, pos++, sbuf_done(sb));
       +                        sb = sbuf_make();
       +                }
       +        }
       +        sbuf_free(sb);
       +        for (i = 0; i < LEN(lb->mark); i++)        /* updating marks */
       +                if (lb->mark[i] >= pos)
       +                        lb->mark[i] += lbuf_len(lb) - lb_len;
       +}
       +
       +/* low-level deletion */
       +static void lbuf_delete(struct lbuf *lb, int beg, int end)
       +{
       +        int i;
       +        for (i = beg; i < end; i++)
       +                free(lb->ln[i]);
       +        memmove(lb->ln + beg, lb->ln + end, (lb->ln_n - end) * sizeof(lb->ln[0]));
       +        lb->ln_n -= end - beg;
       +        for (i = 0; i < LEN(lb->mark); i++)        /* updating marks */
       +                if (lb->mark[i] > beg)
       +                        lb->mark[i] = MAX(beg, lb->mark[i] + beg - end);
       +}
       +
       +/* append undo/redo history */
       +static void lbuf_opt(struct lbuf *lb, int ins, int beg, int end)
       +{
       +        struct lopt *lo = &lb->hist[0];
       +        int n = LEN(lb->hist);
       +        int i;
       +        if (lb->undo) {
       +                for (i = 0; i < lb->undo; i++)
       +                        free(lb->hist[i].buf);
       +                memmove(lb->hist + 1, lb->hist + lb->undo,
       +                        (n - lb->undo) * sizeof(lb->hist[0]));
       +                for (i = n - lb->undo + 1; i < n; i++)
       +                        lb->hist[i].buf = NULL;
       +        } else {
       +                free(lb->hist[n - 1].buf);
       +                memmove(lb->hist + 1, lb->hist, (n - 1) * sizeof(lb->hist[0]));
       +        }
       +        lo->ins = ins;
       +        lo->beg = beg;
       +        lo->end = end;
       +        lo->buf = lbuf_cp(lb, beg, end);
       +        lo->seq = lb->useq;
       +        lb->undo = 0;
       +}
       +
       +void lbuf_rd(struct lbuf *lbuf, int fd, int pos)
       +{
       +        char buf[1 << 8];
       +        struct sbuf *sb;
       +        int nr;
       +        sb = sbuf_make();
       +        while ((nr = read(fd, buf, sizeof(buf))) > 0)
       +                sbuf_mem(sb, buf, nr);
       +        lbuf_put(lbuf, pos, sbuf_buf(sb));
       +        sbuf_free(sb);
       +}
       +
       +void lbuf_wr(struct lbuf *lbuf, int fd, int beg, int end)
       +{
       +        int i;
       +        for (i = beg; i < end; i++)
       +                write(fd, lbuf->ln[i], strlen(lbuf->ln[i]));
       +}
       +
       +void lbuf_rm(struct lbuf *lb, int beg, int end)
       +{
       +        if (end > lb->ln_n)
       +                end = lb->ln_n;
       +        lbuf_opt(lb, 0, beg, end);
       +        lbuf_delete(lb, beg, end);
       +}
       +
       +void lbuf_put(struct lbuf *lb, int pos, char *s)
       +{
       +        int lb_len = lbuf_len(lb);
       +        lbuf_insert(lb, pos, s);
       +        lbuf_opt(lb, 1, pos, pos + lbuf_len(lb) - lb_len);
       +}
       +
       +char *lbuf_cp(struct lbuf *lb, int beg, int end)
       +{
       +        struct sbuf *sb;
       +        int i;
       +        sb = sbuf_make();
       +        for (i = beg; i < end; i++)
       +                if (i < lb->ln_n)
       +                        sbuf_str(sb, lb->ln[i]);
       +        return sbuf_done(sb);
       +}
       +
       +char *lbuf_get(struct lbuf *lb, int pos)
       +{
       +        return pos >= 0 && pos < lb->ln_n ? lb->ln[pos] : NULL;
       +}
       +
       +int lbuf_len(struct lbuf *lb)
       +{
       +        return lb->ln_n;
       +}
       +
       +void lbuf_mark(struct lbuf *lbuf, int mark, int pos)
       +{
       +        lbuf->mark[MARK(mark)] = pos;
       +}
       +
       +int lbuf_markpos(struct lbuf *lbuf, int mark)
       +{
       +        return lbuf->mark[MARK(mark)];
       +}
       +
       +static struct lopt *lbuf_lopt(struct lbuf *lb, int i)
       +{
       +        struct lopt *lo = &lb->hist[i];
       +        return i >= 0 && i < LEN(lb->hist) && lo->buf ? lo : NULL;
       +}
       +
       +void lbuf_undo(struct lbuf *lb)
       +{
       +        struct lopt *lo = lbuf_lopt(lb, lb->undo);
       +        int useq = lo ? lo->seq : 0;
       +        while (lo && lo->seq == useq) {
       +                lb->undo++;
       +                if (lo->ins)
       +                        lbuf_delete(lb, lo->beg, lo->end);
       +                else
       +                        lbuf_insert(lb, lo->beg, lo->buf);
       +                lo = lbuf_lopt(lb, lb->undo);
       +        }
       +}
       +
       +void lbuf_redo(struct lbuf *lb)
       +{
       +        struct lopt *lo = lbuf_lopt(lb, lb->undo - 1);
       +        int useq = lo ? lo->seq : 0;
       +        while (lo && lo->seq == useq) {
       +                lb->undo--;
       +                if (lo->ins)
       +                        lbuf_insert(lb, lo->beg, lo->buf);
       +                else
       +                        lbuf_delete(lb, lo->beg, lo->end);
       +                lo = lbuf_lopt(lb, lb->undo - 1);
       +        }
       +}
       +
       +void lbuf_undofree(struct lbuf *lb)
       +{
       +        int i;
       +        for (i = 0; i < LEN(lb->hist); i++)
       +                free(lb->hist[i].buf);
       +        memset(lb->hist, 0, sizeof(lb->hist));
       +        lb->undo = 0;
       +}
       +
       +void lbuf_undomark(struct lbuf *lbuf)
       +{
       +        lbuf->useq++;
       +}
 (DIR) diff --git a/led.c b/led.c
       t@@ -0,0 +1,144 @@
       +#include <stdio.h>
       +#include <string.h>
       +#include <stdlib.h>
       +#include <unistd.h>
       +#include "vi.h"
       +#include "kmap.h"
       +
       +static char **led_kmap = kmap_def;
       +
       +static char *keymap(char **kmap, int c)
       +{
       +        static char cs[4];
       +        cs[0] = c;
       +        return kmap[c] ? kmap[c] : cs;
       +}
       +
       +char *led_keymap(int c)
       +{
       +        return c >= 0 ? keymap(led_kmap, c) : NULL;
       +}
       +
       +void led_print(char *s, int row)
       +{
       +        char *r = ren_all(s, -1);
       +        term_pos(row, 0);
       +        term_kill();
       +        term_str(r);
       +        free(r);
       +}
       +
       +static int led_lastchar(char *s)
       +{
       +        char *r = *s ? strchr(s, '\0') : s;
       +        if (r != s)
       +                r = uc_beg(s, r - 1);
       +        return r - s;
       +}
       +
       +static void led_printparts(char *pref, char *main, char *post)
       +{
       +        struct sbuf *ln;
       +        int col;
       +        int cur;
       +        int dir;
       +        ln = sbuf_make();
       +        sbuf_str(ln, pref);
       +        sbuf_str(ln, main);
       +        cur = uc_slen(sbuf_buf(ln)) - 1;
       +        dir = uc_dir(sbuf_buf(ln) + led_lastchar(sbuf_buf(ln)));
       +        sbuf_str(ln, post);
       +        led_print(sbuf_buf(ln), -1);
       +        col = ren_cursor(sbuf_buf(ln), ren_pos(sbuf_buf(ln), MAX(cur, 0)));
       +        if (cur >= 0) {
       +                if (dir < 0 || (!dir && ren_dir(sbuf_buf(ln)) < 0))
       +                        col = MAX(col - 1, 0);
       +                else
       +                        col += 1;
       +        }
       +        term_pos(-1, col);
       +        sbuf_free(ln);
       +}
       +
       +static char *led_line(char *pref, char *post, int *key, char ***kmap)
       +{
       +        struct sbuf *sb;
       +        int c;
       +        sb = sbuf_make();
       +        if (!pref)
       +                pref = "";
       +        if (!post)
       +                post = "";
       +        while (1) {
       +                led_printparts(pref, sbuf_buf(sb), post);
       +                c = term_read(-1);
       +                switch (c) {
       +                case TERMCTRL('f'):
       +                        *kmap = *kmap == kmap_def ? kmap_farsi : kmap_def;
       +                        continue;
       +                case TERMCTRL('h'):
       +                case 127:
       +                        if (sbuf_len(sb))
       +                                sbuf_cut(sb, led_lastchar(sbuf_buf(sb)));
       +                        break;
       +                case TERMCTRL('u'):
       +                        sbuf_cut(sb, 0);
       +                        break;
       +                case TERMCTRL('v'):
       +                        sbuf_chr(sb, term_read(-1));
       +                        break;
       +                default:
       +                        if (c == '\n' || c == TERMESC || c < 0)
       +                                break;
       +                        sbuf_str(sb, keymap(*kmap, c));
       +                }
       +                if (c == '\n' || c == TERMESC || c < 0)
       +                        break;
       +        }
       +        *key = c;
       +        return sbuf_done(sb);
       +}
       +
       +/* read an ex command */
       +char *led_prompt(char *pref, char *post)
       +{
       +        char **kmap = kmap_def;
       +        char *s;
       +        int key;
       +        s = led_line(pref, post, &key, &kmap);
       +        if (key == '\n')
       +                return s;
       +        free(s);
       +        return NULL;
       +}
       +
       +/* read visual command input */
       +char *led_input(char *pref, char *post, int *row, int *col)
       +{
       +        struct sbuf *sb = sbuf_make();
       +        int key;
       +        *row = 0;
       +        while (1) {
       +                char *ln = led_line(pref, post, &key, &led_kmap);
       +                if (pref)
       +                        sbuf_str(sb, pref);
       +                sbuf_str(sb, ln);
       +                if (key == '\n')
       +                        sbuf_chr(sb, '\n');
       +                *col = ren_last(pref ? sbuf_buf(sb) : ln);
       +                led_printparts(pref ? pref : "", ln, key == '\n' ? "" : post);
       +                if (key == '\n')
       +                        term_chr('\n');
       +                pref = NULL;
       +                term_kill();
       +                free(ln);
       +                if (key != '\n')
       +                        break;
       +                (*row)++;
       +        }
       +        sbuf_str(sb, post);
       +        if (key == TERMESC)
       +                return sbuf_done(sb);
       +        sbuf_free(sb);
       +        return NULL;
       +}
 (DIR) diff --git a/ren.c b/ren.c
       t@@ -0,0 +1,270 @@
       +/* rendering strings */
       +/*
       + * Overview:
       + * + ren_translate() replaces the characters if necessary.
       + * + ren_position() specifies the position of characters on the screen.
       + * + ren_reorder() is called by ren_position() and changes the order of characters.
       + * + ren_highlight() performs syntax highlighting.
       + */
       +#include <ctype.h>
       +#include <stdio.h>
       +#include <stdlib.h>
       +#include <string.h>
       +#include "vi.h"
       +
       +static int bidi_maximalregion(char *s, int n, int dir, char **chrs, int idx, int *beg, int *end)
       +{
       +        while (idx < n && uc_dir(chrs[idx]) * dir >= 0)
       +                idx++;
       +        *beg = idx;
       +        *end = idx;
       +        while (idx < n && uc_dir(chrs[idx]) * dir <= 0) {
       +                if (uc_dir(chrs[idx]) * dir < 0)
       +                        *end = idx + 1;
       +                idx++;
       +        }
       +        return *beg >= *end;
       +}
       +
       +static void bidi_reverse(int *ord, int beg, int end)
       +{
       +        end--;
       +        while (beg < end) {
       +                int tmp = ord[beg];
       +                ord[beg] = ord[end];
       +                ord[end] = tmp;
       +                beg++;
       +                end--;
       +        }
       +}
       +
       +int ren_dir(char *s)
       +{
       +        return +1;
       +}
       +
       +/* reorder the characters in s */
       +static void ren_reorder(char *s, int *ord)
       +{
       +        int beg = 0, end = 0, n;
       +        char **chrs = uc_chop(s, &n);
       +        int dir = ren_dir(s);
       +        while (!bidi_maximalregion(s, n, dir, chrs, end, &beg, &end))
       +                bidi_reverse(ord, beg, end);
       +        free(chrs);
       +}
       +
       +/* specify the screen position of the characters in s */
       +static int *ren_position(char *s, int *beg, int *end)
       +{
       +        int i, n;
       +        char **chrs = uc_chop(s, &n);
       +        int *off, *pos;
       +        int diff = 0;
       +        int dir = ren_dir(s);
       +        pos = malloc(n * sizeof(pos[0]));
       +        for (i = 0; i < n; i++)
       +                pos[i] = i;
       +        ren_reorder(s, pos);
       +        off = malloc(n * sizeof(off[0]));
       +        for (i = 0; i < n; i++)
       +                off[pos[i]] = i;
       +        for (i = 0; i < n; i++) {
       +                pos[off[i]] += diff;
       +                if (*chrs[i] == '\t')
       +                        diff += 8 - (pos[off[i]] & 7);
       +        }
       +        if (beg)
       +                *beg = 0;
       +        if (end)
       +                *end = n + diff;
       +        if (dir < 0) {
       +                if (beg)
       +                        *beg = xcols - *end - 1;
       +                if (end)
       +                        *end = xcols - 1;
       +                for (i = 0; i < n; i++)
       +                        pos[i] = xcols - pos[i] - 1;
       +        }
       +        free(chrs);
       +        free(off);
       +        return pos;
       +}
       +
       +static char *ren_translate(char *s)
       +{
       +        struct sbuf *sb = sbuf_make();
       +        char *r = s;
       +        while (*r) {
       +                char *c = uc_shape(s, r);
       +                if (!strcmp(c, "‌"))
       +                        c = "-";
       +                if (!strcmp(c, "‍"))
       +                        c = "-";
       +                sbuf_str(sb, c);
       +                r = uc_next(r);
       +        }
       +        return sbuf_done(sb);
       +}
       +
       +char *ren_all(char *s0, int wid)
       +{
       +        int n, w = 0;
       +        int *pos;        /* pos[i]: the screen position of the i-th character */
       +        int *off;        /* off[i]: the character at screen position i */
       +        char **chrs;        /* chrs[i]: the i-th character in s1 */
       +        char *s1;
       +        struct sbuf *out;
       +        int i;
       +        s1 = ren_translate(s0 ? s0 : "");
       +        chrs = uc_chop(s1, &n);
       +        pos = ren_position(s1, NULL, NULL);
       +        for (i = 0; i < n; i++)
       +                if (w <= pos[i])
       +                        w = pos[i] + 1;
       +        off = malloc(w * sizeof(off[0]));
       +        memset(off, 0xff, w * sizeof(off[0]));
       +        for (i = 0; i < n; i++)
       +                off[pos[i]] = i;
       +        out = sbuf_make();
       +        for (i = 0; i < w; i++) {
       +                if (off[i] >= 0 && uc_isprint(chrs[off[i]]))
       +                        sbuf_mem(out, chrs[off[i]], uc_len(chrs[off[i]]));
       +                else
       +                        sbuf_chr(out, ' ');
       +        }
       +        free(pos);
       +        free(off);
       +        free(chrs);
       +        free(s1);
       +        return sbuf_done(out);
       +}
       +
       +int ren_last(char *s)
       +{
       +        int n = uc_slen(s);
       +        int *pos = ren_position(s, NULL, NULL);
       +        int ret = n ? pos[n - 1] : 0;
       +        free(pos);
       +        return ret;
       +}
       +
       +/* find the next character after visual position p; if cur start from p itself */
       +static int pos_next(int *pos, int n, int p, int cur)
       +{
       +        int i, ret = -1;
       +        for (i = 0; i < n; i++)
       +                if (pos[i] - !cur >= p && (ret < 0 || pos[i] < pos[ret]))
       +                        ret = i;
       +        return ret >= 0 ? pos[ret] : -1;
       +}
       +
       +/* find the previous character after visual position p; if cur start from p itself */
       +static int pos_prev(int *pos, int n, int p, int cur)
       +{
       +        int i, ret = -1;
       +        for (i = 0; i < n; i++)
       +                if (pos[i] + !cur <= p && (ret < 0 || pos[i] > pos[ret]))
       +                        ret = i;
       +        return ret >= 0 ? pos[ret] : -1;
       +}
       +
       +/* convert visual position to character offset */
       +int ren_pos(char *s, int off)
       +{
       +        int n = uc_slen(s);
       +        int *pos = ren_position(s, NULL, NULL);
       +        int ret = off < n ? pos[off] : 0;
       +        free(pos);
       +        return ret;
       +}
       +
       +/* convert visual position to character offset */
       +int ren_off(char *s, int p)
       +{
       +        int off = -1;
       +        int n = uc_slen(s);
       +        int *pos = ren_position(s, NULL, NULL);
       +        int i;
       +        if (ren_dir(s) >= 0)
       +                p = pos_prev(pos, n, p, 1);
       +        else
       +                p = pos_next(pos, n, p, 1);
       +        for (i = 0; i < n; i++)
       +                if (pos[i] == p)
       +                        off = i;
       +        free(pos);
       +        return off >= 0 ? off : 0;
       +}
       +
       +/* adjust cursor position */
       +int ren_cursor(char *s, int p)
       +{
       +        int dir = ren_dir(s ? s : "");
       +        int n, next;
       +        int beg, end;
       +        int *pos;
       +        if (!s)
       +                return 0;
       +        n = uc_slen(s);
       +        pos = ren_position(s, &beg, &end);
       +        if (dir >= 0)
       +                p = pos_prev(pos, n, p, 1);
       +        else
       +                p = pos_next(pos, n, p, 1);
       +        if (dir >= 0)
       +                next = pos_next(pos, n, p, 0);
       +        else
       +                next = pos_prev(pos, n, p, 0);
       +        p = (next >= 0 ? next : (dir >= 0 ? end : beg)) - dir;
       +        free(pos);
       +        return p >= 0 ? p : 0;
       +}
       +
       +/* the position of the next character */
       +int ren_next(char *s, int p, int dir)
       +{
       +        int n = uc_slen(s);
       +        int *pos = ren_position(s, NULL, NULL);
       +        if (ren_dir(s ? s : "") >= 0)
       +                p = pos_prev(pos, n, p, 1);
       +        else
       +                p = pos_next(pos, n, p, 1);
       +        if (dir * ren_dir(s ? s : "") >= 0)
       +                p = pos_next(pos, n, p, 0);
       +        else
       +                p = pos_prev(pos, n, p, 0);
       +        free(pos);
       +        return p;
       +}
       +
       +int ren_eol(char *s, int dir)
       +{
       +        int beg, end;
       +        int *pos = ren_position(s, &beg, &end);
       +        free(pos);
       +        return dir * ren_dir(s) >= 0 ? end : beg;
       +}
       +
       +/* compare two visual positions */
       +int ren_cmp(char *s, int pos1, int pos2)
       +{
       +        return ren_dir(s ? s : "") >= 0 ? pos1 - pos2 : pos2 - pos1;
       +}
       +
       +/*
       + * insertion offset before or after the given visual position
       + *
       + * When pre is nonzero, the return value indicates an offset of s,
       + * which, if a character is inserted at that position, it appears
       + * just before the character at pos.  If pre is zero, the inserted
       + * character should appear just after the character at pos.
       + */
       +int ren_insertionoffset(char *s, int pos, int pre)
       +{
       +        int l1;
       +        if (!s)
       +                return 0;
       +        l1 = ren_off(s, pos);
       +        return pre ? l1 : l1 + 1;
       +}
 (DIR) diff --git a/sbuf.c b/sbuf.c
       t@@ -0,0 +1,94 @@
       +/* variable length string buffer */
       +#include <stdarg.h>
       +#include <stdio.h>
       +#include <stdlib.h>
       +#include <string.h>
       +#include "vi.h"
       +
       +#define SBUFSZ                128
       +#define ALIGN(n, a)        (((n) + (a) - 1) & ~((a) - 1))
       +
       +struct sbuf {
       +        char *s;                /* allocated buffer */
       +        int s_n;                /* length of the string stored in s[] */
       +        int s_sz;                /* size of memory allocated for s[] */
       +};
       +
       +static void sbuf_extend(struct sbuf *sbuf, int newsz)
       +{
       +        char *s = sbuf->s;
       +        sbuf->s_sz = newsz;
       +        sbuf->s = malloc(sbuf->s_sz);
       +        if (sbuf->s_n)
       +                memcpy(sbuf->s, s, sbuf->s_n);
       +        free(s);
       +}
       +
       +struct sbuf *sbuf_make(void)
       +{
       +        struct sbuf *sb = malloc(sizeof(*sb));
       +        memset(sb, 0, sizeof(*sb));
       +        return sb;
       +}
       +
       +char *sbuf_buf(struct sbuf *sb)
       +{
       +        if (!sb->s)
       +                sbuf_extend(sb, 1);
       +        sb->s[sb->s_n] = '\0';
       +        return sb->s;
       +}
       +
       +char *sbuf_done(struct sbuf *sb)
       +{
       +        char *s = sbuf_buf(sb);
       +        free(sb);
       +        return s;
       +}
       +
       +void sbuf_free(struct sbuf *sb)
       +{
       +        free(sb->s);
       +        free(sb);
       +}
       +
       +void sbuf_chr(struct sbuf *sbuf, int c)
       +{
       +        if (sbuf->s_n + 2 >= sbuf->s_sz)
       +                sbuf_extend(sbuf, sbuf->s_sz + SBUFSZ);
       +        sbuf->s[sbuf->s_n++] = c;
       +}
       +
       +void sbuf_mem(struct sbuf *sbuf, char *s, int len)
       +{
       +        if (sbuf->s_n + len + 1 >= sbuf->s_sz)
       +                sbuf_extend(sbuf, ALIGN(sbuf->s_n + len + 1, SBUFSZ));
       +        memcpy(sbuf->s + sbuf->s_n, s, len);
       +        sbuf->s_n += len;
       +}
       +
       +void sbuf_str(struct sbuf *sbuf, char *s)
       +{
       +        sbuf_mem(sbuf, s, strlen(s));
       +}
       +
       +int sbuf_len(struct sbuf *sbuf)
       +{
       +        return sbuf->s_n;
       +}
       +
       +void sbuf_cut(struct sbuf *sb, int len)
       +{
       +        if (sb->s_n > len)
       +                sb->s_n = len;
       +}
       +
       +void sbuf_printf(struct sbuf *sbuf, char *s, ...)
       +{
       +        char buf[256];
       +        va_list ap;
       +        va_start(ap, s);
       +        vsnprintf(buf, sizeof(buf), s, ap);
       +        va_end(ap);
       +        sbuf_str(sbuf, buf);
       +}
 (DIR) diff --git a/term.c b/term.c
       t@@ -0,0 +1,105 @@
       +#include <poll.h>
       +#include <stdio.h>
       +#include <stdlib.h>
       +#include <string.h>
       +#include <sys/ioctl.h>
       +#include <termios.h>
       +#include <unistd.h>
       +#include "vi.h"
       +
       +static struct sbuf *term_sbuf;
       +static int rows = 25, cols = 80;
       +static struct termios termios;
       +
       +void term_init(void)
       +{
       +        struct winsize win;
       +        struct termios newtermios;
       +        tcgetattr(0, &termios);
       +        newtermios = termios;
       +        newtermios.c_lflag &= ~ICANON;
       +        newtermios.c_lflag &= ~ECHO;
       +        tcsetattr(0, TCSAFLUSH, &newtermios);
       +        if (!ioctl(0, TIOCGWINSZ, &win)) {
       +                cols = win.ws_col;
       +                rows = win.ws_row;
       +        }
       +}
       +
       +void term_done(void)
       +{
       +        term_commit();
       +        tcsetattr(0, 0, &termios);
       +}
       +
       +void term_record(void)
       +{
       +        if (!term_sbuf)
       +                term_sbuf = sbuf_make();
       +}
       +
       +void term_commit(void)
       +{
       +        if (term_sbuf) {
       +                write(1, sbuf_buf(term_sbuf), sbuf_len(term_sbuf));
       +                sbuf_free(term_sbuf);
       +                term_sbuf = NULL;
       +        }
       +}
       +
       +static void term_out(char *s)
       +{
       +        if (term_sbuf)
       +                sbuf_str(term_sbuf, s);
       +        else
       +                write(1, s, strlen(s));
       +}
       +
       +void term_str(char *s)
       +{
       +        term_out(s);
       +}
       +
       +void term_chr(int ch)
       +{
       +        char s[4] = {ch};
       +        term_out(s);
       +}
       +
       +void term_kill(void)
       +{
       +        term_out("\33[K");
       +}
       +
       +void term_pos(int r, int c)
       +{
       +        char buf[32] = "\r";
       +        if (r < 0)
       +                sprintf(buf, "\r\33[%d%c", abs(c), c > 0 ? 'C' : 'D');
       +        else
       +                sprintf(buf, "\33[%d;%dH", r + 1, c + 1);
       +        term_out(buf);
       +}
       +
       +int term_rows(void)
       +{
       +        return rows;
       +}
       +
       +int term_cols(void)
       +{
       +        return cols;
       +}
       +
       +int term_read(int ms)
       +{
       +        struct pollfd ufds[1];
       +        char b;
       +        ufds[0].fd = 0;
       +        ufds[0].events = POLLIN;
       +        if (poll(ufds, 1, ms * 1000) <= 0)
       +                return -1;
       +        if (read(0, &b, 1) <= 0)
       +                return -1;
       +        return (unsigned char) b;
       +}
 (DIR) diff --git a/uc.c b/uc.c
       t@@ -0,0 +1,303 @@
       +#include <ctype.h>
       +#include <stdio.h>
       +#include <stdlib.h>
       +#include <string.h>
       +#include "vi.h"
       +
       +#define LEN(a)                (sizeof(a) / sizeof((a)[0]))
       +
       +/* return the length of a utf-8 character */
       +int uc_len(char *s)
       +{
       +        int c = (unsigned char) s[0];
       +        if (c > 0 && c <= 0x7f)
       +                return 1;
       +        if (c >= 0xfc)
       +                return 6;
       +        if (c >= 0xf8)
       +                return 5;
       +        if (c >= 0xf0)
       +                return 4;
       +        if (c >= 0xe0)
       +                return 3;
       +        if (c >= 0xc0)
       +                return 2;
       +        return c != 0;
       +}
       +
       +/* the number of utf-8 characters in s */
       +int uc_slen(char *s)
       +{
       +        char *e = s + strlen(s);
       +        int i;
       +        for (i = 0; s < e; i++)
       +                s += uc_len(s);
       +        return i;
       +}
       +
       +/* the unicode codepoint of the given utf-8 character */
       +int uc_code(char *s)
       +{
       +        int result;
       +        int l = uc_len(s);
       +        if (l <= 1)
       +                return (unsigned char) *s;
       +        result = (0x3f >> --l) & (unsigned char) *s++;
       +        while (l--)
       +                result = (result << 6) | ((unsigned char) *s++ & 0x3f);
       +        return result;
       +}
       +
       +/* find the beginning of the character at s[i] */
       +char *uc_beg(char *beg, char *s)
       +{
       +        while (s > beg && (((unsigned char) *s) & 0xc0) == 0x80)
       +                s--;
       +        return s;
       +}
       +
       +/* find the end of the character at s[i] */
       +char *uc_end(char *beg, char *s)
       +{
       +        if (!*s || !((unsigned char) *s & 0x80))
       +                return s;
       +        if (((unsigned char) *s & 0xc0) == 0xc0)
       +                s++;
       +        while (((unsigned char) *s & 0xc0) == 0x80)
       +                s++;
       +        return s - 1;
       +}
       +
       +/* return a pointer to the character following s */
       +char *uc_next(char *s)
       +{
       +        s = uc_end(s, s);
       +        return *s ? s + 1 : s;
       +}
       +
       +int uc_wid(char *s)
       +{
       +        return 1;
       +}
       +
       +/* allocate and return an array for the characters in s */
       +char **uc_chop(char *s, int *n)
       +{
       +        char **chrs;
       +        int i;
       +        *n = uc_slen(s);
       +        chrs = malloc(*n * sizeof(chrs[0]));
       +        for (i = 0; i < *n; i++) {
       +                chrs[i] = s;
       +                s = uc_next(s);
       +        }
       +        return chrs;
       +}
       +
       +int uc_isspace(char *s)
       +{
       +        int c = s ? (unsigned char) *s : 0;
       +        return c <= 0x7f && isspace(c);
       +}
       +
       +int uc_isprint(char *s)
       +{
       +        int c = s ? (unsigned char) *s : 0;
       +        return c > 0x7f || isprint(c);
       +}
       +
       +#define UC_R2L(ch)        (((ch) & 0xff00) == 0x0600 || \
       +                        ((ch) & 0xfffc) == 0x200c || \
       +                        ((ch) & 0xff00) == 0xfb00 || \
       +                        ((ch) & 0xff00) == 0xfc00 || \
       +                        ((ch) & 0xff00) == 0xfe00)
       +
       +/* sorted list of characters that can be shaped */
       +static struct achar {
       +        unsigned c;                /* utf-8 code */
       +        unsigned s;                /* single form */
       +        unsigned i;                /* initial form */
       +        unsigned m;                /* medial form */
       +        unsigned f;                /* final form */
       +} achars[] = {
       +        {0x0621, 0xfe80},                                /* hamza */
       +        {0x0622, 0xfe81, 0, 0, 0xfe82},                        /* alef madda */
       +        {0x0623, 0xfe83, 0, 0, 0xfe84},                        /* alef hamza above */
       +        {0x0624, 0xfe85, 0, 0, 0xfe86},                        /* waw hamza */
       +        {0x0625, 0xfe87, 0, 0, 0xfe88},                        /* alef hamza below */
       +        {0x0626, 0xfe89, 0xfe8b, 0xfe8c, 0xfe8a},        /* yeh hamza */
       +        {0x0627, 0xfe8d, 0, 0, 0xfe8e},                        /* alef */
       +        {0x0628, 0xfe8f, 0xfe91, 0xfe92, 0xfe90},        /* beh */
       +        {0x0629, 0xfe93, 0, 0, 0xfe94},                        /* teh marbuta */
       +        {0x062a, 0xfe95, 0xfe97, 0xfe98, 0xfe96},        /* teh */
       +        {0x062b, 0xfe99, 0xfe9b, 0xfe9c, 0xfe9a},        /* theh */
       +        {0x062c, 0xfe9d, 0xfe9f, 0xfea0, 0xfe9e},        /* jeem */
       +        {0x062d, 0xfea1, 0xfea3, 0xfea4, 0xfea2},        /* hah */
       +        {0x062e, 0xfea5, 0xfea7, 0xfea8, 0xfea6},        /* khah */
       +        {0x062f, 0xfea9, 0, 0, 0xfeaa},                        /* dal */
       +        {0x0630, 0xfeab, 0, 0, 0xfeac},                        /* thal */
       +        {0x0631, 0xfead, 0, 0, 0xfeae},                        /* reh */
       +        {0x0632, 0xfeaf, 0, 0, 0xfeb0},                        /* zain */
       +        {0x0633, 0xfeb1, 0xfeb3, 0xfeb4, 0xfeb2},        /* seen */
       +        {0x0634, 0xfeb5, 0xfeb7, 0xfeb8, 0xfeb6},        /* sheen */
       +        {0x0635, 0xfeb9, 0xfebb, 0xfebc, 0xfeba},        /* sad */
       +        {0x0636, 0xfebd, 0xfebf, 0xfec0, 0xfebe},        /* dad */
       +        {0x0637, 0xfec1, 0xfec3, 0xfec4, 0xfec2},        /* tah */
       +        {0x0638, 0xfec5, 0xfec7, 0xfec8, 0xfec6},        /* zah */
       +        {0x0639, 0xfec9, 0xfecb, 0xfecc, 0xfeca},        /* ain */
       +        {0x063a, 0xfecd, 0xfecf, 0xfed0, 0xfece},        /* ghain */
       +        {0x0640, 0x640, 0x640, 0x640},                        /* tatweel */
       +        {0x0641, 0xfed1, 0xfed3, 0xfed4, 0xfed2},        /* feh */
       +        {0x0642, 0xfed5, 0xfed7, 0xfed8, 0xfed6},        /* qaf */
       +        {0x0643, 0xfed9, 0xfedb, 0xfedc, 0xfeda},        /* kaf */
       +        {0x0644, 0xfedd, 0xfedf, 0xfee0, 0xfede},        /* lam */
       +        {0x0645, 0xfee1, 0xfee3, 0xfee4, 0xfee2},        /* meem */
       +        {0x0646, 0xfee5, 0xfee7, 0xfee8, 0xfee6},        /* noon */
       +        {0x0647, 0xfee9, 0xfeeb, 0xfeec, 0xfeea},        /* heh */
       +        {0x0648, 0xfeed, 0, 0, 0xfeee},                        /* waw */
       +        {0x0649, 0xfeef, 0, 0, 0xfef0},                        /* alef maksura */
       +        {0x064a, 0xfef1, 0xfef3, 0xfef4, 0xfef2},        /* yeh */
       +        {0x067e, 0xfb56, 0xfb58, 0xfb59, 0xfb57},        /* peh */
       +        {0x0686, 0xfb7a, 0xfb7c, 0xfb7d, 0xfb7b},        /* tcheh */
       +        {0x0698, 0xfb8a, 0, 0, 0xfb8b},                        /* jeh */
       +        {0x06a9, 0xfb8e, 0xfb90, 0xfb91, 0xfb8f},        /* fkaf */
       +        {0x06af, 0xfb92, 0xfb94, 0xfb95, 0xfb93},        /* gaf */
       +        {0x06cc, 0xfbfc, 0xfbfe, 0xfbff, 0xfbfd},        /* fyeh */
       +        {0x200c},                                        /* ZWNJ */
       +        {0x200d, 0, 0x200d, 0x200d},                        /* ZWJ */
       +};
       +
       +static struct achar *find_achar(int c)
       +{
       +        int h, m, l;
       +        h = LEN(achars);
       +        l = 0;
       +        /* using binary search to find c */
       +        while (l < h) {
       +                m = (h + l) >> 1;
       +                if (achars[m].c == c)
       +                        return &achars[m];
       +                if (c < achars[m].c)
       +                        h = m;
       +                else
       +                        l = m + 1;
       +        }
       +        return NULL;
       +}
       +
       +static int can_join(int c1, int c2)
       +{
       +        struct achar *a1 = find_achar(c1);
       +        struct achar *a2 = find_achar(c2);
       +        return a1 && a2 && (a1->i || a1->m) && (a2->f || a2->m);
       +}
       +
       +static int uc_cshape(int cur, int prev, int next)
       +{
       +        int c = cur;
       +        int join_prev, join_next;
       +        struct achar *ac = find_achar(c);
       +        if (!ac)                /* ignore non-Arabic characters */
       +                return c;
       +        join_prev = can_join(prev, c);
       +        join_next = can_join(c, next);
       +        if (join_prev && join_next)
       +                c = ac->m;
       +        if (join_prev && !join_next)
       +                c = ac->f;
       +        if (!join_prev && join_next)
       +                c = ac->i;
       +        if (!join_prev && !join_next)
       +                c = ac->c;        /* some fonts do not have a glyph for ac->s */
       +        return c ? c : cur;
       +}
       +
       +/*
       + * return nonzero for Arabic combining characters
       + *
       + * The standard Arabic diacritics:
       + * + 0x064b: fathatan
       + * + 0x064c: dammatan
       + * + 0x064d: kasratan
       + * + 0x064e: fatha
       + * + 0x064f: damma
       + * + 0x0650: kasra
       + * + 0x0651: shadda
       + * + 0x0652: sukun
       + * + 0x0653: madda above
       + * + 0x0654: hamza above
       + * + 0x0655: hamza below
       + * + 0x0670: superscript alef
       + */
       +static int uc_comb(int c)
       +{
       +        return (c >= 0x064b && c <= 0x0655) ||                /* the standard diacritics */
       +                (c >= 0xfc5e && c <= 0xfc63) ||                /* shadda ligatures */
       +                c == 0x0670;                                /* superscript alef */
       +}
       +
       +/* the direction of the given utf-8 character */
       +int uc_dir(char *s)
       +{
       +        int u, c = (unsigned char) s[0];
       +        if (c < 128 && (ispunct(c) || isspace(c)))
       +                return 0;
       +        if (c < 128 && isalnum(c))
       +                return 1;
       +        u = uc_code(s);
       +        if (UC_R2L(u))
       +                return -1;
       +        return 1;
       +}
       +
       +static void uc_cput(char *d, int c)
       +{
       +        int l = 0;
       +        if (c > 0xffff) {
       +                *d++ = 0xf0 | (c >> 18);
       +                l = 3;
       +        } else if (c > 0x7ff) {
       +                *d++ = 0xe0 | (c >> 12);
       +                l = 2;
       +        } else if (c > 0x7f) {
       +                *d++ = 0xc0 | (c >> 6);
       +                l = 1;
       +        } else {
       +                *d++ = c;
       +        }
       +        while (l--)
       +                *d++ = 0x80 | ((c >> (l * 6)) & 0x3f);
       +        *d = '\0';
       +}
       +
       +/* shape the given arabic character; returns a static buffer */
       +char *uc_shape(char *beg, char *s)
       +{
       +        static char out[16];
       +        char *r;
       +        int prev = 0;
       +        int next = 0;
       +        int curr = uc_code(s);
       +        if (!curr || !UC_R2L(curr)) {
       +                uc_cput(out, curr);
       +                return out;
       +        }
       +        r = s;
       +        while (r > beg) {
       +                r = uc_beg(beg, r - 1);
       +                if (!uc_comb(uc_code(r))) {
       +                        prev = uc_code(r);
       +                        break;
       +                }
       +        }
       +        r = s;
       +        while (*r) {
       +                r = uc_next(r);
       +                if (!uc_comb(uc_code(r))) {
       +                        next = uc_code(r);
       +                        break;
       +                }
       +        }
       +        uc_cput(out, uc_cshape(curr, prev, next));
       +        return out;
       +}
 (DIR) diff --git a/vi.c b/vi.c
       t@@ -0,0 +1,548 @@
       +/*
       + * neatvi editor
       + *
       + * Copyright (C) 2015 Ali Gholami Rudi <ali at rudi dot ir>
       + *
       + * This program is released under the Modified BSD license.
       + */
       +#include <ctype.h>
       +#include <fcntl.h>
       +#include <stdio.h>
       +#include <stdlib.h>
       +#include <string.h>
       +#include "vi.h"
       +
       +char xpath[PATHLEN];        /* current file */
       +struct lbuf *xb;        /* current buffer */
       +int xrow, xcol, xtop;        /* current row, column, and top row */
       +int xled = 1;                /* use the line editor */
       +int xquit;
       +
       +static void vi_draw(void)
       +{
       +        int i;
       +        term_record();
       +        for (i = xtop; i < xtop + xrows; i++) {
       +                char *s = lbuf_get(xb, i);
       +                led_print(s ? s : "~", i - xtop);
       +        }
       +        term_pos(xrow, xcol);
       +        term_commit();
       +}
       +
       +static int vi_buf[128];
       +static int vi_buflen;
       +
       +static int vi_read(void)
       +{
       +        return vi_buflen ? vi_buf[--vi_buflen] : term_read(1000);
       +}
       +
       +static void vi_back(int c)
       +{
       +        if (vi_buflen < sizeof(vi_buf))
       +                vi_buf[vi_buflen++] = c;
       +}
       +
       +static char *vi_char(void)
       +{
       +        return led_keymap(vi_read());
       +}
       +
       +static int vi_prefix(void)
       +{
       +        int pre = 0;
       +        int c = vi_read();
       +        if ((c >= '1' && c <= '9')) {
       +                while (isdigit(c)) {
       +                        pre = pre * 10 + c - '0';
       +                        c = vi_read();
       +                }
       +        }
       +        vi_back(c);
       +        return pre;
       +}
       +
       +static int lbuf_lnnext(struct lbuf *lb, int *r, int *c, int dir)
       +{
       +        char *ln = lbuf_get(lb, *r);
       +        int col = ren_next(ln, *c, dir);
       +        if (col < 0)
       +                return -1;
       +        *c = col;
       +        return 0;
       +}
       +
       +static void lbuf_eol(struct lbuf *lb, int *r, int *c, int dir)
       +{
       +        char *ln = lbuf_get(lb, *r);
       +        *c = ren_eol(ln ? ln : "", dir);
       +}
       +
       +static int lbuf_next(struct lbuf *lb, int *r, int *c, int dir)
       +{
       +        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;
       +        }
       +        return 0;
       +}
       +
       +/* return a static buffer to the character at visual position c of line r */
       +static char *lbuf_chr(struct lbuf *lb, int r, int c)
       +{
       +        static char chr[8];
       +        char *ln = lbuf_get(lb, r);
       +        int n;
       +        if (ln) {
       +                char **chrs = uc_chop(ln, &n);
       +                int off = ren_off(ln, c);
       +                if (off >= 0 && off < n) {
       +                        memcpy(chr, chrs[off], uc_len(chrs[off]));
       +                        chr[uc_len(chr)] = '\0';
       +                        free(chrs);
       +                        return chr;
       +                }
       +                free(chrs);
       +        }
       +        return NULL;
       +}
       +
       +static void lbuf_postindents(struct lbuf *lb, int *r, int *c)
       +{
       +        lbuf_eol(lb, r, c, -1);
       +        while (uc_isspace(lbuf_chr(lb, *r, *c)))
       +                if (lbuf_lnnext(lb, r, c, +1))
       +                        break;
       +}
       +
       +static void lbuf_findchar(struct lbuf *lb, int *row, int *col, char *cs, int dir, int n)
       +{
       +        int c = *col;
       +        if (!cs)
       +                return;
       +        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;
       +}
       +
       +static void lbuf_tochar(struct lbuf *lb, int *row, int *col, char *cs, int dir, int n)
       +{
       +        int c = *col;
       +        if (!cs)
       +                return;
       +        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;
       +                lbuf_lnnext(lb, row, col, -dir);
       +        }
       +}
       +
       +static int vi_motionln(int *row, int cmd, int pre1, int pre2)
       +{
       +        int pre = (pre1 ? pre1 : 1) * (pre2 ? pre2 : 1);
       +        int c = vi_read();
       +        int mark;
       +        switch (c) {
       +        case '\n':
       +                *row = MIN(*row + pre, lbuf_len(xb) - 1);
       +                break;
       +        case '-':
       +                *row = MAX(*row - pre, 0);
       +                break;
       +        case '\'':
       +                if ((mark = vi_read()) > 0 && (isalpha(mark) || mark == '\''))
       +                        if (lbuf_markpos(xb, mark) >= 0)
       +                                *row = lbuf_markpos(xb, mark);
       +                break;
       +        case 'j':
       +                *row = MIN(*row + pre, lbuf_len(xb) - 1);
       +                break;
       +        case 'k':
       +                *row = MAX(*row - pre, 0);
       +                break;
       +        case 'G':
       +                *row = (pre1 || pre2) ? pre - 1 : lbuf_len(xb) - 1;
       +                break;
       +        case 'H':
       +                if (lbuf_len(xb))
       +                        *row = MIN(xtop + pre - 1, lbuf_len(xb) - 1);
       +                else
       +                        *row = 0;
       +                break;
       +        case 'L':
       +                if (lbuf_len(xb))
       +                        *row = MIN(xtop + xrows - 1 - pre + 1, lbuf_len(xb) - 1);
       +                else
       +                        *row = 0;
       +                break;
       +        case 'M':
       +                if (lbuf_len(xb))
       +                        *row = MIN(xtop + xrows / 2, lbuf_len(xb) - 1);
       +                else
       +                        *row = 0;
       +                break;
       +        default:
       +                if (c == cmd) {
       +                        *row = MIN(*row + pre - 1, lbuf_len(xb) - 1);
       +                        break;
       +                }
       +                vi_back(c);
       +                return 0;
       +        }
       +        return c;
       +}
       +
       +static int vi_motion(int *row, int *col, int pre1, int pre2)
       +{
       +        int c = vi_read();
       +        int pre = (pre1 ? pre1 : 1) * (pre2 ? pre2 : 1);
       +        char *ln = lbuf_get(xb, *row);
       +        int dir = ren_dir(ln ? ln : "");
       +        int i;
       +        switch (c) {
       +        case ' ':
       +                for (i = 0; i < pre; i++)
       +                        if (lbuf_next(xb, row, col, 1))
       +                                break;
       +                break;
       +        case 'f':
       +                lbuf_findchar(xb, row, col, vi_char(), +1, pre);
       +                break;
       +        case 'F':
       +                lbuf_findchar(xb, row, col, vi_char(), -1, pre);
       +                break;
       +        case 'h':
       +                for (i = 0; i < pre; i++)
       +                        if (lbuf_lnnext(xb, row, col, -1 * dir))
       +                                break;
       +                break;
       +        case 'l':
       +                for (i = 0; i < pre; i++)
       +                        if (lbuf_lnnext(xb, row, col, +1 * dir))
       +                                break;
       +                break;
       +        case 't':
       +                lbuf_tochar(xb, row, col, vi_char(), 1, pre);
       +                break;
       +        case 'T':
       +                lbuf_tochar(xb, row, col, vi_char(), 0, pre);
       +                break;
       +        case 'B':
       +                if (!uc_isspace(lbuf_chr(xb, *row, *col)))
       +                        lbuf_next(xb, row, col, -1);
       +                while (uc_isspace(lbuf_chr(xb, *row, *col)))
       +                        if (lbuf_next(xb, row, col, -1))
       +                                break;
       +                while (!lbuf_next(xb, row, col, -1)) {
       +                        if (uc_isspace(lbuf_chr(xb, *row, *col))) {
       +                                lbuf_next(xb, row, col, 1);
       +                                break;
       +                        }
       +                }
       +                break;
       +        case 'E':
       +                if (!uc_isspace(lbuf_chr(xb, *row, *col)))
       +                        lbuf_next(xb, row, col, 1);
       +                while (uc_isspace(lbuf_chr(xb, *row, *col)))
       +                        if (lbuf_next(xb, row, col, 1))
       +                                break;
       +                while (!lbuf_next(xb, row, col, 1)) {
       +                        if (uc_isspace(lbuf_chr(xb, *row, *col))) {
       +                                lbuf_next(xb, row, col, -1);
       +                                break;
       +                        }
       +                }
       +                break;
       +        case 'W':
       +                while (!uc_isspace(lbuf_chr(xb, *row, *col)))
       +                        if (lbuf_next(xb, row, col, 1))
       +                                break;
       +                while (uc_isspace(lbuf_chr(xb, *row, *col)))
       +                        if (lbuf_next(xb, row, col, 1))
       +                                break;
       +                break;
       +        case '0':
       +                lbuf_eol(xb, row, col, -1);
       +                break;
       +        case '$':
       +                lbuf_eol(xb, row, col, +1);
       +                lbuf_lnnext(xb, row, col, -1);
       +                break;
       +        case 127:
       +        case TERMCTRL('h'):
       +                *col = ren_cursor(ln, *col);
       +                for (i = 0; i < pre; i++)
       +                        if (lbuf_next(xb, row, col, -1))
       +                                break;
       +                break;
       +        default:
       +                vi_back(c);
       +                return 0;
       +        }
       +        return c;
       +}
       +
       +static char *vi_strprefix(char *s, int off)
       +{
       +        struct sbuf *sb = sbuf_make();
       +        int n;
       +        char **chrs = uc_chop(s ? s : "", &n);
       +        if (n > 0)
       +                sbuf_mem(sb, s, chrs[MIN(n - 1, off)] - s);
       +        free(chrs);
       +        return sbuf_done(sb);
       +}
       +
       +static char *vi_strpostfix(char *s, int off)
       +{
       +        struct sbuf *sb = sbuf_make();
       +        int n;
       +        char **chrs = uc_chop(s ? s : "", &n);
       +        if (n >= 0 && off < n)
       +                sbuf_str(sb, chrs[off]);
       +        free(chrs);
       +        if (!sbuf_len(sb))
       +                sbuf_chr(sb, '\n');
       +        return sbuf_done(sb);
       +}
       +
       +static void swap(int *a, int *b)
       +{
       +        int t = *a;
       +        *a = *b;
       +        *b = t;
       +}
       +
       +static char *sdup(char *s)                /* strdup() */
       +{
       +        char *r = malloc(strlen(s) + 1);
       +        return r ? strcpy(r, s) : NULL;
       +}
       +
       +static void vc_motion(int c, int pre1)
       +{
       +        int r1 = xrow, r2 = xrow;        /* region rows */
       +        int c1 = xcol, c2 = xcol;        /* visual region columns */
       +        int l1, l2;                        /* logical region columns */
       +        int ln = 0;                        /* line-based region */
       +        int closed = 1;                        /* include the last character */
       +        int mv, i;
       +        char *pref = NULL;
       +        char *post = NULL;
       +        int pre2 = vi_prefix();
       +        if (pre2 < 0)
       +                return;
       +        if (vi_motionln(&r2, c, pre1, pre2)) {
       +                ln = 1;
       +                lbuf_eol(xb, &r1, &c1, -1);
       +                lbuf_eol(xb, &r2, &c2, +1);
       +        } else if ((mv = vi_motion(&r2, &c2, pre1, pre2))) {
       +                if (strchr("0bBhlwW ", mv))
       +                        closed = 0;
       +        } else {
       +                return;
       +        }
       +        /* make sure the first position is visually before the second */
       +        if (r2 < r1 || (r2 == r1 && ren_cmp(lbuf_get(xb, r1), c1, c2) > 0)) {
       +                swap(&r1, &r2);
       +                swap(&c1, &c2);
       +        }
       +        for (i = 0; i < 2; i++) {
       +                l1 = ren_insertionoffset(lbuf_get(xb, r1), c1, 1);
       +                l2 = ren_insertionoffset(lbuf_get(xb, r2), c2, !closed);
       +                if (r1 == r2 && l2 < l1)        /* offsets out of order */
       +                        swap(&l1, &l2);
       +        }
       +        pref = ln ? sdup("") : vi_strprefix(lbuf_get(xb, r1), l1);
       +        post = ln ? sdup("\n") : vi_strpostfix(lbuf_get(xb, r2), l2);
       +        if (c == 'd') {
       +                lbuf_rm(xb, r1, r2 + 1);
       +                if (!ln) {
       +                        struct sbuf *sb = sbuf_make();
       +                        sbuf_str(sb, pref);
       +                        sbuf_str(sb, post);
       +                        lbuf_put(xb, r1, sbuf_buf(sb));
       +                        sbuf_free(sb);
       +                }
       +                xrow = r1;
       +                xcol = c1;
       +                if (ln)
       +                        lbuf_postindents(xb, &xrow, &xcol);
       +        }
       +        if (c == 'c') {
       +                int row, col;
       +                char *rep = led_input(pref, post, &row, &col);
       +                if (rep) {
       +                        lbuf_rm(xb, r1, r2 + 1);
       +                        lbuf_put(xb, r1, rep);
       +                        xrow = r1 + row;
       +                        xcol = col;
       +                        free(rep);
       +                }
       +        }
       +        free(pref);
       +        free(post);
       +}
       +
       +static void vc_insert(int cmd)
       +{
       +        char *pref, *post;
       +        char *ln = lbuf_get(xb, xrow);
       +        int row, col, 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);
       +        }
       +        if (cmd == 'o')
       +                xrow += 1;
       +        if (cmd == 'o' || cmd == 'O')
       +                ln = NULL;
       +        if (cmd == 'i' || cmd == 'I')
       +                off = ln ? ren_insertionoffset(ln, xcol, 1) : 0;
       +        if (cmd == 'a' || cmd == 'A')
       +                off = ln ? ren_insertionoffset(ln, xcol, 0) : 0;
       +        pref = ln ? vi_strprefix(ln, off) : sdup("");
       +        post = ln ? vi_strpostfix(ln, off) : sdup("\n");
       +        rep = led_input(pref, post, &row, &col);
       +        if (rep) {
       +                if (cmd != 'o' && cmd != 'O')
       +                        lbuf_rm(xb, xrow, xrow + 1);
       +                lbuf_put(xb, xrow, rep);
       +                xrow += row;
       +                xcol = col;
       +                free(rep);
       +        }
       +        free(pref);
       +        free(post);
       +}
       +
       +static void vi(void)
       +{
       +        int mark;
       +        term_init();
       +        xtop = 0;
       +        xrow = 0;
       +        lbuf_eol(xb, &xrow, &xcol, -1);
       +        vi_draw();
       +        term_pos(xrow, xcol);
       +        while (!xquit) {
       +                int redraw = 0;
       +                int orow = xrow;
       +                int pre1, mv;
       +                if ((pre1 = vi_prefix()) < 0)
       +                        continue;
       +                if ((mv = vi_motionln(&xrow, 0, pre1, 0))) {
       +                        if (strchr("\'GHML", mv))
       +                                lbuf_mark(xb, '\'', orow);
       +                        if (!strchr("jk", mv))
       +                                lbuf_postindents(xb, &xrow, &xcol);
       +                } else if (!vi_motion(&xrow, &xcol, pre1, 0)) {
       +                        int c = vi_read();
       +                        if (c <= 0)
       +                                continue;
       +                        switch (c) {
       +                        case 'u':
       +                                lbuf_undo(xb);
       +                                redraw = 1;
       +                                break;
       +                        case TERMCTRL('b'):
       +                                xtop = MAX(0, xtop - xrows + 1);
       +                                xrow = xtop + xrows - 1;
       +                                redraw = 1;
       +                                break;
       +                        case TERMCTRL('f'):
       +                                if (lbuf_len(xb))
       +                                        xtop = MIN(lbuf_len(xb) - 1, xtop + xrows - 1);
       +                                else
       +                                        xtop = 0;
       +                                xrow = xtop;
       +                                redraw = 1;
       +                                break;
       +                        case TERMCTRL('r'):
       +                                lbuf_redo(xb);
       +                                redraw = 1;
       +                                break;
       +                        case ':':
       +                                term_pos(xrows, 0);
       +                                term_kill();
       +                                ex_command(NULL);
       +                                if (xquit)
       +                                        continue;
       +                                redraw = 1;
       +                                break;
       +                        case 'c':
       +                        case 'd':
       +                                vc_motion(c, pre1);
       +                                redraw = 1;
       +                                break;
       +                        case 'i':
       +                        case 'I':
       +                        case 'a':
       +                        case 'A':
       +                        case 'o':
       +                        case 'O':
       +                                vc_insert(c);
       +                                redraw = 1;
       +                                break;
       +                        case 'm':
       +                                if ((mark = vi_read()) > 0 && isalpha(mark))
       +                                        lbuf_mark(xb, mark, xrow);
       +                                break;
       +                        default:
       +                                continue;
       +                        }
       +                }
       +                if (xrow < 0 || xrow >= lbuf_len(xb))
       +                        xrow = lbuf_len(xb) ? lbuf_len(xb) - 1 : 0;
       +                if (xrow < xtop || xrow >= xtop + xrows) {
       +                        xtop = xrow < xtop ? xrow : MAX(0, xrow - xrows + 1);
       +                        redraw = 1;
       +                }
       +                if (redraw)
       +                        vi_draw();
       +                term_pos(xrow - xtop, ren_cursor(lbuf_get(xb, xrow), xcol));
       +                lbuf_undomark(xb);
       +        }
       +        term_pos(xrows, 0);
       +        term_kill();
       +        term_done();
       +}
       +
       +int main(int argc, char *argv[])
       +{
       +        int visual = 1;
       +        char ecmd[PATHLEN];
       +        int i;
       +        xb = lbuf_make();
       +        for (i = 1; i < argc && argv[i][0] == '-'; i++) {
       +                if (argv[i][1] == 's')
       +                        xled = 0;
       +                if (argv[i][1] == 'e')
       +                        visual = 0;
       +                if (argv[i][1] == 'v')
       +                        visual = 1;
       +        }
       +        if (i < argc) {
       +                snprintf(ecmd, PATHLEN, "e %s", argv[i]);
       +                ex_command(ecmd);
       +        }
       +        if (visual)
       +                vi();
       +        else
       +                ex();
       +        lbuf_free(xb);
       +        return 0;
       +}
 (DIR) diff --git a/vi.h b/vi.h
       t@@ -0,0 +1,102 @@
       +/* neatvi main header */
       +
       +#define PATHLEN                512
       +
       +/* helper macros */
       +#define LEN(a)                (sizeof(a) / sizeof((a)[0]))
       +#define MIN(a, b)        ((a) < (b) ? (a) : (b))
       +#define MAX(a, b)        ((a) < (b) ? (b) : (a))
       +
       +/* line buffer, managing a number of lines */
       +struct lbuf *lbuf_make(void);
       +void lbuf_free(struct lbuf *lbuf);
       +void lbuf_rd(struct lbuf *lbuf, int fd, int pos);
       +void lbuf_wr(struct lbuf *lbuf, int fd, int beg, int end);
       +void lbuf_rm(struct lbuf *lbuf, int beg, int end);
       +char *lbuf_cp(struct lbuf *lbuf, int beg, int end);
       +void lbuf_put(struct lbuf *lbuf, int pos, char *s);
       +char *lbuf_get(struct lbuf *lbuf, int pos);
       +int lbuf_len(struct lbuf *lbuf);
       +void lbuf_mark(struct lbuf *lbuf, int mark, int pos);
       +int lbuf_markpos(struct lbuf *lbuf, int mark);
       +void lbuf_undo(struct lbuf *lbuf);
       +void lbuf_redo(struct lbuf *lbuf);
       +void lbuf_undomark(struct lbuf *lbuf);
       +void lbuf_undofree(struct lbuf *lbuf);
       +
       +/* string buffer, variable-sized string */
       +struct sbuf *sbuf_make(void);
       +void sbuf_free(struct sbuf *sb);
       +char *sbuf_done(struct sbuf *sb);
       +char *sbuf_buf(struct sbuf *sb);
       +void sbuf_chr(struct sbuf *sb, int c);
       +void sbuf_str(struct sbuf *sb, char *s);
       +void sbuf_mem(struct sbuf *sb, char *s, int len);
       +void sbuf_printf(struct sbuf *sbuf, char *s, ...);
       +int sbuf_len(struct sbuf *sb);
       +void sbuf_cut(struct sbuf *s, int len);
       +
       +/* rendering lines */
       +char *ren_all(char *s, int wid);
       +int ren_cursor(char *s, int pos);
       +int ren_next(char *s, int p, int dir);
       +int ren_eol(char *s, int dir);
       +int ren_dir(char *s);
       +int ren_pos(char *s, int off);
       +int ren_off(char *s, int pos);
       +int ren_last(char *s);
       +int ren_cmp(char *s, int pos1, int pos2);
       +int ren_insertionoffset(char *s, int pos, int pre);
       +
       +/* utf-8 helper functions */
       +int uc_len(char *s);
       +int uc_dir(char *s);
       +int uc_wid(char *s);
       +int uc_slen(char *s);
       +int uc_code(char *s);
       +int uc_isspace(char *s);
       +int uc_isprint(char *s);
       +char **uc_chop(char *s, int *n);
       +char *uc_next(char *s);
       +char *uc_beg(char *beg, char *s);
       +char *uc_end(char *beg, char *s);
       +char *uc_shape(char *beg, char *s);
       +
       +/* managing the terminal */
       +#define xrows                (term_rows() - 1)
       +#define xcols                (term_cols())
       +
       +void term_init(void);
       +void term_done(void);
       +void term_str(char *s);
       +void term_chr(int ch);
       +void term_pos(int r, int c);
       +void term_clear(void);
       +void term_kill(void);
       +int term_rows(void);
       +int term_cols(void);
       +int term_read(int timeout);
       +void term_record(void);
       +void term_commit(void);
       +
       +#define TERMCTRL(x)        ((x) - 96)
       +#define TERMESC                27
       +
       +/* line-oriented input and output */
       +char *led_prompt(char *pref, char *post);
       +char *led_input(char *pref, char *post, int *row, int *col);
       +void led_print(char *msg, int row);
       +char *led_keymap(int c);
       +
       +/* ex commands */
       +void ex(void);
       +void ex_command(char *cmd);
       +
       +/* global variables */
       +extern struct lbuf *xb;
       +extern int xrow;
       +extern int xcol;
       +extern int xtop;
       +extern int xled;
       +extern char xpath[];
       +extern int xquit;