tneatvi: the skeleton - neatvi - [fork] simple vi-type editor with UTF-8 support | |
git clone git://src.adamsgaard.dk/neatvi | |
Log | |
Files | |
Refs | |
README | |
--- | |
commit b1c10bc71e9c7d3c7db8cbd85adf17aa63f9736b | |
Author: Ali Gholami Rudi <[email protected]> | |
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(-) | |
--- | |
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 | |
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, cm… | |
+ 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(); | |
+} | |
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'] = "ء", | |
+ ['<'] = ">", | |
+ ['>'] = "<", | |
+ ['?'] = "؟", | |
+ ['|'] = "|", | |
+}; | |
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++; | |
+} | |
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; | |
+} | |
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 charac… | |
+ * + 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, i… | |
+{ | |
+ 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 it… | |
+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; | |
+} | |
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); | |
+} | |
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; | |
+} | |
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 a… | |
+ {0x0624, 0xfe85, 0, 0, 0xfe86}, /* waw hamza */ | |
+ {0x0625, 0xfe87, 0, 0, 0xfe88}, /* alef hamza b… | |
+ {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 … | |
+ (c >= 0xfc5e && c <= 0xfc63) || /* shadda ligat… | |
+ c == 0x0670; /* superscript ale… | |
+} | |
+ | |
+/* 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; | |
+} | |
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 d… | |
+{ | |
+ 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 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) - … | |
+ 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 + xr… | |
+ 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; | |
+} | |
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; |