summary refs log tree commit diff
diff options
context:
space:
mode:
authorJune McEnroe <june@causal.agency>2019-08-10 15:47:59 -0400
committerJune McEnroe <june@causal.agency>2019-08-10 15:47:59 -0400
commit6d7320a10ded22619ee49eb71e8380fd514b849b (patch)
tree40fa5e95e8c75e734cae9a33c10528a0aa68d845
parentFlip enumerator-constant order for sorting (diff)
downloadstream-6d7320a10ded22619ee49eb71e8380fd514b849b.tar.gz
stream-6d7320a10ded22619ee49eb71e8380fd514b849b.zip
Rewrite terminal emulator
-rw-r--r--Makefile2
-rw-r--r--ingest.c8
-rw-r--r--term.c777
-rw-r--r--term.h (renamed from stream.h)42
-rw-r--r--view.c22
5 files changed, 425 insertions, 426 deletions
diff --git a/Makefile b/Makefile
index 9b8c704..db34005 100644
--- a/Makefile
+++ b/Makefile
@@ -22,7 +22,7 @@ ingest: ingest.o term.o
 view: view.o term.o
 	${CC} ${LDFLAGS} view.o term.o ${LDLIBS} -o $@
 
-ingest.o term.o view.o: stream.h
+ingest.o term.o view.o: term.h
 
 tags: *.c *.h
 	ctags -w *.c *.h
diff --git a/ingest.c b/ingest.c
index 67328a8..3991adc 100644
--- a/ingest.c
+++ b/ingest.c
@@ -29,7 +29,7 @@
 #include <unistd.h>
 #include <wchar.h>
 
-#include "stream.h"
+#include "term.h"
 
 int main(void) {
 	int error;
@@ -37,7 +37,7 @@ int main(void) {
 
 	// TODO: Read info from file.
 	const char *path = "example.sock";
-	termInit(24, 80);
+	struct Term *term = termAlloc(24, 80);
 	
 	int server = socket(PF_LOCAL, SOCK_STREAM, 0);
 	if (server < 0) err(EX_OSERR, "socket");
@@ -90,7 +90,7 @@ int main(void) {
 				wchar_t ch;
 				int n = mbtowc(&ch, &buf[pos], rlen - pos);
 				if (n <= 0) break;
-				termUpdate(ch);
+				termUpdate(term, ch);
 				pos += n;
 			}
 		}
@@ -107,7 +107,7 @@ int main(void) {
 				continue;
 			}
 
-			error = termSnapshot(client);
+			error = termSnapshot(term, client);
 			if (error) {
 				close(client);
 				continue;
diff --git a/term.c b/term.c
index f4bb566..b7c7e66 100644
--- a/term.c
+++ b/term.c
@@ -14,9 +14,9 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+#include <assert.h>
 #include <err.h>
 #include <stdarg.h>
-#include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -24,54 +24,28 @@
 #include <unistd.h>
 #include <wchar.h>
 
-#include "stream.h"
+#include "term.h"
 
-#define E(c, e) e = c
-
-enum C0 {
-	NUL, SOH, STX, ETX, EOT, ENQ, ACK, BEL,
-	BS, HT, NL, VT, NP, CR, SO, SI,
-	DLE, DC1, DC2, DC3, DC4, NAK, SYN, ETB,
-	CAN, EM, SUB, ESC, FS, GS, RS, US,
-	DEL = 0x7F,
-};
-
-static char unhandled(const char *format, ...) {
-	if (isatty(STDERR_FILENO)) return NUL;
+static void unhandled(const char *format, ...) {
+	if (isatty(STDERR_FILENO)) return;
 	va_list ap;
 	va_start(ap, format);
 	char buf[256];
 	vsnprintf(buf, sizeof(buf), format, ap);
 	warnx("unhandled %s", buf);
 	va_end(ap);
-	return NUL;
 }
 
-static const struct Style Default = { .bg = -1, .fg = -1 };
-
-static uint rows, cols;
-static uint y, x;
-
-static bool cursor = true;
-static bool insert;
-static struct {
-	uint top, bot;
-} scroll;
-static struct {
-	uint y, x;
-} save;
-
-static struct Style style;
-static struct Cell *cells;
-
-static struct Cell *cell(uint y, uint x) {
-	return &cells[y * cols + x];
+static struct Cell *cell(struct Term *t, uint y, uint x) {
+	assert(y < t->rows);
+	assert(x < t->cols);
+	return &t->cells[y * t->cols + x];
 }
 
-static void clear(struct Cell *a, struct Cell *b) {
+static void erase(struct Style style, struct Cell *a, struct Cell *b) {
 	for (; a <= b; ++a) {
 		a->style = style;
-		a->ch = ' ';
+		a->ch = L' ';
 	}
 }
 
@@ -79,471 +53,479 @@ static void move(struct Cell *dst, struct Cell *src, uint len) {
 	memmove(dst, src, sizeof(*dst) * len);
 }
 
-static void scrollUp(uint n) {
+static void scrollUp(struct Term *t, uint n) {
 	move(
-		cell(scroll.top, 0), cell(scroll.top + n, 0),
-		cols * (scroll.bot - scroll.top - (n - 1))
+		cell(t, t->scroll.top, 0),
+		cell(t, t->scroll.top + n, 0),
+		t->cols * (1 + t->scroll.bot - t->scroll.top - n)
+	);
+	erase(
+		t->style,
+		cell(t, 1 + t->scroll.bot - n, 0),
+		cell(t, t->scroll.bot, t->cols - 1)
 	);
-	clear(cell(scroll.bot - (n - 1), 0), cell(scroll.bot, cols - 1));
 }
 
-static void scrollDown(uint n) {
+static void scrollDown(struct Term *t, uint n) {
 	move(
-		cell(scroll.top + n, 0), cell(scroll.top, 0),
-		cols * (scroll.bot - scroll.top - (n - 1))
+		cell(t, t->scroll.top + n, 0),
+		cell(t, t->scroll.top, 0),
+		t->cols * (1 + t->scroll.bot - t->scroll.top - n)
+	);
+	erase(
+		t->style,
+		cell(t, t->scroll.top, 0),
+		cell(t, t->scroll.top + n - 1, t->cols - 1)
 	);
-	clear(cell(scroll.top, 0), cell(scroll.top + (n - 1), cols - 1));
 }
 
-static char updateNUL(wchar_t ch) {
-	switch (ch) {
-		break; case ESC: return ESC;
-
-		break; case BS: if (x) x--;
-		break; case CR: x = 0;
+typedef void Action(struct Term *, wchar_t ch);
 
-		break; case NL: {
-			if (y == scroll.bot) {
-				scrollUp(1);
-			} else {
-				y = MIN(y + 1, rows - 1);
-			}
-		}
+#define ACTION(name) \
+	static void name(struct Term *t, wchar_t _ch __attribute__((__unused__)))
 
-		break; default: {
-			int width = wcwidth(ch);
-			if (ch < ' ') return unhandled("\\x%02X", ch);
-			if (width < 0) return unhandled("\\u%X", ch);
-			if (x + width > cols) return unhandled("'%lc' too wide", ch);
+enum {
+	G0  = '(',
+	CSI = '[',
+	ST = '\\',
+	OSC = ']',
+};
 
-			if (insert) {
-				move(cell(y, x + width), cell(y, x), cols - x - width);
-			}
-			cell(y, x)->style = style;
-			cell(y, x)->ch = ch;
+ACTION(nop) { (void)t; }
+ACTION(esc) { t->state = ESC; }
+ACTION(g0)  { t->state = G0; }
+ACTION(osc) { t->state = OSC; }
+ACTION(st)  { t->state = ST; }
+ACTION(csi) {
+	t->state = CSI;
+	memset(&t->param, 0, sizeof(t->param));
+}
 
-			for (int i = 1; i < width; ++i) {
-				cell(y, x + i)->style = style;
-				cell(y, x + i)->ch = '\0';
-			}
-			x = MIN(x + width, cols - 1);
-		}
+static void csiParam(struct Term *t, wchar_t ch) {
+	if (ch == L'?') {
+		t->param.q = true;
+	} else if (ch == L';') {
+		t->param.n++;
+		t->param.i++;
+		t->param.i %= ParamCap;
+	} else if (ch >= L'0' && ch <= L'9') {
+		t->param.s[t->param.i] *= 10;
+		t->param.s[t->param.i] += ch - L'0';
+		if (!t->param.n) t->param.n++;
+	} else {
+		// FIXME: Allow other characters in CSI params.
+		unhandled("CSI %lc", ch);
+		return;
 	}
-	return NUL;
+	t->state = CSI;
 }
 
-enum C1 {
-	E('7', DECSC),
-	E('8', DECRC),
-	E('D', IND),
-	E('M', RI),
-	E('[', CSI),
-	E('\\', ST),
-	E(']', OSC),
-};
+static void escUnhandled(struct Term *t, wchar_t ch) {
+	(void)t;
+	unhandled("ESC %lc", ch);
+}
 
-static char updateESC(wchar_t ch) {
-	static bool discard;
-	if (discard) {
-		discard = false;
-		return NUL;
-	}
+#define Y t->y
+#define X t->x
+#define B (t->rows - 1)
+#define R (t->cols - 1)
+#define C(y, x) cell(t, y, x)
+#define P(i, z) ((i) < t->param.n ? t->param.s[(i)] : (z))
+
+ACTION(bs)  { if (X) X--; }
+ACTION(cr)  { X = 0; }
+ACTION(cuu) { Y -= MIN(P(0, 1), Y); }
+ACTION(cud) { Y  = MIN(Y + P(0, 1), B); }
+ACTION(cuf) { X  = MIN(X + P(0, 1), R); }
+ACTION(cub) { X -= MIN(P(0, 1), X); }
+ACTION(cnl) { X = 0; cud(t, 0); }
+ACTION(cpl) { X = 0; cuu(t, 0); }
+ACTION(cha) { X = MIN(P(0, 1) - 1, R); }
+ACTION(vpa) { Y = MIN(P(0, 1) - 1, B); }
+ACTION(cup) {
+	Y = MIN(P(0, 1) - 1, B);
+	X = MIN(P(1, 1) - 1, R);
+}
+ACTION(decsc) {
+	t->save.y = Y;
+	t->save.x = X;
+}
+ACTION(decrc) {
+	Y = t->save.y;
+	X = t->save.x;
+}
 
-	switch (ch) {
-		break; case CSI: return CSI;
-		break; case OSC: return OSC;
+ACTION(ed) {
+	erase(
+		t->style,
+		(P(0, 0) == 0 ? C(Y, X) : C(0, 0)),
+		(P(0, 0) == 1 ? C(Y, X) : C(B, R))
+	);
+}
+ACTION(el) {
+	erase(
+		t->style,
+		(P(0, 0) == 0 ? C(Y, X) : C(Y, 0)),
+		(P(0, 0) == 1 ? C(Y, X) : C(Y, R))
+	);
+}
+ACTION(ech) {
+	erase(t->style, C(Y, X), C(Y, MIN(X + P(0, 1) - 1, R)));
+}
 
-		break; case DECSC: save.y = y; save.x = x;
-		break; case DECRC: y = save.y; x = save.x;
+ACTION(dl) {
+	uint n = MIN(P(0, 1), t->rows - Y);
+	move(C(Y, 0), C(Y + n, 0), t->cols * (t->rows - Y - n));
+	erase(t->style, C(t->rows - n, 0), C(B, R));
+}
+ACTION(dch) {
+	uint n = MIN(P(0, 1), t->cols - X);
+	move(C(Y, X), C(Y, X + n), t->cols - X - n);
+	erase(t->style, C(Y, t->cols - n), C(Y, R));
+}
+ACTION(il) {
+	uint n = MIN(P(0, 1), t->rows - Y);
+	move(C(Y + n, 0), C(Y, 0), t->cols * (t->rows - Y - n));
+	erase(t->style, C(Y, 0), C(Y + n - 1, R));
+}
+ACTION(ich) {
+	uint n = MIN(P(0, 1), t->cols - X);
+	move(C(Y, X + n), C(Y, X), t->cols - X - n);
+	erase(t->style, C(Y, X), C(Y, X + n - 1));
+}
 
-		break; case IND: scrollUp(1);
-		break; case RI:  scrollDown(1);
+ACTION(nl) {
+	if (Y == t->scroll.bot) {
+		scrollUp(t, 1);
+	} else {
+		Y = MIN(Y + 1, B);
+	}
+}
+ACTION(ind) { scrollUp(t, 1); }
+ACTION(ri)  { scrollDown(t, 1); }
+ACTION(su)  { scrollUp(t, MIN(P(0, 1), t->scroll.bot - t->scroll.top)); }
+ACTION(sd)  { scrollDown(t, MIN(P(0, 1), t->scroll.bot - t->scroll.top)); }
+ACTION(decstbm) {
+	t->scroll.bot = MIN(P(1, t->rows) - 1, B);
+	t->scroll.top = MIN(P(0, 1) - 1, t->scroll.bot);
+}
 
-		break; case '(': discard = true; return ESC;
-		break; case '=': // ignore
-		break; case '>': // ignore
+enum {
+	IRM = 4,
+	DECAWM = 7,
+	DECTCEM = 25,
+};
 
-		break; default:  return unhandled("ESC %lc", ch);
+static void mode(struct Term *t, wchar_t ch) {
+	enum Mode mode = 0;
+	for (uint i = 0; i < t->param.n; ++i) {
+		if (t->param.q) {
+			switch (t->param.s[i]) {
+				break; case 1:  // ignore
+				break; case DECAWM: mode |= Wrap;
+				break; case DECTCEM: mode |= Cursor;
+				break; default: unhandled("DECSET/DECRST %u", t->param.s[i]);
+			}
+		} else {
+			switch (t->param.s[i]) {
+				break; case IRM: mode |= Insert;
+				break; default: unhandled("SM/RM %u", t->param.s[i]);
+			}
+		}
 	}
-	return NUL;
+	t->mode = (ch == L'h' ? t->mode | mode : t->mode & ~mode);
 }
 
-enum SGR {
-	E(0, Reset),
-	E(1, SetBold),
-	E(2, SetDim),
-	E(3, SetItalic),
-	E(4, SetUnderline),
-	E(5, SetBlink),
-	E(7, SetReverse),
-
-	E(22, UnsetBoldDim),
-	E(23, UnsetItalic),
-	E(24, UnsetUnderline),
-	E(25, UnsetBlink),
-	E(27, UnsetReverse),
-
-	E(30, SetFg0),
-	E(37, SetFg7),
-	E(38, SetFg),
-	E(39, ResetFg),
-	E(40, SetBg0),
-	E(47, SetBg7),
-	E(48, SetBg),
-	E(49, ResetBg),
-
-	E(90, SetFg8),
-	E(97, SetFg15),
-	E(100, SetBg8),
-	E(107, SetBg15),
-
-	E(5, Color256),
+enum {
+	Reset,
+	SetBold,
+	SetDim,
+	SetItalic,
+	SetUnderline,
+	SetBlink,
+	SetReverse = 7,
+
+	UnsetBoldDim = 22,
+	UnsetItalic,
+	UnsetUnderline,
+	UnsetBlink,
+	UnsetReverse = 27,
+
+	SetFg0 = 30,
+	SetFg7 = 37,
+	SetFg,
+	ResetFg,
+	SetBg0 = 40,
+	SetBg7 = 47,
+	SetBg,
+	ResetBg,
+
+	SetFg8 = 90,
+	SetFgF = 97,
+	SetBg8 = 100,
+	SetBgF = 107,
+
+	Color256 = 5,
 };
 
-static void updateSGR(uint ps[], uint n) {
+static const struct Style Default = { .bg = -1, .fg = -1 };
+
+ACTION(sgr) {
+	uint n = t->param.i + 1;
 	for (uint i = 0; i < n; ++i) {
-		switch (ps[i]) {
-			break; case Reset: style = Default;
-
-			break; case SetBold: style.attr &= ~Dim; style.attr |= Bold;
-			break; case SetDim:  style.attr &= ~Bold; style.attr |= Dim;
-			break; case SetItalic:    style.attr |= Italic;
-			break; case SetUnderline: style.attr |= Underline;
-			break; case SetBlink:     style.attr |= Blink;
-			break; case SetReverse:   style.attr |= Reverse;
-
-			break; case UnsetBoldDim:   style.attr &= ~(Bold | Dim);
-			break; case UnsetItalic:    style.attr &= ~Italic;
-			break; case UnsetUnderline: style.attr &= ~Underline;
-			break; case UnsetBlink:     style.attr &= ~Blink;
-			break; case UnsetReverse:   style.attr &= ~Reverse;
+		switch (t->param.s[i]) {
+			break; case Reset: t->style = Default;
+			
+			break; case SetBold: t->style.attr &= ~Dim; t->style.attr |= Bold;
+			break; case SetDim:  t->style.attr &= ~Bold; t->style.attr |= Dim;
+			break; case SetItalic:    t->style.attr |= Italic;
+			break; case SetUnderline: t->style.attr |= Underline;
+			break; case SetBlink:     t->style.attr |= Blink;
+			break; case SetReverse:   t->style.attr |= Reverse;
+
+			break; case UnsetBoldDim:   t->style.attr &= ~(Bold | Dim);
+			break; case UnsetItalic:    t->style.attr &= ~Italic;
+			break; case UnsetUnderline: t->style.attr &= ~Underline;
+			break; case UnsetBlink:     t->style.attr &= ~Blink;
+			break; case UnsetReverse:   t->style.attr &= ~Reverse;
 
 			break; case SetFg: {
-				if (++i < n && ps[i] == Color256) {
-					if (++i < n) style.fg = ps[i];
+				if (++i < n && t->param.s[i] == Color256) {
+					if (++i < n) t->style.fg = t->param.s[i];
 				}
 			}
 			break; case SetBg: {
-				if (++i < n && ps[i] == Color256) {
-					if (++i < n) style.bg = ps[i];
+				if (++i < n && t->param.s[i] == Color256) {
+					if (++i < n) t->style.bg = t->param.s[i];
 				}
 			}
 
-			break; case ResetFg: style.fg = -1;
-			break; case ResetBg: style.bg = -1;
+			break; case ResetFg: t->style.fg = Default.fg;
+			break; case ResetBg: t->style.bg = Default.bg;
 
 			break; default: {
-				if (ps[i] >= SetFg0 && ps[i] <= SetFg7) {
-					style.fg = ps[i] - SetFg0;
-				} else if (ps[i] >= SetBg0 && ps[i] <= SetBg7) {
-					style.bg = ps[i] - SetBg0;
-				} else if (ps[i] >= SetFg8 && ps[i] <= SetFg15) {
-					style.fg = 8 + ps[i] - SetFg8;
-				} else if (ps[i] >= SetBg8 && ps[i] <= SetBg15) {
-					style.bg = 8 + ps[i] - SetBg8;
+				if (t->param.s[i] >= SetFg0 && t->param.s[i] <= SetFg7) {
+					t->style.fg = t->param.s[i] - SetFg0;
+				} else if (t->param.s[i] >= SetBg0 && t->param.s[i] <= SetBg7) {
+					t->style.bg = t->param.s[i] - SetBg0;
+				} else if (t->param.s[i] >= SetFg8 && t->param.s[i] <= SetFgF) {
+					t->style.fg = 8 + t->param.s[i] - SetFg8;
+				} else if (t->param.s[i] >= SetBg8 && t->param.s[i] <= SetBgF) {
+					t->style.bg = 8 + t->param.s[i] - SetBg8;
 				}
 			}
 		}
 	}
 }
 
-// SGR toggling all attributes and setting both colors in 256 palette.
-enum { CSIMax = 5 + 2 * 3 };
-
-enum CSI {
-	E('?', DEC),
-	E('@', ICH),
-	E('A', CUU),
-	E('B', CUD),
-	E('C', CUF),
-	E('D', CUB),
-	E('E', CNL),
-	E('F', CPL),
-	E('G', CHA),
-	E('H', CUP),
-	E('J', ED),
-	E('K', EL),
-	E('L', IL),
-	E('M', DL),
-	E('P', DCH),
-	E('S', SU),
-	E('T', SD),
-	E('X', ECH),
-	E('d', VPA),
-	E('h', SM),
-	E('l', RM),
-	E('m', SGR),
-	E('r', DECSTBM),
-	E(DEC + SM, DECSET),
-	E(DEC + RM, DECRST),
-};
-
-enum Mode {
-	E(4, IRM),
-
-	E(1, DECCKM),
-	E(7, DECAWM),
-	E(25, DECTCEM),
-};
-
-static char updateCSI(wchar_t ch) {
-	static char dec;
-	if (ch == DEC) {
-		dec = DEC;
-		return CSI;
-	}
-
-	static uint n, p, ps[CSIMax];
-	if (ch == ';') {
-		n++;
-		p++;
-		ps[p %= CSIMax] = 0;
-		return CSI;
+static void add(struct Term *t, wchar_t ch) {
+	int width = wcwidth(ch);
+	if (width < 0) {
+		unhandled("\\u%02X", ch);
+		return;
 	}
-	if (ch >= '0' && ch <= '9') {
-		ps[p] *= 10;
-		ps[p] += ch - '0';
-		if (!n) n++;
-		return CSI;
+	if (X + width > t->cols) {
+		unhandled("'%lc' too wide", ch);
+		return;
 	}
 
-	switch (dec + ch) {
-		break; case CUU: y -= MIN((n ? ps[0] : 1), y);
-		break; case CUD: y  = MIN(y + (n ? ps[0] : 1), rows - 1);
-		break; case CUF: x  = MIN(x + (n ? ps[0] : 1), cols - 1);
-		break; case CUB: x -= MIN((n ? ps[0] : 1), x);
-		break; case CNL: y  = MIN(y + (n ? ps[0] : 1), rows - 1); x = 0;
-		break; case CPL: y -= MIN((n ? ps[0] : 1), y); x = 0;
-		break; case CHA: x  = MIN((n ? ps[0] - 1 : 0), cols - 1);
-		break; case VPA: y  = MIN((n ? ps[0] - 1 : 0), rows - 1);
-		break; case CUP: {
-			y = MIN((n > 0 ? ps[0] - 1 : 0), rows - 1);
-			x = MIN((n > 1 ? ps[1] - 1 : 0), cols - 1);
-		}
-
-		break; case ED: {
-			struct Cell *a = cell(0, 0);
-			struct Cell *b = cell(rows - 1, cols - 1);
-			if (ps[0] == 0) a = cell(y, x);
-			if (ps[0] == 1) b = cell(y, x);
-			clear(a, b);
-		}
-		break; case EL: {
-			struct Cell *a = cell(y, 0);
-			struct Cell *b = cell(y, cols - 1);
-			if (ps[0] == 0) a = cell(y, x);
-			if (ps[0] == 1) b = cell(y, x);
-			clear(a, b);
-		}
-		break; case ECH: {
-			struct Cell *a = cell(y, x);
-			struct Cell *b = cell(y, MIN(x + (n > 0 ? ps[0] - 1 : 0), cols - 1));
-			clear(a, b);
-		}
-
-		break; case IL: {
-			uint i = MIN((n ? ps[0] : 1), rows - y);
-			move(cell(y + i, 0), cell(y, 0), cols * (rows - y - i));
-			clear(cell(y, 0), cell(y + i - 1, cols - 1));
-		}
-		break; case ICH: {
-			uint i = MIN((n ? ps[0] : 1), cols - x);
-			move(cell(y, x + i), cell(y, x), cols - x - i);
-			clear(cell(y, x), cell(y, x + i - 1));
-		}
-
-		break; case DL: {
-			uint i = MIN((n ? ps[0] : 1), rows - y);
-			move(cell(y, 0), cell(y + i, 0), cols * (rows - y - i));
-			clear(cell(rows - i, 0), cell(rows - 1, cols - 1));
-		}
-		break; case DCH: {
-			uint i = MIN((n ? ps[0] : 1), cols - x);
-			move(cell(y, x), cell(y, x + i), cols - x - i);
-			clear(cell(y, cols - i), cell(y, cols - 1));
-		}
-
-		break; case SU: scrollUp(MIN((n ? ps[0] : 1), scroll.bot - scroll.top));
-		break; case SD: scrollDown(MIN((n ? ps[0] : 1), scroll.bot - scroll.top));
-
-		break; case SM: {
-			switch (ps[0]) {
-				break; case IRM: insert = true;
-				break; default: unhandled("SM %u", ps[0]);
-			}
-		}
-		break; case RM: {
-			switch (ps[0]) {
-				break; case IRM: insert = false;
-				break; default: unhandled("RM %u", ps[0]);
-			}
-		}
-
-		break; case SGR: updateSGR(ps, p + 1);
-
-		break; case DECSTBM: {
-			// FIXME: Prevent bot < top.
-			scroll.top = (n > 0 ? ps[0] - 1 : 0);
-			scroll.bot = (n > 1 ? ps[1] - 1 : rows - 1);
-		}
-
-		break; case DECSET: {
-			switch (ps[0]) {
-				break; case DECCKM:  // ignore
-				break; case DECAWM:  // TODO
-				break; case 12:      // ignore
-				break; case DECTCEM: cursor = true;
-				break; default: if (ps[0] < 1000) unhandled("DECSET %u", ps[0]);
-			}
-		}
-		break; case DECRST: {
-			switch (ps[0]) {
-				break; case DECCKM:  // ignore
-				break; case DECAWM:  // TODO
-				break; case 12:      // ignore
-				break; case DECTCEM: cursor = false;
-				break; default: if (ps[0] < 1000) unhandled("DECRST %u", ps[0]);
-			}
-		}
-
-		break; case 't': // ignore
-		break; default: unhandled("CSI %s%lc", (dec ? "? " : ""), ch);
+	if (t->mode & Insert) {
+		move(C(Y, X + width), C(Y, X), t->cols - X - width);
 	}
 
-	dec = 0;
-	ps[n = p = 0] = 0;
-	return NUL;
-}
-
-static char updateOSC(wchar_t ch) {
-	static bool esc;
-	switch (ch) {
-		break; case BEL: return NUL;
-		break; case ESC: esc = true;
-		break; case ST: {
-			if (!esc) break;
-			esc = false;
-			return NUL;
-		}
+	C(Y, X)->style = t->style;
+	C(Y, X)->ch = ch;
+	for (int i = 1; i < width; ++i) {
+		C(Y, X + i)->style = t->style;
+		C(Y, X + i)->ch = L'\0';
 	}
-	return OSC;
-}
 
-void termUpdate(wchar_t ch) {
-	static char seq;
-	switch (seq) {
-		break; case NUL: seq = updateNUL(ch);
-		break; case ESC: seq = updateESC(ch);
-		break; case CSI: seq = updateCSI(ch);
-		break; case OSC: seq = updateOSC(ch);
+	if (t->mode & Wrap && X + width >= t->cols) {
+		cr(t, 0);
+		nl(t, 0);
+	} else {
+		X = MIN(X + width, R);
 	}
 }
 
-void termInit(uint _rows, uint _cols) {
-	rows = _rows;
-	cols = _cols;
-	cells = calloc(rows * cols, sizeof(*cells));
-	if (!cells) err(EX_OSERR, "calloc");
+static Action *Actions[][128] = {
+	[NUL][0]   = add,
+	[NUL][BEL] = nop,
+	[NUL][BS]  = bs,
+	[NUL][NL]  = nl,
+	[NUL][CR]  = cr,
+	[NUL][ESC] = esc,
+
+	[ESC][0]   = escUnhandled,
+	[ESC]['('] = g0,
+	[ESC]['7'] = decsc,
+	[ESC]['8'] = decrc,
+	[ESC]['='] = nop,
+	[ESC]['>'] = nop,
+	[ESC]['D'] = ind,
+	[ESC]['M'] = ri,
+	[ESC]['['] = csi,
+	[ESC][']'] = osc,
+	
+	[G0][0]    = nop,
+
+	[CSI][0]   = csiParam,
+	[CSI]['@'] = ich,
+	[CSI]['A'] = cuu,
+	[CSI]['B'] = cud,
+	[CSI]['C'] = cuf,
+	[CSI]['D'] = cub,
+	[CSI]['E'] = cnl,
+	[CSI]['F'] = cpl,
+	[CSI]['G'] = cha,
+	[CSI]['H'] = cup,
+	[CSI]['J'] = ed,
+	[CSI]['K'] = el,
+	[CSI]['L'] = il,
+	[CSI]['M'] = dl,
+	[CSI]['P'] = dch,
+	[CSI]['S'] = su,
+	[CSI]['T'] = sd,
+	[CSI]['X'] = ech,
+	[CSI]['d'] = vpa,
+	[CSI]['h'] = mode,
+	[CSI]['l'] = mode,
+	[CSI]['m'] = sgr,
+	[CSI]['r'] = decstbm,
+	[CSI]['t'] = nop,
+	
+	[OSC][0]   = osc,
+	[OSC][BEL] = nop,
+	[OSC][ESC] = st,
+	[ST][0]    = osc,
+	[ST]['\\'] = nop,
+};
+
+void termUpdate(struct Term *term, wchar_t ch) {
+	assert((uint)term->state < sizeof(Actions) / sizeof(Actions[0]));
+	Action *action = NULL;
+	if (ch < 128) action = Actions[(uint)term->state][ch];
+	if (!action) action = Actions[(uint)term->state][0];
+	assert(action);
+	term->state = NUL;
+	action(term, ch);
+}
 
-	style = Default;
-	clear(cell(0, 0), cell(rows - 1, cols - 1));
-	scroll.bot = rows - 1;
+struct Term *termAlloc(uint rows, uint cols) {
+	size_t size = sizeof(struct Term) + sizeof(struct Cell) * rows * cols;
+	struct Term *term = malloc(size);
+	if (!term) err(EX_OSERR, "malloc");
+
+	memset(term, 0, sizeof(*term));
+	term->rows = rows;
+	term->cols = cols;
+	term->mode = Wrap | Cursor;
+	term->scroll.top = 0;
+	term->scroll.bot = rows - 1;
+	term->style = Default;
+	erase(Default, cell(term, 0, 0), cell(term, rows - 1, cols - 1));
+
+	return term;
 }
 
 static int
 styleDiff(FILE *file, const struct Style *prev, const struct Style *next) {
 	if (!memcmp(prev, next, sizeof(*prev))) return 0;
-	if (!memcmp(next, &Default, sizeof(*next))) {
-		return fprintf(file, "%c%c%c", ESC, CSI, SGR);
-	}
+	if (!memcmp(next, &Default, sizeof(*next))) return fprintf(file, "\33[m");
 
-	uint ps[CSIMax];
+	uint p[ParamCap];
 	uint n = 0;
 
 	enum Attr diff = prev->attr ^ next->attr;
 	if (diff & (Bold | Dim)) {
-		ps[n++] = (
+		p[n++] = (
 			next->attr & Bold ? SetBold
 			: next->attr & Dim ? SetDim
 			: UnsetBoldDim
 		);
 	}
 	if (diff & Italic) {
-		ps[n++] = (next->attr & Italic ? SetItalic : UnsetItalic);
+		p[n++] = (next->attr & Italic ? SetItalic : UnsetItalic);
 	}
 	if (diff & Underline) {
-		ps[n++] = (next->attr & Underline ? SetUnderline : UnsetUnderline);
+		p[n++] = (next->attr & Underline ? SetUnderline : UnsetUnderline);
 	}
 	if (diff & Blink) {
-		ps[n++] = (next->attr & Blink ? SetBlink : UnsetBlink);
+		p[n++] = (next->attr & Blink ? SetBlink : UnsetBlink);
 	}
 	if (diff & Reverse) {
-		ps[n++] = (next->attr & Reverse ? SetReverse : UnsetReverse);
+		p[n++] = (next->attr & Reverse ? SetReverse : UnsetReverse);
 	}
 
 	if (next->bg != prev->bg) {
 		if (next->bg == -1) {
-			ps[n++] = ResetBg;
+			p[n++] = ResetBg;
 		} else if (next->bg < 8) {
-			ps[n++] = SetBg0 + next->bg;
+			p[n++] = SetBg0 + next->bg;
 		} else if (next->bg < 16) {
-			ps[n++] = SetBg8 + next->bg - 8;
+			p[n++] = SetBg8 + next->bg - 8;
 		} else {
-			ps[n++] = SetBg;
-			ps[n++] = Color256;
-			ps[n++] = next->bg;
+			p[n++] = SetBg;
+			p[n++] = Color256;
+			p[n++] = next->bg;
 		}
 	}
 	if (next->fg != prev->fg) {
 		if (next->fg == -1) {
-			ps[n++] = ResetFg;
+			p[n++] = ResetFg;
 		} else if (next->fg < 8) {
-			ps[n++] = SetFg0 + next->fg;
+			p[n++] = SetFg0 + next->fg;
 		} else if (next->fg < 16) {
-			ps[n++] = SetFg8 + next->fg - 8;
+			p[n++] = SetFg8 + next->fg - 8;
 		} else {
-			ps[n++] = SetFg;
-			ps[n++] = Color256;
-			ps[n++] = next->fg;
+			p[n++] = SetFg;
+			p[n++] = Color256;
+			p[n++] = next->fg;
 		}
 	}
 
-	if (0 > fprintf(file, "%c%c%u", ESC, CSI, ps[0])) return -1;
+	if (0 > fprintf(file, "\33[%u", p[0])) return -1;
 	for (uint i = 1; i < n; ++i) {
-		if (0 > fprintf(file, ";%u", ps[i])) return -1;
+		if (0 > fprintf(file, ";%u", p[i])) return -1;
 	}
-	return fprintf(file, "%c", SGR);
+	return fprintf(file, "m");
 }
 
-int termSnapshot(int _fd) {
+int termSnapshot(struct Term *term, int _fd) {
 	int fd = dup(_fd);
 	if (fd < 0) return -1;
 
 	FILE *file = fdopen(fd, "w");
 	if (!file) return -1;
 
+	int n = fprintf(file, "\33[%dl\33[?%dl\33[r\33[H\33[m", IRM, DECAWM);
+	if (n < 0) goto fail;
+
 	struct Style prev = Default;
-	for (uint y = 0; y < rows; ++y) {
+	for (uint y = 0; y < term->rows; ++y) {
 		if (y && 0 > fprintf(file, "\r\n")) goto fail;
-		for (uint x = 0; x < cols; ++x) {
-			if (!cell(y, x)->ch) continue;
-			if (0 > styleDiff(file, &prev, &cell(y, x)->style)) goto fail;
-			if (0 > fprintf(file, "%lc", cell(y, x)->ch)) goto fail;
-			prev = cell(y, x)->style;
+		for (uint x = 0; x < term->cols; ++x) {
+			struct Cell *c = cell(term, y, x);
+			if (!c->ch) continue;
+			if (0 > styleDiff(file, &prev, &c->style)) goto fail;
+			if (0 > fprintf(file, "%lc", c->ch)) goto fail;
+			prev = c->style;
 		}
 	}
-	if (0 > styleDiff(file, &prev, &style)) goto fail;
+	if (0 > styleDiff(file, &prev, &term->style)) goto fail;
 
-	int n = fprintf(
+	n = fprintf(
 		file,
-		"%c%c%d%c"
-		"%c%c%c%d%c"
-		"%c%c%u;%u%c"
-		"%c%c%u;%u%c",
-		ESC, CSI, IRM, (insert ? SM : RM),
-		ESC, CSI, DEC, DECTCEM, (cursor ? DECSET : DECRST) - DEC,
-		ESC, CSI, 1 + scroll.top, 1 + scroll.bot, DECSTBM,
-		ESC, CSI, 1 + y, 1 + x, CUP
+		"\33[%d%c"
+		"\33[?%d%c"
+		"\33[?%d%c"
+		"\33[%u;%ur"
+		"\33[%u;%uH",
+		IRM, (term->mode & Insert ? 'h' : 'l'),
+		DECAWM, (term->mode & Wrap ? 'h' : 'l'),
+		DECTCEM, (term->mode & Cursor ? 'h' : 'l'),
+		1 + term->scroll.top, 1 + term->scroll.bot,
+		1 + term->y, 1 + term->x
 	);
 	if (n < 0) goto fail;
 
@@ -553,14 +535,3 @@ fail:
 	fclose(file);
 	return -1;
 }
-
-struct Display termDisplay(void) {
-	return (struct Display) {
-		.cursor = cursor,
-		.rows = rows,
-		.cols = cols,
-		.y = y,
-		.x = x,
-		.cells = cells,
-	};
-}
diff --git a/stream.h b/term.h
index 4610647..03d9ac5 100644
--- a/stream.h
+++ b/term.h
@@ -21,6 +21,14 @@
 
 typedef unsigned uint;
 
+enum {
+	NUL, SOH, STX, ETX, EOT, ENQ, ACK, BEL,
+	BS, HT, NL, VT, NP, CR, SO, SI,
+	DLE, DC1, DC2, DC3, DC4, NAK, SYN, ETB,
+	CAN, EM, SUB, ESC, FS, GS, RS, US,
+	DEL = 0x7F,
+};
+
 enum Attr {
 	Bold      = 1 << 0,
 	Dim       = 1 << 1,
@@ -40,14 +48,34 @@ struct Cell {
 	wchar_t ch;
 };
 
-struct Display {
-	bool cursor;
+enum Mode {
+	Insert = 1 << 0,
+	Wrap   = 1 << 1,
+	Cursor = 1 << 2,
+};
+
+enum { ParamCap = 16 };
+
+struct Term {
 	uint rows, cols;
+	char state;
+	struct {
+		bool q;
+		uint s[ParamCap];
+		uint n, i;
+	} param;
+	enum Mode mode;
+	struct {
+		uint y, x;
+	} save;
+	struct {
+		uint top, bot;
+	} scroll;
 	uint y, x;
-	const struct Cell *cells;
+	struct Style style;
+	struct Cell cells[];
 };
 
-void termInit(uint rows, uint cols);
-void termUpdate(wchar_t ch);
-int termSnapshot(int fd);
-struct Display termDisplay(void);
+struct Term *termAlloc(uint rows, uint cols);
+void termUpdate(struct Term *term, wchar_t ch);
+int termSnapshot(struct Term *term, int fd);
diff --git a/view.c b/view.c
index a5861c4..7a40123 100644
--- a/view.c
+++ b/view.c
@@ -27,7 +27,7 @@
 #include <sysexits.h>
 #include <unistd.h>
 
-#include "stream.h"
+#include "term.h"
 
 #ifndef A_ITALIC
 #define A_ITALIC A_UNDERLINE
@@ -86,10 +86,10 @@ static attr_t styleAttr(struct Style style) {
 	return attr;
 }
 
-static void render(WINDOW *win, struct Display term) {
-	for (uint y = 0; y < term.rows; ++y) {
-		for (uint x = 0; x < term.cols; ++x) {
-			struct Cell cell = term.cells[term.cols * y + x];
+static void render(WINDOW *win, const struct Term *term) {
+	for (uint y = 0; y < term->rows; ++y) {
+		for (uint x = 0; x < term->cols; ++x) {
+			struct Cell cell = term->cells[term->cols * y + x];
 			if (!cell.ch) continue;
 			wattr_set(
 				win,
@@ -100,9 +100,9 @@ static void render(WINDOW *win, struct Display term) {
 			mvwaddnwstr(win, y, x, &cell.ch, 1);
 		}
 	}
-	curs_set(term.cursor);
-	leaveok(win, !term.cursor);
-	wmove(win, term.y, term.x);
+	curs_set(!!(term->mode & Cursor));
+	leaveok(win, !(term->mode & Cursor));
+	wmove(win, term->y, term->x);
 }
 
 int main(void) {
@@ -111,7 +111,7 @@ int main(void) {
 
 	// TODO: Read info from file.
 	const char *path = "example.sock";
-	termInit(24, 80);
+	struct Term *term = termAlloc(24, 80);
 
 	int client = socket(PF_LOCAL, SOCK_STREAM, 0);
 	if (client < 0) err(EX_OSERR, "socket");
@@ -134,11 +134,11 @@ int main(void) {
 			wchar_t ch;
 			int n = mbtowc(&ch, &buf[pos], len - pos);
 			if (n <= 0) break;
-			termUpdate(ch);
+			termUpdate(term, ch);
 			pos += n;
 		}
 
-		render(win, termDisplay());
+		render(win, term);
 		wrefresh(win);
 	}
 }