Home

File: edit9.c

/* The author of this work places it in the public domain. It may used freely without attribution or notice. */

#include gt;   /* tcgetattr, tcsetattr, struct termios */
#include gt; /* ioctl, TIOCGWINSZ, struct winsize */
#include gt;  /* stat */
#include gt;    /* read, close, write */
#include gt;     /* open, specifically for O_RDONLY|O_CREAT */
#include gt;    /* signal, SIGWINCH */
#include gt;     /* errno */
#include gt;    /* atexit */
#include gt;    /* strlen */
#include gt;     /* snprintf */
#include gt;    /* for variadic arguments in dodie */
#include gt;     /* isprint */

#define SYSCOLOR  "\x1b[4;33;44m"
#define MESSAGECOLOR  "\x1b[4;37;44m"
#define COMMANDCOLOR "\x1b[4;30;41m" 
#define INSERTCOLOR "\x1b[4;30;43m"
#define MARKCOLOR "\x1b[4;30;46m"
#define REGIONCOLOR "\x1b[7m"
#define WSCOLOR "\x1b[4;30;42m"
#define TEXTCOLOR "\x1b[0m"
#define LINEWRAP  '>gt;'
#define TABWIDTH 3
#define TABDRAW "   "
#define GAPINCR 16
#define SAVEMESSAGE "saved file"
#define ENDMESSAGE "end of file"
#define BEGINMESSAGE "beginning of file"
#define COPYMESSAGE "copied region"
#define CUTMESSAGE "cut region"
#define EOFMARKER "eof"
#define SAVEPERMMESSAGE "unable to save (bad permissions)"
#define SAVEBADMESSAGE "unable to save"
#define PROMPTBUFFERSIZE 128
#define NEWFILEMODE (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)
#define die(...) dodie(__LINE__, __FILE__, __VA_ARGS__)
#define ABS(X) ((X) gt;>gt; 6 == 0x2;
}

unsigned utf8prefixlen(unsigned char c)
{
	nbsp;	nbsp;	nbsp;return c >gt;>gt; 7 == 0 ? 1 : (c >gt;>gt; 5 == 0x6 ? 2 : (c >gt;>gt; 4 == 0xe ? 3 : (c >gt;>gt; 3 == 0x1e ? 4 : 0)));
}

int isgap(const char *c)
{
	nbsp;	nbsp;	nbsp;return c >gt;= buffer.gapstart && c gt;c = buffer.start;
	nbsp;	nbsp;	nbsp;iter->gt;gx = iter->gt;gy = 0; iter->gt;tx = iter->gt;ty = 1;
	nbsp;	nbsp;	nbsp;iter->gt;linewrap = iter->gt;done = 0;
	nbsp;	nbsp;	nbsp;while (isgap(iter->gt;c) || isutf8cont(*iter->gt;c)) iter->gt;c++;
	nbsp;	nbsp;	nbsp;iter->gt;done = iter->gt;c >gt;= buffer.end;
	nbsp;	nbsp;	nbsp;return !iter->gt;done;
}

int bufferiteratornext(struct bufferiterator *iter)
{
	nbsp;	nbsp;	nbsp;if (iter->gt;done) return 0;
	nbsp;	nbsp;	nbsp;const char *prev = iter->gt;c;
	nbsp;	nbsp;	nbsp;do { iter->gt;c++; } while (iter->gt;c gt;c) || isutf8cont(*iter->gt;c)));
	nbsp;	nbsp;	nbsp;if (iter->gt;c >gt; buffer.end) { iter->gt;done = 1; return 0; }
	nbsp;	nbsp;	nbsp;if (*prev == '\n') { iter->gt;tx = 1; iter->gt;ty++; } else { iter->gt;tx++; }
	nbsp;	nbsp;	nbsp;if (isprint(*prev) || utf8prefixlen(*prev) >gt; 1)
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;iter->gt;gx++;
	nbsp;	nbsp;	nbsp;else if (*prev == '\n')
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;iter->gt;gx = 0, iter->gt;gy++;
	nbsp;	nbsp;	nbsp;else if (*prev == '\t')
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;iter->gt;gx += TABWIDTH;
	nbsp;	nbsp;	nbsp;iter->gt;linewrap = 0;
	nbsp;	nbsp;	nbsp;if (iter->gt;gx >gt;= w - 1) iter->gt;gx = 0, iter->gt;gy++, iter->gt;linewrap = 1;
	nbsp;	nbsp;	nbsp;return 1;
}

int isvisible(unsigned gx, unsigned gy)
{
	nbsp;	nbsp;	nbsp;return gy >gt;= toprow && gy gt;= mark.c && c gt;= buffer.gapend);
}

void draw(void)
{
	nbsp;	nbsp;	nbsp;struct bufferiterator iter;
	nbsp;	nbsp;	nbsp;printf(TEXTCOLOR "\x1b[2J"); /* text color on, clear the screen */
	nbsp;	nbsp;	nbsp;if (bufferiteratorinit(&iter)) {
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;do {
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;unsigned plen = utf8prefixlen(*iter.c);
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;if (iter.linewrap && isvisible(w - 1, iter.gy - 1)) {  
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;printf(SYSCOLOR "%c" TEXTCOLOR, LINEWRAP);
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;}
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;if (isvisible(iter.gx, iter.gy) && (isprint(*iter.c) || isspace(*iter.c) || *iter.c == '\t' || plen >gt; 1)) {
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;char outbuf[8] = {0}, *out = outbuf;
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;memcpy(out, iter.c, plen);
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;const char *style = inregion(iter.c) ? REGIONCOLOR : (ws && isspace(out[0]) ? WSCOLOR : TEXTCOLOR);
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;if (ws) switch (out[0]) { case ' ': out = "."; break; case '\t': out = ">gt;"; break; case '\n': out = "$"; break; }
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;movepen(iter.gx, iter.gy + 1 - toprow);
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;out = out[0] == '\t' ? TABDRAW : out;
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;if (isprint(out[0]) || plen >gt; 1) printf("%s%s", style, out);
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;}
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;} while (bufferiteratornext(&iter));
	nbsp;	nbsp;	nbsp;}
	nbsp;	nbsp;	nbsp;iter.gy++;
	nbsp;	nbsp;	nbsp;if (isvisible(0, iter.gy)) {
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;movepen(0, iter.gy + 1 - toprow);
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;printf(TEXTCOLOR SYSCOLOR "%s" TEXTCOLOR, EOFMARKER);
	nbsp;	nbsp;	nbsp;}
	nbsp;	nbsp;	nbsp;if (isvisible(cursor.gx, cursor.gy)) {
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;char outbuf[8] = {0}, *out = outbuf;
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;unsigned plen = utf8prefixlen(*cursor.c);
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;memcpy(out, cursor.c, plen);
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;movepen(cursor.gx, cursor.gy + 1 - toprow);
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;out = isprint(out[0]) || plen >gt; 1 ? out : " ";
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;printf("%s%s" TEXTCOLOR, mark.isactive ? MARKCOLOR : (mode == COMMANDMODE ? COMMANDCOLOR : INSERTCOLOR), out);
	nbsp;	nbsp;	nbsp;}
	nbsp;	nbsp;	nbsp;movepen(0, 0); /* draw filename at top */
	nbsp;	nbsp;	nbsp;printf(SYSCOLOR);  /* menu color on */
	nbsp;	nbsp;	nbsp;for (int i = 0; i gt; L%d C%d", l, n, l, m, r, indent, t, cursor.ty, cursor.tx);
	nbsp;	nbsp;	nbsp;}
	nbsp;	nbsp;	nbsp;fflush(stdout);
}

void resize(int i)
{
	nbsp;	nbsp;	nbsp;size();
	nbsp;	nbsp;	nbsp;draw();
}

void dosignal(int i)
{
   exit(1);
}

void reset(void)
{
	nbsp;	nbsp;	nbsp;printf("\x1b[2J");
	nbsp;	nbsp;	nbsp;printf("\x1b[?1049l");
	nbsp;	nbsp;	nbsp;printf("\x1b[?25h");
	nbsp;	nbsp;	nbsp;tcsetattr(1, TCSANOW, &t0); /* reset term to initial config */
	nbsp;	nbsp;	nbsp;fflush(stdout);
}

void init(const char *name)
{
	nbsp;	nbsp;	nbsp;size_t buffersize = gapallocsize + 1;
	nbsp;	nbsp;	nbsp;buffer.name = name;
	nbsp;	nbsp;	nbsp;int fd = 0;
	nbsp;	nbsp;	nbsp;printf("\x1b[?1049h");
	nbsp;	nbsp;	nbsp;printf("\x1b[?25l");
	nbsp;	nbsp;	nbsp;tcgetattr(1, &t0);
	nbsp;	nbsp;	nbsp;t1 = t0;
	nbsp;	nbsp;	nbsp;t1.c_lflag &= (~ECHO & ~ICANON);
	nbsp;	nbsp;	nbsp;tcsetattr(1, TCSANOW, &t1);
	nbsp;	nbsp;	nbsp;if (buffer.name) {
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;struct stat st;
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;fd = open(name, O_RDONLY|O_CREAT, NEWFILEMODE);
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;fd != -1 || die("cannot open file %s\nopen: %s", name, strerror(errno));
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;fstat(fd, &st) != 1 || die("cannot find file %s size\nstat: %s", name, strerror(errno));
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;buffersize += st.st_size;
	nbsp;	nbsp;	nbsp;}
	nbsp;	nbsp;	nbsp;buffer.start = malloc(buffersize); memset(buffer.start, '\0', buffersize);
	nbsp;	nbsp;	nbsp;buffer.start || die("unable to allocate %d bytes for in-memory file editing", buffersize);
	nbsp;	nbsp;	nbsp;buffer.end = buffer.start + buffersize - 1; buffer.end[0] = '\0';
	nbsp;	nbsp;	nbsp;buffer.gapstart = buffer.start;
	nbsp;	nbsp;	nbsp;buffer.gapend = buffer.gapstart + GAPINCR;
	nbsp;	nbsp;	nbsp;if (fd) {
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;char *current = buffer.gapend;
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;ssize_t saw = 0;
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;while (current += saw, saw = read(fd, current, buffer.end - current), saw && saw != -1);
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;saw != -1 || die("cannot read file %s\nread: %s", name, strerror(errno));
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;close(fd);
	nbsp;	nbsp;	nbsp;}
	nbsp;	nbsp;	nbsp;size();
	nbsp;	nbsp;	nbsp;atexit(reset);
	nbsp;	nbsp;	nbsp;signal(SIGWINCH, resize);
	nbsp;	nbsp;	nbsp;signal(SIGTERM, dosignal);
	nbsp;	nbsp;	nbsp;signal(SIGINT, dosignal);
}

int movegap(const char *c)
{
	nbsp;	nbsp;	nbsp;char *side = ABS(c - buffer.gapstart) gt; 0) {
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;while (amount--) {
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;if (buffer.gapend == buffer.end) return 0;
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;*buffer.gapstart = *buffer.gapend;
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;buffer.gapstart++;
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;buffer.gapend++;
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;}
	nbsp;	nbsp;	nbsp;}
	nbsp;	nbsp;	nbsp;else {
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;while (amount--) {
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;if (buffer.gapstart == buffer.start) return 0;
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;buffer.gapstart--;
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;buffer.gapend--;
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;*buffer.gapend = *buffer.gapstart;
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;}
	nbsp;	nbsp;	nbsp;}
	nbsp;	nbsp;	nbsp;return 1;
}

void centercursor(void)
{
	nbsp;	nbsp;	nbsp;int target = cursor.gy - (int)(h / 2);
	nbsp;	nbsp;	nbsp;if (target gt; rows) target = rows;
	nbsp;	nbsp;	nbsp;toprow = target;
}

void moveline(unsigned desiredx, unsigned desiredy, int isgraph)
{
	nbsp;	nbsp;	nbsp;struct bufferiterator iter;
	nbsp;	nbsp;	nbsp;const char *before = NULL;
	nbsp;	nbsp;	nbsp;cursor.isupdatingcache = 0;
	nbsp;	nbsp;	nbsp;if (bufferiteratorinit(&iter)) {
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;do {
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;unsigned x, y; if (isgraph) x = iter.gx, y = iter.gy; else x = iter.tx, y = iter.ty;
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;if (x == desiredx && y == desiredy) {
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;movegap(iter.c);
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;return;
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;}
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;else if (y >gt; desiredy && before) {
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;movegap(before);
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;return;
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;}
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;before = iter.c;
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;} while (bufferiteratornext(&iter));
	nbsp;	nbsp;	nbsp;}
	nbsp;	nbsp;	nbsp;if (before) movegap(before);
}

void previousline(void)
{
	nbsp;	nbsp;	nbsp;if (cursor.gy == 0) message = BEGINMESSAGE;
	nbsp;	nbsp;	nbsp;else moveline(cursor.cachedgx, cursor.gy - 1, 1); 
}

void nextline(void)
{
	nbsp;	nbsp;	nbsp;if (cursor.gy == rows) message = ENDMESSAGE;
	nbsp;	nbsp;	nbsp;else moveline(cursor.cachedgx, cursor.gy + 1, 1);
}

void grow(void)
{
	nbsp;	nbsp;	nbsp;gapallocsize += GAPINCR;
	nbsp;	nbsp;	nbsp;size_t gapoffset = buffer.gapstart - buffer.start;
	nbsp;	nbsp;	nbsp;size_t oldsize = buffer.end - buffer.start;
	nbsp;	nbsp;	nbsp;size_t newsize = oldsize + gapallocsize + 1;
	nbsp;	nbsp;	nbsp;buffer.start = realloc(buffer.start, newsize);
	nbsp;	nbsp;	nbsp;buffer.start || die("unable to allocate %d bytes for in-memory file editing", newsize);
	nbsp;	nbsp;	nbsp;buffer.gapstart = buffer.start + gapoffset;
	nbsp;	nbsp;	nbsp;buffer.gapend = buffer.gapstart + gapallocsize;
	nbsp;	nbsp;	nbsp;buffer.end = buffer.start + newsize - 1; buffer.end[0] = '\0'; 
	nbsp;	nbsp;	nbsp;memmove(buffer.gapend, buffer.gapstart, oldsize - gapoffset);
}

const char *prompt(const char *p)
{
	nbsp;	nbsp;	nbsp;static char promptbuffer[PROMPTBUFFERSIZE];
	nbsp;	nbsp;	nbsp;memset(promptbuffer, 0, PROMPTBUFFERSIZE);
	nbsp;	nbsp;	nbsp;unsigned i = 0;
	nbsp;	nbsp;	nbsp;for (;;) {
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;unsigned start = (p ? strlen(p) : 0) + 1;
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;movepen(0, 0);
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;printf(SYSCOLOR);
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;for (int i = 0; i gt; buffer.start && (isutf8cont(*here) || isgap(here))); 
	nbsp;	nbsp;	nbsp;return here;
}

void save(void)
{
	nbsp;	nbsp;	nbsp;const char *name = buffer.name ? buffer.name : prompt("file name: ");
	nbsp;	nbsp;	nbsp;if (name) {
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;int fd = open(name, O_WRONLY|O_CREAT|O_TRUNC, NEWFILEMODE);
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;if (fd == -1) { message = errno == EACCES ? SAVEPERMMESSAGE : SAVEBADMESSAGE; return; }
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;if (*prevchar(buffer.end) != '\n') { char *r = buffer.gapstart; movegap(buffer.end); insert('\n'); movegap(r); }
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;char *current = buffer.start;
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;for (ssize_t written = 0; current gt; buffer.end ? buffer.end : to);
	nbsp;	nbsp;	nbsp;int step = *from gt; 0x10ffff) return;
	nbsp;	nbsp;	nbsp;if (cpi gt;>gt; 6));
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;insert(0x80 | (cpi & 0x3f));
	nbsp;	nbsp;	nbsp;}
	nbsp;	nbsp;	nbsp;else if (cpi gt;>gt; 12));
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;insert(0x80 | ((cpi >gt;>gt; 6) & 0x3f));
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;insert(0x80 | (cpi & 0x3f));
	nbsp;	nbsp;	nbsp;}
	nbsp;	nbsp;	nbsp;else if (cpi gt;>gt; 18));
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;insert(0x80 | ((cpi >gt;>gt; 12) & 0x3f));
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;insert(0x80 | ((cpi >gt;>gt; 6) & 0x3f));
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;insert(0x80 | (cpi & 0x3f));
	nbsp;	nbsp;	nbsp;}
} 

void interpret()
{
	nbsp;	nbsp;	nbsp;cursor.isupdatingcache = 1;
	nbsp;	nbsp;	nbsp;in = in == '\r' ? '\n' : in;
	nbsp;	nbsp;	nbsp;if (mode == COMMANDMODE) {
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;switch (in) {
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;case 'i':    mode = INSERTMODE; mark.isactive = 0; break;
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;case 'Q':    running = 0; break;
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;case 'f':    while (movegap(buffer.gapend + 1) && isutf8cont(*buffer.gapend)); break;
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;case 'b':    while (movegap(buffer.gapstart - 1) && isutf8cont(*buffer.gapend)); break;
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;case 'd':    deleteto(mark.isactive ? mark.c : buffer.gapend + 1); mark.isactive = 0; break;
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;case '\x7f': deleteto(mark.isactive ? mark.c : buffer.gapstart - 1); mark.isactive = 0; break;
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;case 'p':    previousline(); break;
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;case 'n':    nextline(); break;
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;case 'g':    { unsigned dty; if (promptu("enter line number: ", 10, &dty)) moveline(cursor.tx, dty, 0); } break;
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;case 'e':    while (*buffer.gapend != '\n' && movegap(buffer.gapend + 1)); break;
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;case 'a':    while (*prevchar(buffer.gapstart) != '\n' && movegap(buffer.gapstart - 1)); break;
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;case 'l':    centercursor(); break;
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;case 'm':    if (mark.isactive = !mark.isactive) mark.tx = cursor.tx, mark.ty = cursor.ty; break;
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;case 's':    save(); break;
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;case 'c':    if (mark.isactive) { copyregion(); mark.isactive = 0; message = COPYMESSAGE; } break;
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;case 'x':    if (mark.isactive) { copyregion(); deleteto(mark.c); mark.isactive = 0; message = CUTMESSAGE; } break;
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;case 'y':    if (clip) { char *c = clip; while (*c) insert(*c++); } break;
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;case 'w':    ws = !ws; break;
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;case '>gt;':    movegap(buffer.end); break;
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;case 'gt; buffer.start) buffer.gapstart--; break;
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;case '\t':   if (indent == 0) insert('\t'); else if (indent >gt; 0) { int i = indent; while (i--) insert(' '); } break;
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;default:
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;if (isprint(in) || in == '\n') {
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;insert(in);
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;}
	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;	nbsp;}
	nbsp;	nbsp;	nbsp;}
}

int main(int argc, char **argv)
{
	nbsp;	nbsp;	nbsp;argc