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;