summaryrefslogtreecommitdiffstats
path: root/src/libvterm
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/libvterm/.bzrignore13
-rw-r--r--src/libvterm/.gitignore17
-rw-r--r--src/libvterm/LICENSE23
-rw-r--r--src/libvterm/Makefile141
-rw-r--r--src/libvterm/README30
-rw-r--r--src/libvterm/bin/unterm.c288
-rw-r--r--src/libvterm/bin/vterm-ctrl.c368
-rw-r--r--src/libvterm/bin/vterm-dump.c232
-rw-r--r--src/libvterm/doc/URLs14
-rw-r--r--src/libvterm/doc/seqs.txt227
-rw-r--r--src/libvterm/include/vterm.h435
-rw-r--r--src/libvterm/include/vterm_keycodes.h64
-rw-r--r--src/libvterm/src/encoding.c234
-rw-r--r--src/libvterm/src/encoding/DECdrawing.inc136
-rw-r--r--src/libvterm/src/encoding/DECdrawing.tbl31
-rw-r--r--src/libvterm/src/encoding/uk.inc136
-rw-r--r--src/libvterm/src/encoding/uk.tbl1
-rw-r--r--src/libvterm/src/keyboard.c229
-rw-r--r--src/libvterm/src/mouse.c98
-rw-r--r--src/libvterm/src/parser.c346
-rw-r--r--src/libvterm/src/pen.c516
-rw-r--r--src/libvterm/src/rect.h56
-rw-r--r--src/libvterm/src/state.c1925
-rw-r--r--src/libvterm/src/termscreen.c939
-rw-r--r--src/libvterm/src/unicode.c349
-rw-r--r--src/libvterm/src/utf8.h47
-rw-r--r--src/libvterm/src/vterm.c425
-rw-r--r--src/libvterm/src/vterm_internal.h263
-rw-r--r--src/libvterm/t/02parser.test200
-rw-r--r--src/libvterm/t/03encoding_utf8.test122
-rw-r--r--src/libvterm/t/10state_putglyph.test61
-rw-r--r--src/libvterm/t/11state_movecursor.test224
-rw-r--r--src/libvterm/t/12state_scroll.test150
-rw-r--r--src/libvterm/t/13state_edit.test300
-rw-r--r--src/libvterm/t/14state_encoding.test105
-rw-r--r--src/libvterm/t/15state_mode.test86
-rw-r--r--src/libvterm/t/16state_resize.test48
-rw-r--r--src/libvterm/t/17state_mouse.test172
-rw-r--r--src/libvterm/t/18state_termprops.test36
-rw-r--r--src/libvterm/t/20state_wrapping.test69
-rw-r--r--src/libvterm/t/21state_tabstops.test60
-rw-r--r--src/libvterm/t/22state_save.test64
-rw-r--r--src/libvterm/t/25state_input.test143
-rw-r--r--src/libvterm/t/26state_query.test62
-rw-r--r--src/libvterm/t/27state_reset.test32
-rw-r--r--src/libvterm/t/28state_dbl_wh.test61
-rw-r--r--src/libvterm/t/29state_fallback.test19
-rw-r--r--src/libvterm/t/30pen.test106
-rw-r--r--src/libvterm/t/40screen_ascii.test69
-rw-r--r--src/libvterm/t/41screen_unicode.test47
-rw-r--r--src/libvterm/t/42screen_damage.test155
-rw-r--r--src/libvterm/t/43screen_resize.test90
-rw-r--r--src/libvterm/t/44screen_pen.test55
-rw-r--r--src/libvterm/t/45screen_protect.test16
-rw-r--r--src/libvterm/t/46screen_extent.test11
-rw-r--r--src/libvterm/t/47screen_dbl_wh.test32
-rw-r--r--src/libvterm/t/48screen_termprops.test17
-rw-r--r--src/libvterm/t/90vttest_01-movement-1.test87
-rw-r--r--src/libvterm/t/90vttest_01-movement-2.test40
-rw-r--r--src/libvterm/t/90vttest_01-movement-3.test21
-rw-r--r--src/libvterm/t/90vttest_01-movement-4.test36
-rw-r--r--src/libvterm/t/90vttest_02-screen-1.test18
-rw-r--r--src/libvterm/t/90vttest_02-screen-2.test29
-rw-r--r--src/libvterm/t/90vttest_02-screen-3.test16
-rw-r--r--src/libvterm/t/90vttest_02-screen-4.test17
-rw-r--r--src/libvterm/t/92lp1640917.test13
-rw-r--r--src/libvterm/t/harness.c945
-rw-r--r--src/libvterm/t/run-test.pl196
-rw-r--r--src/libvterm/tbl2inc_c.pl51
-rw-r--r--src/libvterm/vterm.pc.in9
70 files changed, 11673 insertions, 0 deletions
diff --git a/src/libvterm/.bzrignore b/src/libvterm/.bzrignore
new file mode 100644
index 0000000..e58c036
--- /dev/null
+++ b/src/libvterm/.bzrignore
@@ -0,0 +1,13 @@
+.libs
+*.lo
+*.la
+
+bin/*
+!bin/*.c
+
+pangoterm
+t/test
+t/suites.h
+t/externs.h
+t/harness
+src/encoding/*.inc
diff --git a/src/libvterm/.gitignore b/src/libvterm/.gitignore
new file mode 100644
index 0000000..5e5e308
--- /dev/null
+++ b/src/libvterm/.gitignore
@@ -0,0 +1,17 @@
+*~
+*.swp
+
+tags
+src/*.o
+src/*.lo
+
+libvterm.la
+bin/unterm
+bin/vterm-ctrl
+bin/vterm-dump
+
+t/harness
+t/harness.lo
+t/harness.o
+
+.libs/
diff --git a/src/libvterm/LICENSE b/src/libvterm/LICENSE
new file mode 100644
index 0000000..0d05163
--- /dev/null
+++ b/src/libvterm/LICENSE
@@ -0,0 +1,23 @@
+
+
+The MIT License
+
+Copyright (c) 2008 Paul Evans <leonerd@leonerd.org.uk>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/src/libvterm/Makefile b/src/libvterm/Makefile
new file mode 100644
index 0000000..2fca6ad
--- /dev/null
+++ b/src/libvterm/Makefile
@@ -0,0 +1,141 @@
+ifeq ($(shell uname),Darwin)
+ LIBTOOL ?= glibtool
+else
+ LIBTOOL ?= libtool
+endif
+
+ifneq ($(VERBOSE),1)
+ LIBTOOL +=--quiet
+endif
+
+override CFLAGS +=-Wall -Iinclude -std=c99 -Wpedantic -DINLINE=""
+
+ifeq ($(shell uname),SunOS)
+ override CFLAGS +=-D__EXTENSIONS__ -D_XPG6 -D__XOPEN_OR_POSIX
+endif
+
+ifeq ($(DEBUG),1)
+ override CFLAGS +=-ggdb -DDEBUG
+endif
+
+ifeq ($(PROFILE),1)
+ override CFLAGS +=-pg
+ override LDFLAGS+=-pg
+endif
+
+CFILES=$(sort $(wildcard src/*.c))
+HFILES=$(sort $(wildcard include/*.h))
+OBJECTS=$(CFILES:.c=.lo)
+LIBRARY=libvterm.la
+
+BINFILES_SRC=$(sort $(wildcard bin/*.c))
+BINFILES=$(BINFILES_SRC:.c=)
+
+TBLFILES=$(sort $(wildcard src/encoding/*.tbl))
+INCFILES=$(TBLFILES:.tbl=.inc)
+
+HFILES_INT=$(sort $(wildcard src/*.h)) $(HFILES)
+
+VERSION_MAJOR=0
+VERSION_MINOR=0
+
+VERSION_CURRENT=0
+VERSION_REVISION=0
+VERSION_AGE=0
+
+VERSION=0
+
+PREFIX=/usr/local
+BINDIR=$(PREFIX)/bin
+LIBDIR=$(PREFIX)/lib
+INCDIR=$(PREFIX)/include
+MANDIR=$(PREFIX)/share/man
+MAN3DIR=$(MANDIR)/man3
+
+# Uncomment to check for memory access errors with valgrind.
+# VALGRIND=1
+
+all: $(LIBRARY) $(BINFILES)
+
+$(LIBRARY): $(OBJECTS)
+ $(LIBTOOL) --mode=link --tag=CC $(CC) -rpath $(LIBDIR) -version-info $(VERSION_CURRENT):$(VERSION_REVISION):$(VERSION_AGE) -o $@ $^ $(LDFLAGS)
+
+src/%.lo: src/%.c $(HFILES_INT)
+ $(LIBTOOL) --mode=compile --tag=CC $(CC) $(CFLAGS) -o $@ -c $<
+
+src/encoding/%.inc: src/encoding/%.tbl
+ perl -CSD tbl2inc_c.pl $< >$@
+
+src/encoding.lo: $(INCFILES)
+
+bin/%: bin/%.c $(LIBRARY)
+ $(LIBTOOL) --mode=link --tag=CC $(CC) $(CFLAGS) -o $@ $< -lvterm $(LDFLAGS)
+
+t/harness.lo: t/harness.c $(HFILES)
+ $(LIBTOOL) --mode=compile --tag=CC $(CC) $(CFLAGS) -o $@ -c $<
+
+t/harness: t/harness.lo $(LIBRARY)
+ $(LIBTOOL) --mode=link --tag=CC $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
+
+.PHONY: test
+test: $(LIBRARY) t/harness
+ for T in `ls t/[0-9]*.test`; do echo "** $$T **"; perl t/run-test.pl $$T $(if $(VALGRIND),--valgrind) || exit 1; done
+
+.PHONY: clean
+clean:
+ $(LIBTOOL) --mode=clean rm -f $(OBJECTS) $(INCFILES)
+ $(LIBTOOL) --mode=clean rm -f t/harness.lo t/harness
+ $(LIBTOOL) --mode=clean rm -f $(LIBRARY) $(BINFILES)
+
+.PHONY: install
+install: install-inc install-lib install-bin
+
+install-inc:
+ install -d $(DESTDIR)$(INCDIR)
+ install -m644 $(HFILES) $(DESTDIR)$(INCDIR)
+ install -d $(DESTDIR)$(LIBDIR)/pkgconfig
+ sed -e "s,@PREFIX@,$(PREFIX)," -e "s,@LIBDIR@,$(LIBDIR)," -e "s,@VERSION@,$(VERSION)," <vterm.pc.in >$(DESTDIR)$(LIBDIR)/pkgconfig/vterm.pc
+
+install-lib: $(LIBRARY)
+ install -d $(DESTDIR)$(LIBDIR)
+ $(LIBTOOL) --mode=install install $(LIBRARY) $(DESTDIR)$(LIBDIR)/$(LIBRARY)
+ $(LIBTOOL) --mode=finish $(DESTDIR)$(LIBDIR)
+
+install-bin: $(BINFILES)
+ install -d $(DESTDIR)$(BINDIR)
+ $(LIBTOOL) --mode=install install $(BINFILES) $(DESTDIR)$(BINDIR)/
+
+# DIST CUT
+
+VERSION=$(VERSION_MAJOR).$(VERSION_MINOR)
+
+DISTDIR=libvterm-$(VERSION)
+
+distdir: $(INCFILES)
+ mkdir __distdir
+ cp LICENSE __distdir
+ mkdir __distdir/src
+ cp src/*.c src/*.h __distdir/src
+ mkdir __distdir/src/encoding
+ cp src/encoding/*.inc __distdir/src/encoding
+ mkdir __distdir/include
+ cp include/*.h __distdir/include
+ mkdir __distdir/bin
+ cp bin/*.c __distdir/bin
+ mkdir __distdir/t
+ cp t/*.test t/harness.c t/run-test.pl __distdir/t
+ sed "s,@VERSION@,$(VERSION)," <vterm.pc.in >__distdir/vterm.pc.in
+ sed "/^# DIST CUT/Q" <Makefile >__distdir/Makefile
+ mv __distdir $(DISTDIR)
+
+TARBALL=$(DISTDIR).tar.gz
+
+dist: distdir
+ tar -czf $(TARBALL) $(DISTDIR)
+ rm -rf $(DISTDIR)
+
+dist+bzr:
+ $(MAKE) dist VERSION=$(VERSION)+bzr`bzr revno`
+
+distdir+bzr:
+ $(MAKE) distdir VERSION=$(VERSION)+bzr`bzr revno`
diff --git a/src/libvterm/README b/src/libvterm/README
new file mode 100644
index 0000000..208066b
--- /dev/null
+++ b/src/libvterm/README
@@ -0,0 +1,30 @@
+This is a MODIFIED version of libvterm.
+
+The original can be found:
+- on the original site (tar archive and Bazaar repository):
+ http://www.leonerd.org.uk/code/libvterm/
+- cloned on Github:
+ https://github.com/neovim/libvterm
+
+Modifications:
+- Add a .gitignore file.
+- Convert from C99 to C90.
+- Other changes to support embedding in Vim.
+
+
+To merge in changes from Github, do this:
+- Commit any pending changes.
+- Setup the merge tool:
+ git config merge.tool vimdiff
+ git config merge.conflictstyle diff3
+ git config mergetool.prompt false
+- Run the merge tool:
+ git mergetool
+ This will open a four-way diff between:
+ LOCAL - your current version
+ BASE - version as it was at your last sync
+ REMOTE - version at head on Github
+ MERGED - best-effort merge of LOCAL and REMOTE
+ Now find places where automatic merge didn't work, they are marked with
+ <<<<<<<<, ======= and >>>>>>>
+ Fix those places in MERGED, remove the markers, and save the file :wqall.
diff --git a/src/libvterm/bin/unterm.c b/src/libvterm/bin/unterm.c
new file mode 100644
index 0000000..5c310d7
--- /dev/null
+++ b/src/libvterm/bin/unterm.c
@@ -0,0 +1,288 @@
+#include <stdio.h>
+#include <string.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <unistd.h>
+
+#include "vterm.h"
+
+#define DEFINE_INLINES
+#include "../src/utf8.h" // fill_utf8
+
+#define streq(a,b) (!strcmp(a,b))
+
+static VTerm *vt;
+static VTermScreen *vts;
+
+static int cols;
+static int rows;
+
+static enum {
+ FORMAT_PLAIN,
+ FORMAT_SGR,
+} format = FORMAT_PLAIN;
+
+static int col2index(VTermColor target)
+{
+ int index;
+
+ for(index = 0; index < 256; index++) {
+ VTermColor col;
+ vterm_state_get_palette_color(NULL, index, &col);
+ if(col.red == target.red && col.green == target.green && col.blue == target.blue)
+ return index;
+ }
+ return -1;
+}
+
+static void dump_cell(const VTermScreenCell *cell, const VTermScreenCell *prevcell)
+{
+ switch(format) {
+ case FORMAT_PLAIN:
+ break;
+ case FORMAT_SGR:
+ {
+ // If all 7 attributes change, that means 7 SGRs max
+ // Each colour could consume up to 3
+ int sgr[7 + 2*3]; int sgri = 0;
+
+ if(!prevcell->attrs.bold && cell->attrs.bold)
+ sgr[sgri++] = 1;
+ if(prevcell->attrs.bold && !cell->attrs.bold)
+ sgr[sgri++] = 22;
+
+ if(!prevcell->attrs.underline && cell->attrs.underline)
+ sgr[sgri++] = 4;
+ if(prevcell->attrs.underline && !cell->attrs.underline)
+ sgr[sgri++] = 24;
+
+ if(!prevcell->attrs.italic && cell->attrs.italic)
+ sgr[sgri++] = 3;
+ if(prevcell->attrs.italic && !cell->attrs.italic)
+ sgr[sgri++] = 23;
+
+ if(!prevcell->attrs.blink && cell->attrs.blink)
+ sgr[sgri++] = 5;
+ if(prevcell->attrs.blink && !cell->attrs.blink)
+ sgr[sgri++] = 25;
+
+ if(!prevcell->attrs.reverse && cell->attrs.reverse)
+ sgr[sgri++] = 7;
+ if(prevcell->attrs.reverse && !cell->attrs.reverse)
+ sgr[sgri++] = 27;
+
+ if(!prevcell->attrs.strike && cell->attrs.strike)
+ sgr[sgri++] = 9;
+ if(prevcell->attrs.strike && !cell->attrs.strike)
+ sgr[sgri++] = 29;
+
+ if(!prevcell->attrs.font && cell->attrs.font)
+ sgr[sgri++] = 10 + cell->attrs.font;
+ if(prevcell->attrs.font && !cell->attrs.font)
+ sgr[sgri++] = 10;
+
+ if(prevcell->fg.red != cell->fg.red ||
+ prevcell->fg.green != cell->fg.green ||
+ prevcell->fg.blue != cell->fg.blue) {
+ int index = col2index(cell->fg);
+ if(index == -1)
+ sgr[sgri++] = 39;
+ else if(index < 8)
+ sgr[sgri++] = 30 + index;
+ else if(index < 16)
+ sgr[sgri++] = 90 + (index - 8);
+ else {
+ sgr[sgri++] = 38;
+ sgr[sgri++] = 5 | CSI_ARG_FLAG_MORE;
+ sgr[sgri++] = index | CSI_ARG_FLAG_MORE;
+ }
+ }
+
+ if(prevcell->bg.red != cell->bg.red ||
+ prevcell->bg.green != cell->bg.green ||
+ prevcell->bg.blue != cell->bg.blue) {
+ int index = col2index(cell->bg);
+ if(index == -1)
+ sgr[sgri++] = 49;
+ else if(index < 8)
+ sgr[sgri++] = 40 + index;
+ else if(index < 16)
+ sgr[sgri++] = 100 + (index - 8);
+ else {
+ sgr[sgri++] = 48;
+ sgr[sgri++] = 5 | CSI_ARG_FLAG_MORE;
+ sgr[sgri++] = index | CSI_ARG_FLAG_MORE;
+ }
+ }
+
+ if(!sgri)
+ break;
+
+ printf("\x1b[");
+ {
+ int i;
+ for(i = 0; i < sgri; i++)
+ printf(!i ? "%d" :
+ CSI_ARG_HAS_MORE(sgr[i]) ? ":%d" :
+ ";%d",
+ CSI_ARG(sgr[i]));
+ }
+ printf("m");
+ }
+ break;
+ }
+
+ {
+ int i;
+ for(i = 0; i < VTERM_MAX_CHARS_PER_CELL && cell->chars[i]; i++) {
+ char bytes[6];
+ bytes[fill_utf8(cell->chars[i], bytes)] = 0;
+ printf("%s", bytes);
+ }
+ }
+}
+
+static void dump_eol(const VTermScreenCell *prevcell)
+{
+ switch(format) {
+ case FORMAT_PLAIN:
+ break;
+ case FORMAT_SGR:
+ if(prevcell->attrs.bold || prevcell->attrs.underline || prevcell->attrs.italic ||
+ prevcell->attrs.blink || prevcell->attrs.reverse || prevcell->attrs.strike ||
+ prevcell->attrs.font)
+ printf("\x1b[m");
+ break;
+ }
+
+ printf("\n");
+}
+
+void dump_row(int row)
+{
+ VTermPos pos;
+ VTermScreenCell prevcell;
+ pos.row = row;
+ pos.col = 0;
+ memset(&prevcell, 0, sizeof(prevcell));
+ vterm_state_get_default_colors(vterm_obtain_state(vt), &prevcell.fg, &prevcell.bg);
+
+ while(pos.col < cols) {
+ VTermScreenCell cell;
+ vterm_screen_get_cell(vts, pos, &cell);
+
+ dump_cell(&cell, &prevcell);
+
+ pos.col += cell.width;
+ prevcell = cell;
+ }
+
+ dump_eol(&prevcell);
+}
+
+static int screen_sb_pushline(int cols, const VTermScreenCell *cells, void *user)
+{
+ VTermScreenCell prevcell;
+ int col;
+
+ memset(&prevcell, 0, sizeof(prevcell));
+ vterm_state_get_default_colors(vterm_obtain_state(vt), &prevcell.fg, &prevcell.bg);
+
+ for(col = 0; col < cols; col++) {
+ dump_cell(cells + col, &prevcell);
+ prevcell = cells[col];
+ }
+
+ dump_eol(&prevcell);
+
+ return 1;
+}
+
+static int screen_resize(int new_rows, int new_cols, void *user)
+{
+ rows = new_rows;
+ cols = new_cols;
+ return 1;
+}
+
+static VTermScreenCallbacks cb_screen = {
+ NULL, /* damage */
+ NULL, /* moverect */
+ NULL, /* movecursor */
+ NULL, /* settermprop */
+ NULL, /* bell */
+ &screen_resize, /* resize */
+ &screen_sb_pushline, /* sb_pushline */
+ NULL, /* popline */
+};
+
+int main(int argc, char *argv[])
+{
+ int opt;
+ const char *file;
+ int fd;
+ int len;
+ char buffer[1024];
+ int row;
+
+ rows = 25;
+ cols = 80;
+
+ while((opt = getopt(argc, argv, "f:l:c:")) != -1) {
+ switch(opt) {
+ case 'f':
+ if(streq(optarg, "plain"))
+ format = FORMAT_PLAIN;
+ else if(streq(optarg, "sgr"))
+ format = FORMAT_SGR;
+ else {
+ fprintf(stderr, "Unrecognised format '%s'\n", optarg);
+ exit(1);
+ }
+ break;
+
+ case 'l':
+ rows = atoi(optarg);
+ if(!rows)
+ rows = 25;
+ break;
+
+ case 'c':
+ cols = atoi(optarg);
+ if(!cols)
+ cols = 80;
+ break;
+ }
+ }
+
+ file = argv[optind++];
+ fd = open(file, O_RDONLY);
+ if(fd == -1) {
+ fprintf(stderr, "Cannot open %s - %s\n", file, strerror(errno));
+ exit(1);
+ }
+
+ vt = vterm_new(rows, cols);
+ vterm_set_utf8(vt, TRUE);
+
+ vts = vterm_obtain_screen(vt);
+ vterm_screen_set_callbacks(vts, &cb_screen, NULL);
+
+ vterm_screen_reset(vts, 1);
+
+ while((len = read(fd, buffer, sizeof(buffer))) > 0) {
+ vterm_input_write(vt, buffer, len);
+ }
+
+ for(row = 0; row < rows; row++) {
+ dump_row(row);
+ }
+
+ close(fd);
+
+ vterm_free(vt);
+
+ return 0;
+}
diff --git a/src/libvterm/bin/vterm-ctrl.c b/src/libvterm/bin/vterm-ctrl.c
new file mode 100644
index 0000000..7c08fe1
--- /dev/null
+++ b/src/libvterm/bin/vterm-ctrl.c
@@ -0,0 +1,368 @@
+#define _XOPEN_SOURCE 500 /* strdup */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#define streq(a,b) (strcmp(a,b)==0)
+#define TRUE 1
+#define FALSE 0
+
+#include <termios.h>
+
+static char *getvalue(int *argip, int argc, char *argv[])
+{
+ if(*argip >= argc) {
+ fprintf(stderr, "Expected an option value\n");
+ exit(1);
+ }
+
+ return argv[(*argip)++];
+}
+
+static int getchoice(int *argip, int argc, char *argv[], const char *options[])
+{
+ const char *arg = getvalue(argip, argc, argv);
+
+ int value = -1;
+ while(options[++value])
+ if(streq(arg, options[value]))
+ return value;
+
+ fprintf(stderr, "Unrecognised option value %s\n", arg);
+ exit(1);
+}
+
+typedef enum {
+ OFF,
+ ON,
+ QUERY,
+} BoolQuery;
+
+static BoolQuery getboolq(int *argip, int argc, char *argv[])
+{
+ const char *choices[] = {"off", "on", "query", NULL};
+ return getchoice(argip, argc, argv, choices);
+}
+
+static char *helptext[] = {
+ "reset",
+ "s8c1t [off|on]",
+ "keypad [app|num]",
+ "screen [off|on|query]",
+ "cursor [off|on|query]",
+ "curblink [off|on|query]",
+ "curshape [block|under|bar|query]",
+ "mouse [off|click|clickdrag|motion]",
+ "reportfocus [off|on|query]",
+ "altscreen [off|on|query]",
+ "bracketpaste [off|on|query]",
+ "icontitle [STR]",
+ "icon [STR]",
+ "title [STR]",
+ NULL
+};
+
+static int seticanon(int icanon, int echo)
+{
+ struct termios termios;
+ int ret;
+
+ tcgetattr(0, &termios);
+
+ ret = (termios.c_lflag & ICANON);
+
+ if(icanon) termios.c_lflag |= ICANON;
+ else termios.c_lflag &= ~ICANON;
+
+ if(echo) termios.c_lflag |= ECHO;
+ else termios.c_lflag &= ~ECHO;
+
+ tcsetattr(0, TCSANOW, &termios);
+
+ return ret;
+}
+
+static void await_c1(unsigned char c1)
+{
+ unsigned char c;
+
+ /* await CSI - 8bit or 2byte 7bit form */
+ int in_esc = FALSE;
+ while((c = getchar())) {
+ if(c == c1)
+ break;
+ if(in_esc && c == (char)(c1 - 0x40))
+ break;
+ if(!in_esc && c == 0x1b)
+ in_esc = TRUE;
+ else
+ in_esc = FALSE;
+ }
+}
+
+static char *read_csi()
+{
+ unsigned char csi[32];
+ int i = 0;
+
+ await_c1(0x9B); // CSI
+
+ /* TODO: This really should be a more robust CSI parser
+ */
+ for(; i < sizeof(csi)-1; i++) {
+ int c = csi[i] = getchar();
+ if(c >= 0x40 && c <= 0x7e)
+ break;
+ }
+ csi[++i] = 0;
+
+ // TODO: returns longer than 32?
+
+ return strdup((char *)csi);
+}
+
+static char *read_dcs()
+{
+ unsigned char dcs[32];
+ int in_esc = FALSE;
+ int i;
+
+ await_c1(0x90);
+
+ for(i = 0; i < sizeof(dcs)-1; ) {
+ char c = getchar();
+ if(c == 0x9c) // ST
+ break;
+ if(in_esc && c == 0x5c)
+ break;
+ if(!in_esc && c == 0x1b)
+ in_esc = TRUE;
+ else {
+ dcs[i++] = c;
+ in_esc = FALSE;
+ }
+ }
+ dcs[++i] = 0;
+
+ return strdup((char *)dcs);
+}
+
+static void usage(int exitcode)
+{
+ char **p;
+
+ fprintf(stderr, "Control a libvterm-based terminal\n"
+ "\n"
+ "Options:\n");
+
+ for(p = helptext; *p; p++)
+ fprintf(stderr, " %s\n", *p);
+
+ exit(exitcode);
+}
+
+static int query_dec_mode(int mode)
+{
+ char *s = NULL;
+
+ printf("\x1b[?%d$p", mode);
+
+ do {
+ int reply_mode, reply_value;
+ char reply_cmd;
+
+ if(s)
+ free(s);
+ s = read_csi();
+
+ /* expect "?" mode ";" value "$y" */
+
+ /* If the sscanf format string ends in a literal, we can't tell from
+ * its return value if it matches. Hence we'll %c the cmd and check it
+ * explicitly
+ */
+ if(sscanf(s, "?%d;%d$%c", &reply_mode, &reply_value, &reply_cmd) < 3)
+ continue;
+ if(reply_cmd != 'y')
+ continue;
+
+ if(reply_mode != mode)
+ continue;
+
+ free(s);
+
+ if(reply_value == 1 || reply_value == 3)
+ return TRUE;
+ if(reply_value == 2 || reply_value == 4)
+ return FALSE;
+
+ printf("Unrecognised reply to DECRQM: %d\n", reply_value);
+ return FALSE;
+ } while(1);
+}
+
+static void do_dec_mode(int mode, BoolQuery val, const char *name)
+{
+ switch(val) {
+ case OFF:
+ case ON:
+ printf("\x1b[?%d%c", mode, val == ON ? 'h' : 'l');
+ break;
+
+ case QUERY:
+ if(query_dec_mode(mode))
+ printf("%s on\n", name);
+ else
+ printf("%s off\n", name);
+ break;
+ }
+}
+
+static int query_rqss_numeric(char *cmd)
+{
+ char *s = NULL;
+
+ printf("\x1bP$q%s\x1b\\", cmd);
+
+ do {
+ int num;
+
+ if(s)
+ free(s);
+ s = read_dcs();
+
+ if(!s)
+ return -1;
+ if(strlen(s) < strlen(cmd))
+ return -1;
+ if(strcmp(s + strlen(s) - strlen(cmd), cmd) != 0) {
+ printf("No match\n");
+ continue;
+ }
+
+ if(s[0] != '1' || s[1] != '$' || s[2] != 'r')
+ return -1;
+
+ if(sscanf(s + 3, "%d", &num) != 1)
+ return -1;
+
+ return num;
+ } while(1);
+}
+
+int wasicanon;
+
+void restoreicanon(void)
+{
+ seticanon(wasicanon, TRUE);
+}
+
+int main(int argc, char *argv[])
+{
+ int argi = 1;
+
+ if(argc == 1)
+ usage(0);
+
+ wasicanon = seticanon(FALSE, FALSE);
+ atexit(restoreicanon);
+
+ while(argi < argc) {
+ const char *arg = argv[argi++];
+
+ if(streq(arg, "reset")) {
+ printf("\x1b" "c");
+ }
+ else if(streq(arg, "s8c1t")) {
+ const char *choices[] = {"off", "on", NULL};
+ switch(getchoice(&argi, argc, argv, choices)) {
+ case 0:
+ printf("\x1b F"); break;
+ case 1:
+ printf("\x1b G"); break;
+ }
+ }
+ else if(streq(arg, "keypad")) {
+ const char *choices[] = {"app", "num", NULL};
+ switch(getchoice(&argi, argc, argv, choices)) {
+ case 0:
+ printf("\x1b="); break;
+ case 1:
+ printf("\x1b>"); break;
+ }
+ }
+ else if(streq(arg, "screen")) {
+ do_dec_mode(5, getboolq(&argi, argc, argv), "screen");
+ }
+ else if(streq(arg, "cursor")) {
+ do_dec_mode(25, getboolq(&argi, argc, argv), "cursor");
+ }
+ else if(streq(arg, "curblink")) {
+ do_dec_mode(12, getboolq(&argi, argc, argv), "curblink");
+ }
+ else if(streq(arg, "curshape")) {
+ // TODO: This ought to query the current value of DECSCUSR because it
+ // may need blinking on or off
+ const char *choices[] = {"block", "under", "bar", "query", NULL};
+ int shape = getchoice(&argi, argc, argv, choices);
+ switch(shape) {
+ case 3: // query
+ shape = query_rqss_numeric(" q");
+ switch(shape) {
+ case 1: case 2:
+ printf("curshape block\n");
+ break;
+ case 3: case 4:
+ printf("curshape under\n");
+ break;
+ case 5: case 6:
+ printf("curshape bar\n");
+ break;
+ }
+ break;
+
+ case 0:
+ case 1:
+ case 2:
+ printf("\x1b[%d q", 1 + (shape * 2));
+ break;
+ }
+ }
+ else if(streq(arg, "mouse")) {
+ const char *choices[] = {"off", "click", "clickdrag", "motion", NULL};
+ switch(getchoice(&argi, argc, argv, choices)) {
+ case 0:
+ printf("\x1b[?1000l"); break;
+ case 1:
+ printf("\x1b[?1000h"); break;
+ case 2:
+ printf("\x1b[?1002h"); break;
+ case 3:
+ printf("\x1b[?1003h"); break;
+ }
+ }
+ else if(streq(arg, "reportfocus")) {
+ do_dec_mode(1004, getboolq(&argi, argc, argv), "reportfocus");
+ }
+ else if(streq(arg, "altscreen")) {
+ do_dec_mode(1049, getboolq(&argi, argc, argv), "altscreen");
+ }
+ else if(streq(arg, "bracketpaste")) {
+ do_dec_mode(2004, getboolq(&argi, argc, argv), "bracketpaste");
+ }
+ else if(streq(arg, "icontitle")) {
+ printf("\x1b]0;%s\a", getvalue(&argi, argc, argv));
+ }
+ else if(streq(arg, "icon")) {
+ printf("\x1b]1;%s\a", getvalue(&argi, argc, argv));
+ }
+ else if(streq(arg, "title")) {
+ printf("\x1b]2;%s\a", getvalue(&argi, argc, argv));
+ }
+ else {
+ fprintf(stderr, "Unrecognised command %s\n", arg);
+ exit(1);
+ }
+ }
+ return 0;
+}
diff --git a/src/libvterm/bin/vterm-dump.c b/src/libvterm/bin/vterm-dump.c
new file mode 100644
index 0000000..a299d9c
--- /dev/null
+++ b/src/libvterm/bin/vterm-dump.c
@@ -0,0 +1,232 @@
+// Require getopt(3)
+#define _XOPEN_SOURCE
+
+#include <stdio.h>
+#include <string.h>
+#define streq(a,b) (strcmp(a,b)==0)
+
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "vterm.h"
+
+static const char *special_begin = "{";
+static const char *special_end = "}";
+
+static int parser_text(const char bytes[], size_t len, void *user)
+{
+ unsigned char *b = (unsigned char *)bytes;
+
+ int i;
+ for(i = 0; i < len; /* none */) {
+ if(b[i] < 0x20) // C0
+ break;
+ else if(b[i] < 0x80) // ASCII
+ i++;
+ else if(b[i] < 0xa0) // C1
+ break;
+ else if(b[i] < 0xc0) // UTF-8 continuation
+ break;
+ else if(b[i] < 0xe0) { // UTF-8 2-byte
+ // 2-byte UTF-8
+ if(len < i+2) break;
+ i += 2;
+ }
+ else if(b[i] < 0xf0) { // UTF-8 3-byte
+ if(len < i+3) break;
+ i += 3;
+ }
+ else if(b[i] < 0xf8) { // UTF-8 4-byte
+ if(len < i+4) break;
+ i += 4;
+ }
+ else // otherwise invalid
+ break;
+ }
+
+ printf("%.*s", i, b);
+ return i;
+}
+
+/* 0 1 2 3 4 5 6 7 8 9 A B C D E F */
+static const char *name_c0[] = {
+ "NUL", "SOH", "STX", "ETX", "EOT", "ENQ", "ACK", "BEL", "BS", "HT", "LF", "VT", "FF", "CR", "LS0", "LS1",
+ "DLE", "DC1", "DC2", "DC3", "DC4", "NAK", "SYN", "ETB", "CAN", "EM", "SUB", "ESC", "FS", "GS", "RS", "US",
+};
+static const char *name_c1[] = {
+ NULL, NULL, "BPH", "NBH", NULL, "NEL", "SSA", "ESA", "HTS", "HTJ", "VTS", "PLD", "PLU", "RI", "SS2", "SS3",
+ "DCS", "PU1", "PU2", "STS", "CCH", "MW", "SPA", "EPA", "SOS", NULL, "SCI", "CSI", "ST", "OSC", "PM", "APC",
+};
+
+static int parser_control(unsigned char control, void *user)
+{
+ if(control < 0x20)
+ printf("%s%s%s", special_begin, name_c0[control], special_end);
+ else if(control >= 0x80 && control < 0xa0 && name_c1[control - 0x80])
+ printf("%s%s%s", special_begin, name_c1[control - 0x80], special_end);
+ else
+ printf("%sCONTROL 0x%02x%s", special_begin, control, special_end);
+
+ if(control == 0x0a)
+ printf("\n");
+ return 1;
+}
+
+static int parser_escape(const char bytes[], size_t len, void *user)
+{
+ if(bytes[0] >= 0x20 && bytes[0] < 0x30) {
+ if(len < 2)
+ return -1;
+ len = 2;
+ }
+ else {
+ len = 1;
+ }
+
+ printf("%sESC %.*s%s", special_begin, (int)len, bytes, special_end);
+
+ return len;
+}
+
+/* 0 1 2 3 4 5 6 7 8 9 A B C D E F */
+static const char *name_csi_plain[] = {
+ "ICH", "CUU", "CUD", "CUF", "CUB", "CNL", "CPL", "CHA", "CUP", "CHT", "ED", "EL", "IL", "DL", "EF", "EA",
+ "DCH", "SSE", "CPR", "SU", "SD", "NP", "PP", "CTC", "ECH", "CVT", "CBT", "SRS", "PTX", "SDS", "SIMD",NULL,
+ "HPA", "HPR", "REP", "DA", "VPA", "VPR", "HVP", "TBC", "SM", "MC", "HPB", "VPB", "RM", "SGR", "DSR", "DAQ",
+};
+
+/*0 4 8 B */
+static const int newline_csi_plain[] = {
+ 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0,
+};
+
+static int parser_csi(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user)
+{
+ const char *name = NULL;
+ if(!leader && !intermed && command < 0x70)
+ name = name_csi_plain[command - 0x40];
+ else if(leader && streq(leader, "?") && !intermed) {
+ /* DEC */
+ switch(command) {
+ case 'h': name = "DECSM"; break;
+ case 'l': name = "DECRM"; break;
+ }
+ if(name)
+ leader = NULL;
+ }
+
+ if(!leader && !intermed && command < 0x70 && newline_csi_plain[command - 0x40])
+ printf("\n");
+
+ if(name)
+ printf("%s%s", special_begin, name);
+ else
+ printf("%sCSI", special_begin);
+
+ if(leader && leader[0])
+ printf(" %s", leader);
+
+ {
+ int i;
+ for(i = 0; i < argcount; i++) {
+ printf(i ? "," : " ");
+ }
+
+ if(args[i] == CSI_ARG_MISSING)
+ printf("*");
+ else {
+ while(CSI_ARG_HAS_MORE(args[i]))
+ printf("%ld+", CSI_ARG(args[i++]));
+ printf("%ld", CSI_ARG(args[i]));
+ }
+ }
+
+ if(intermed && intermed[0])
+ printf(" %s", intermed);
+
+ if(name)
+ printf("%s", special_end);
+ else
+ printf(" %c%s", command, special_end);
+
+ return 1;
+}
+
+static int parser_osc(const char *command, size_t cmdlen, void *user)
+{
+ printf("%sOSC %.*s%s", special_begin, (int)cmdlen, command, special_end);
+
+ return 1;
+}
+
+static int parser_dcs(const char *command, size_t cmdlen, void *user)
+{
+ printf("%sDCS %.*s%s", special_begin, (int)cmdlen, command, special_end);
+
+ return 1;
+}
+
+static VTermParserCallbacks parser_cbs = {
+ &parser_text, /* text */
+ &parser_control, /* control */
+ &parser_escape, /* escape */
+ &parser_csi, /* csi */
+ &parser_osc, /* osc */
+ &parser_dcs, /* dcs */
+ NULL /* resize */
+};
+
+int main(int argc, char *argv[])
+{
+ int use_colour = isatty(1);
+ const char *file;
+ int fd;
+ VTerm *vt;
+ int len;
+ char buffer[1024];
+
+ int opt;
+ while((opt = getopt(argc, argv, "c")) != -1) {
+ switch(opt) {
+ case 'c': use_colour = 1; break;
+ }
+ }
+
+ file = argv[optind++];
+
+ if(!file || streq(file, "-"))
+ fd = 0; // stdin
+ else {
+ fd = open(file, O_RDONLY);
+ if(fd == -1) {
+ fprintf(stderr, "Cannot open %s - %s\n", file, strerror(errno));
+ exit(1);
+ }
+ }
+
+ if(use_colour) {
+ special_begin = "\x1b[7m{";
+ special_end = "}\x1b[m";
+ }
+
+ /* Size matters not for the parser */
+ vt = vterm_new(25, 80);
+ vterm_set_utf8(vt, 1);
+ vterm_parser_set_callbacks(vt, &parser_cbs, NULL);
+
+ while((len = read(fd, buffer, sizeof(buffer))) > 0) {
+ vterm_input_write(vt, buffer, len);
+ }
+
+ printf("\n");
+
+ close(fd);
+ vterm_free(vt);
+
+ return 0;
+}
diff --git a/src/libvterm/doc/URLs b/src/libvterm/doc/URLs
new file mode 100644
index 0000000..8380f5c
--- /dev/null
+++ b/src/libvterm/doc/URLs
@@ -0,0 +1,14 @@
+ECMA-48:
+ http://www.ecma-international.org/publications/standards/Ecma-048.htm
+
+Xterm Control Sequences:
+ http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
+
+Digital VT100 User Guide:
+ http://vt100.net/docs/vt100-ug/
+
+Digital VT220 Programmer Reference Manual
+ http://vt100.net/docs/vt220-rm/
+
+Summary of ANSI standards for ASCII terminals
+ http://www.inwap.com/pdp10/ansicode.txt
diff --git a/src/libvterm/doc/seqs.txt b/src/libvterm/doc/seqs.txt
new file mode 100644
index 0000000..c26dc44
--- /dev/null
+++ b/src/libvterm/doc/seqs.txt
@@ -0,0 +1,227 @@
+Sequences documented in parens are implicit ones from parser.c, which move
+between states.
+
+1 = VT100
+2 = VT220
+3 = VT320
+
+ C0 controls
+
+123 0x00 = NUL
+123 0x07 = BEL
+123 0x08 = BS
+123 0x09 = HT
+123 0x0A = LF
+123 0x0B = VT
+123 0x0C = FF
+123 0x0D = CR
+123 0x0E = LS1
+123 0x0F = LS0
+ (0x18 = CAN)
+ (0x1A = SUB)
+ (0x1B = ESC)
+
+123 0x7f = DEL (ignored)
+
+ C1 controls
+
+123 0x84 = IND
+123 0x85 = NEL
+123 0x88 = HTS
+123 0x8D = RI
+ 23 0x8e = SS2
+ 23 0x8f = SS3
+ (0x90 = DCS)
+ (0x9B = CSI)
+ (0x9C = ST)
+ (0x9D = OSC)
+
+ Escape sequences
+ - excluding sequences that are C1 aliases
+
+123 ESC () = SCS, select character set (G0, G1)
+ 23 ESC *+ = SCS, select character set (G2, G3)
+123 ESC 7 = DECSC - save cursor
+123 ESC 8 = DECRC - restore cursor
+123 ESC # 3 = DECDHL, double-height line (top half)
+123 ESC # 4 = DECDHL, double-height line (bottom half)
+123 ESC # 5 = DECSWL, single-width single-height line
+123 ESC # 6 = DECDWL, double-width single-height line
+123 ESC # 8 = DECALN
+123 ESC < = Ignored (used by VT100 to exit VT52 mode)
+123 ESC = = DECKPAM, keypad application mode
+123 ESC > = DECKPNM, keypad numeric mode
+ 23 ESC Sp F = S7C1T
+ 23 ESC Sp G = S8C1T
+ (ESC P = DCS)
+ (ESC [ = CSI)
+ (ESC \ = ST)
+ (ESC ] = OSC)
+123 ESC c = RIS, reset initial state
+ 3 ESC n = LS2
+ 3 ESC o = LS3
+ 3 ESC ~ = LS1R
+ 3 ESC } = LS2R
+ 3 ESC | = LS3R
+
+ DCSes
+
+ 3 DCS $ q ST = DECRQSS
+ 3 m = Request SGR
+ Sp q = Request DECSCUSR
+ 3 " q = Request DECSCA
+ 3 r = Request DECSTBM
+ s = Request DECSLRM
+
+ CSIs
+ 23 CSI @ = ICH
+123 CSI A = CUU
+123 CSI B = CUD
+123 CSI C = CUF
+123 CSI D = CUB
+ CSI E = CNL
+ CSI F = CPL
+ CSI G = CHA
+123 CSI H = CUP
+ CSI I = CHT
+123 CSI J = ED
+ 23 CSI ? J = DECSED, selective erase in display
+123 CSI K = EL
+ 23 CSI ? K = DECSEL, selective erase in line
+ 23 CSI L = IL
+ 23 CSI M = DL
+ 23 CSI P = DCH
+ CSI S = SU
+ CSI T = SD
+ 23 CSI X = ECH
+ CSI Z = CBT
+ CSI ` = HPA
+ CSI a = HPR
+123 CSI c = DA, device attributes
+123 0 = DA
+ 23 CSI > c = DECSDA
+ 23 0 = SDA
+ CSI d = VPA
+ CSI e = VPR
+123 CSI f = HVP
+123 CSI g = TBC
+123 CSI h = SM, Set mode
+123 CSI ? h = DECSM, DEC set mode
+ CSI j = HPB
+ CSI k = VPB
+123 CSI l = RM, Reset mode
+123 CSI ? l = DECRM, DEC reset mode
+123 CSI m = SGR, Set Graphic Rendition
+123 CSI n = DSR, Device Status Report
+ 23 5 = operating status
+ 23 6 = CPR = cursor position
+ 23 CSI ? n = DECDSR; behaves as DSR but uses CSI ? instead of CSI to respond
+ 23 CSI ! p = DECSTR, soft terminal reset
+ 3 CSI ? $ p = DECRQM, request mode
+ CSI Sp q = DECSCUSR (odd numbers blink, even numbers solid)
+ 1 or 2 = block
+ 3 or 4 = underline
+ 5 or 6 = I-beam to left
+ 23 CSI " q = DECSCA, select character attributes
+123 CSI r = DECSTBM
+ CSI s = DECSLRM
+ CSI ' } = DECIC
+ CSI ' ~ = DECDC
+
+ OSCs
+
+ OSC 0; = Set icon name and title
+ OSC 1; = Set icon name
+ OSC 2; = Set title
+
+ Standard modes
+
+ 23 SM 4 = IRM
+123 SM 20 = NLM, linefeed/newline
+
+ DEC modes
+
+123 DECSM 1 = DECCKM, cursor keys
+123 DECSM 5 = DECSCNM, screen
+123 DECSM 6 = DECOM, origin
+123 DECSM 7 = DECAWM, autowrap
+ DECSM 12 = Cursor blink
+ 23 DECSM 25 = DECTCEM, text cursor enable
+ DECSM 69 = DECVSSM, vertical screen split
+ DECSM 1000 = Mouse click/release tracking
+ DECSM 1002 = Mouse click/release/drag tracking
+ DECSM 1003 = Mouse all movements tracking
+ DECSM 1004 = Focus in/out reporting
+ DECSM 1005 = Mouse protocol extended (UTF-8) - not recommended
+ DECSM 1006 = Mouse protocol SGR
+ DECSM 1015 = Mouse protocol rxvt
+ DECSM 1047 = Altscreen
+ DECSM 1048 = Save cursor
+ DECSM 1049 = 1047 + 1048
+ DECSM 2004 = Bracketed paste
+
+ Graphic Renditions
+
+123 SGR 0 = Reset
+123 SGR 1 = Bold on
+ SGR 3 = Italic on
+123 SGR 4 = Underline single
+123 SGR 5 = Blink on
+123 SGR 7 = Reverse on
+ SGR 9 = Strikethrough on
+ SGR 10-19 = Select font
+ SGR 21 = Underline double
+ 23 SGR 22 = Bold off
+ SGR 23 = Italic off
+ 23 SGR 24 = Underline off
+ 23 SGR 25 = Blink off
+ 23 SGR 27 = Reverse off
+ SGR 29 = Strikethrough off
+ SGR 30-37 = Foreground ANSI
+ SGR 38 = Foreground alternative palette
+ SGR 39 = Foreground default
+ SGR 40-47 = Background ANSI
+ SGR 48 = Background alternative palette
+ SGR 49 = Background default
+ SGR 90-97 = Foreground ANSI high-intensity
+ SGR 100-107 = Background ANSI high-intensity
+
+The state storage used by ESC 7 and DECSM 1048/1049 is shared.
+
+ Unimplemented sequences:
+
+The following sequences are not recognised by libvterm.
+
+123 0x05 = ENQ
+ 3 0x11 = DC1 (XON)
+ 3 0x13 = DC3 (XOFF)
+12 ESC Z = DECID, identify terminal
+ DCS $ q = [DECRQSS]
+ 3 " p = Request DECSCL
+ 3 $ } = Request DECSASD
+ 3 $ ~ = Request DECSSDT
+ 23 DCS { = DECDLD, down-line-loadable character set
+ 23 DCS | = DECUDK, user-defined key
+ 23 CSI i = DEC printer control
+ 23 CSI " p = DECSCL, set compatibility level
+1 CSI q = DECLL, load LEDs
+ 3 CSI $ u = DECRQTSR, request terminal state report
+ 3 1 = terminal state report
+ 3 CSI & u = DECRQUPSS, request user-preferred supplemental set
+ 3 CSI $ w = DECRQPSR, request presentation state report
+ 3 1 = cursor information report
+ 3 2 = tab stop report
+1 CSI x = DECREQTPARM, request terminal parameters
+123 CSI y = DECTST, invoke confidence test
+ 3 CSI $ } = DECSASD, select active status display
+ 3 CSI $ ~ = DECSSDT, select status line type
+ 23 SM 2 = KAM, keyboard action
+123 SM 12 = SRM, send/receive
+123 DECSM 2 = DECANM, ANSI/VT52
+123 DECSM 3 = DECCOLM, 132 column
+123 DECSM 4 = DECSCLM, scrolling
+123 DECSM 8 = DECARM, auto-repeat
+12 DECSM 9 = DECINLM, interlace
+ 23 DECSM 18 = DECPFF, print form feed
+ 23 DECSM 19 = DECPEX, print extent
+ 23 DECSM 42 = DECNRCM, national/multinational character
diff --git a/src/libvterm/include/vterm.h b/src/libvterm/include/vterm.h
new file mode 100644
index 0000000..3b77cd2
--- /dev/null
+++ b/src/libvterm/include/vterm.h
@@ -0,0 +1,435 @@
+/*
+ * NOTE: This is a MODIFIED version of libvterm, see the README file.
+ */
+#ifndef __VTERM_H__
+#define __VTERM_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdlib.h>
+
+#include "vterm_keycodes.h"
+
+#define TRUE 1
+#define FALSE 0
+
+/* from stdint.h */
+typedef unsigned char uint8_t;
+typedef unsigned int uint32_t;
+
+typedef struct VTerm VTerm;
+typedef struct VTermState VTermState;
+typedef struct VTermScreen VTermScreen;
+
+/* Specifies a screen point. */
+typedef struct {
+ int row;
+ int col;
+} VTermPos;
+
+/*
+ * Some small utility functions; we can just keep these static here.
+ */
+
+/*
+ * Order points by on-screen flow order:
+ * Return < 0 if "a" is before "b"
+ * Return 0 if "a" and "b" are equal
+ * Return > 0 if "a" is after "b".
+ */
+int vterm_pos_cmp(VTermPos a, VTermPos b);
+
+#if defined(DEFINE_INLINES) || USE_INLINE
+INLINE int vterm_pos_cmp(VTermPos a, VTermPos b)
+{
+ return (a.row == b.row) ? a.col - b.col : a.row - b.row;
+}
+#endif
+
+/* Specifies a rectangular screen area. */
+typedef struct {
+ int start_row;
+ int end_row;
+ int start_col;
+ int end_col;
+} VTermRect;
+
+/* Return true if the rect "r" contains the point "p". */
+int vterm_rect_contains(VTermRect r, VTermPos p);
+
+#if defined(DEFINE_INLINES) || USE_INLINE
+INLINE int vterm_rect_contains(VTermRect r, VTermPos p)
+{
+ return p.row >= r.start_row && p.row < r.end_row &&
+ p.col >= r.start_col && p.col < r.end_col;
+}
+#endif
+
+/* Move "rect" "row_delta" down and "col_delta" right.
+ * Does not check boundaries. */
+void vterm_rect_move(VTermRect *rect, int row_delta, int col_delta);
+
+#if defined(DEFINE_INLINES) || USE_INLINE
+INLINE void vterm_rect_move(VTermRect *rect, int row_delta, int col_delta)
+{
+ rect->start_row += row_delta; rect->end_row += row_delta;
+ rect->start_col += col_delta; rect->end_col += col_delta;
+}
+#endif
+
+/* The ansi_index is used for the lower 16 colors, which can be set to any
+ * color. */
+#define VTERM_ANSI_INDEX_DEFAULT 0 /* color cleared */
+#define VTERM_ANSI_INDEX_MIN 1
+#define VTERM_ANSI_INDEX_MAX 16
+#define VTERM_ANSI_INDEX_NONE 255 /* non-ANSI color, use red/green/blue */
+
+typedef struct {
+ uint8_t red, green, blue;
+ uint8_t ansi_index;
+} VTermColor;
+
+typedef enum {
+ /* VTERM_VALUETYPE_NONE = 0 */
+ VTERM_VALUETYPE_BOOL = 1,
+ VTERM_VALUETYPE_INT,
+ VTERM_VALUETYPE_STRING,
+ VTERM_VALUETYPE_COLOR,
+
+ VTERM_N_VALUETYPES
+} VTermValueType;
+
+typedef union {
+ int boolean;
+ int number;
+ char *string;
+ VTermColor color;
+} VTermValue;
+
+typedef enum {
+ /* VTERM_ATTR_NONE = 0 */
+ VTERM_ATTR_BOLD = 1, // bool: 1, 22
+ VTERM_ATTR_UNDERLINE, // number: 4, 21, 24
+ VTERM_ATTR_ITALIC, // bool: 3, 23
+ VTERM_ATTR_BLINK, // bool: 5, 25
+ VTERM_ATTR_REVERSE, // bool: 7, 27
+ VTERM_ATTR_STRIKE, // bool: 9, 29
+ VTERM_ATTR_FONT, // number: 10-19
+ VTERM_ATTR_FOREGROUND, // color: 30-39 90-97
+ VTERM_ATTR_BACKGROUND, // color: 40-49 100-107
+
+ VTERM_N_ATTRS
+} VTermAttr;
+
+typedef enum {
+ /* VTERM_PROP_NONE = 0 */
+ VTERM_PROP_CURSORVISIBLE = 1, // bool
+ VTERM_PROP_CURSORBLINK, // bool
+ VTERM_PROP_ALTSCREEN, // bool
+ VTERM_PROP_TITLE, // string
+ VTERM_PROP_ICONNAME, // string
+ VTERM_PROP_REVERSE, // bool
+ VTERM_PROP_CURSORSHAPE, // number
+ VTERM_PROP_MOUSE, // number
+ VTERM_PROP_CURSORCOLOR, // string
+
+ VTERM_N_PROPS
+} VTermProp;
+
+enum {
+ VTERM_PROP_CURSORSHAPE_BLOCK = 1,
+ VTERM_PROP_CURSORSHAPE_UNDERLINE,
+ VTERM_PROP_CURSORSHAPE_BAR_LEFT,
+
+ VTERM_N_PROP_CURSORSHAPES
+};
+
+enum {
+ VTERM_PROP_MOUSE_NONE = 0,
+ VTERM_PROP_MOUSE_CLICK,
+ VTERM_PROP_MOUSE_DRAG,
+ VTERM_PROP_MOUSE_MOVE,
+
+ VTERM_N_PROP_MOUSES
+};
+
+typedef struct {
+ const uint32_t *chars;
+ int width;
+ unsigned int protected_cell:1; /* DECSCA-protected against DECSEL/DECSED */
+ unsigned int dwl:1; /* DECDWL or DECDHL double-width line */
+ unsigned int dhl:2; /* DECDHL double-height line (1=top 2=bottom) */
+} VTermGlyphInfo;
+
+typedef struct {
+ unsigned int doublewidth:1; /* DECDWL or DECDHL line */
+ unsigned int doubleheight:2; /* DECDHL line (1=top 2=bottom) */
+} VTermLineInfo;
+
+typedef struct {
+ /* libvterm relies on the allocated memory to be zeroed out before it is
+ * returned by the allocator. */
+ void *(*malloc)(size_t size, void *allocdata);
+ void (*free)(void *ptr, void *allocdata);
+} VTermAllocatorFunctions;
+
+/* Allocate and initialize a new terminal with default allocators. */
+VTerm *vterm_new(int rows, int cols);
+
+/* Allocate and initialize a new terminal with specified allocators. */
+VTerm *vterm_new_with_allocator(int rows, int cols, VTermAllocatorFunctions *funcs, void *allocdata);
+
+/* Free and cleanup a terminal and all its data. */
+void vterm_free(VTerm* vt);
+
+/* Get the current size of the terminal and store in "rowsp" and "colsp". */
+void vterm_get_size(const VTerm *vt, int *rowsp, int *colsp);
+
+void vterm_set_size(VTerm *vt, int rows, int cols);
+
+int vterm_get_utf8(const VTerm *vt);
+void vterm_set_utf8(VTerm *vt, int is_utf8);
+
+size_t vterm_input_write(VTerm *vt, const char *bytes, size_t len);
+
+size_t vterm_output_get_buffer_size(const VTerm *vt);
+size_t vterm_output_get_buffer_current(const VTerm *vt);
+size_t vterm_output_get_buffer_remaining(const VTerm *vt);
+
+size_t vterm_output_read(VTerm *vt, char *buffer, size_t len);
+
+void vterm_keyboard_unichar(VTerm *vt, uint32_t c, VTermModifier mod);
+void vterm_keyboard_key(VTerm *vt, VTermKey key, VTermModifier mod);
+
+void vterm_keyboard_start_paste(VTerm *vt);
+void vterm_keyboard_end_paste(VTerm *vt);
+
+void vterm_mouse_move(VTerm *vt, int row, int col, VTermModifier mod);
+/* "button" is 1 for left, 2 for middle, 3 for right.
+ * Button 4 is scroll wheel down, button 5 is scroll wheel up. */
+void vterm_mouse_button(VTerm *vt, int button, int pressed, VTermModifier mod);
+
+// ------------
+// Parser layer
+// ------------
+
+/* Flag to indicate non-final subparameters in a single CSI parameter.
+ * Consider
+ * CSI 1;2:3:4;5a
+ * 1 4 and 5 are final.
+ * 2 and 3 are non-final and will have this bit set
+ *
+ * Don't confuse this with the final byte of the CSI escape; 'a' in this case.
+ */
+#define CSI_ARG_FLAG_MORE (1U<<31)
+#define CSI_ARG_MASK (~(1U<<31))
+
+#define CSI_ARG_HAS_MORE(a) ((a) & CSI_ARG_FLAG_MORE)
+#define CSI_ARG(a) ((a) & CSI_ARG_MASK)
+
+/* Can't use -1 to indicate a missing argument; use this instead */
+#define CSI_ARG_MISSING ((1<<30)-1)
+
+#define CSI_ARG_IS_MISSING(a) (CSI_ARG(a) == CSI_ARG_MISSING)
+#define CSI_ARG_OR(a,def) (CSI_ARG(a) == CSI_ARG_MISSING ? (def) : CSI_ARG(a))
+#define CSI_ARG_COUNT(a) (CSI_ARG(a) == CSI_ARG_MISSING || CSI_ARG(a) == 0 ? 1 : CSI_ARG(a))
+
+typedef struct {
+ int (*text)(const char *bytes, size_t len, void *user);
+ int (*control)(unsigned char control, void *user);
+ int (*escape)(const char *bytes, size_t len, void *user);
+ int (*csi)(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user);
+ int (*osc)(const char *command, size_t cmdlen, void *user);
+ int (*dcs)(const char *command, size_t cmdlen, void *user);
+ int (*resize)(int rows, int cols, void *user);
+} VTermParserCallbacks;
+
+void vterm_parser_set_callbacks(VTerm *vt, const VTermParserCallbacks *callbacks, void *user);
+void *vterm_parser_get_cbdata(VTerm *vt);
+
+// -----------
+// State layer
+// -----------
+
+typedef struct {
+ int (*putglyph)(VTermGlyphInfo *info, VTermPos pos, void *user);
+ int (*movecursor)(VTermPos pos, VTermPos oldpos, int visible, void *user);
+ int (*scrollrect)(VTermRect rect, int downward, int rightward, void *user);
+ int (*moverect)(VTermRect dest, VTermRect src, void *user);
+ int (*erase)(VTermRect rect, int selective, void *user);
+ int (*initpen)(void *user);
+ int (*setpenattr)(VTermAttr attr, VTermValue *val, void *user);
+ /* Callback for setting various properties. Must return 1 if the property
+ * was accepted, 0 otherwise. */
+ int (*settermprop)(VTermProp prop, VTermValue *val, void *user);
+ int (*bell)(void *user);
+ int (*resize)(int rows, int cols, VTermPos *delta, void *user);
+ int (*setlineinfo)(int row, const VTermLineInfo *newinfo, const VTermLineInfo *oldinfo, void *user);
+} VTermStateCallbacks;
+
+typedef struct {
+ VTermPos pos;
+ int buttons;
+#define MOUSE_BUTTON_LEFT 0x01
+#define MOUSE_BUTTON_MIDDLE 0x02
+#define MOUSE_BUTTON_RIGHT 0x04
+ int flags;
+#define MOUSE_WANT_CLICK 0x01
+#define MOUSE_WANT_DRAG 0x02
+#define MOUSE_WANT_MOVE 0x04
+ /* useful to add protocol? */
+} VTermMouseState;
+
+VTermState *vterm_obtain_state(VTerm *vt);
+
+void vterm_state_set_callbacks(VTermState *state, const VTermStateCallbacks *callbacks, void *user);
+void *vterm_state_get_cbdata(VTermState *state);
+
+// Only invokes control, csi, osc, dcs
+void vterm_state_set_unrecognised_fallbacks(VTermState *state, const VTermParserCallbacks *fallbacks, void *user);
+void *vterm_state_get_unrecognised_fbdata(VTermState *state);
+
+/* Initialize the state. */
+void vterm_state_reset(VTermState *state, int hard);
+
+void vterm_state_get_cursorpos(const VTermState *state, VTermPos *cursorpos);
+void vterm_state_get_mousestate(const VTermState *state, VTermMouseState *mousestate);
+void vterm_state_get_default_colors(const VTermState *state, VTermColor *default_fg, VTermColor *default_bg);
+void vterm_state_get_palette_color(const VTermState *state, int index, VTermColor *col);
+void vterm_state_set_default_colors(VTermState *state, const VTermColor *default_fg, const VTermColor *default_bg);
+void vterm_state_set_palette_color(VTermState *state, int index, const VTermColor *col);
+void vterm_state_set_bold_highbright(VTermState *state, int bold_is_highbright);
+int vterm_state_get_penattr(const VTermState *state, VTermAttr attr, VTermValue *val);
+int vterm_state_set_termprop(VTermState *state, VTermProp prop, VTermValue *val);
+void vterm_state_focus_in(VTermState *state);
+void vterm_state_focus_out(VTermState *state);
+const VTermLineInfo *vterm_state_get_lineinfo(const VTermState *state, int row);
+
+// ------------
+// Screen layer
+// ------------
+
+typedef struct {
+ unsigned int bold : 1;
+ unsigned int underline : 2;
+ unsigned int italic : 1;
+ unsigned int blink : 1;
+ unsigned int reverse : 1;
+ unsigned int strike : 1;
+ unsigned int font : 4; /* 0 to 9 */
+ unsigned int dwl : 1; /* On a DECDWL or DECDHL line */
+ unsigned int dhl : 2; /* On a DECDHL line (1=top 2=bottom) */
+} VTermScreenCellAttrs;
+
+typedef struct {
+#define VTERM_MAX_CHARS_PER_CELL 6
+ uint32_t chars[VTERM_MAX_CHARS_PER_CELL];
+ char width;
+ VTermScreenCellAttrs attrs;
+ VTermColor fg, bg;
+} VTermScreenCell;
+
+/* All fields are optional, NULL when not used. */
+typedef struct {
+ int (*damage)(VTermRect rect, void *user);
+ int (*moverect)(VTermRect dest, VTermRect src, void *user);
+ int (*movecursor)(VTermPos pos, VTermPos oldpos, int visible, void *user);
+ int (*settermprop)(VTermProp prop, VTermValue *val, void *user);
+ int (*bell)(void *user);
+ int (*resize)(int rows, int cols, void *user);
+ /* A line was pushed off the top of the window.
+ * "cells[cols]" contains the cells of that line.
+ * Return value is unused. */
+ int (*sb_pushline)(int cols, const VTermScreenCell *cells, void *user);
+ int (*sb_popline)(int cols, VTermScreenCell *cells, void *user);
+} VTermScreenCallbacks;
+
+/* Return the screen of the vterm. */
+VTermScreen *vterm_obtain_screen(VTerm *vt);
+
+/*
+ * Install screen callbacks. These are invoked when the screen contents is
+ * changed. "user" is passed into to the callback.
+ */
+void vterm_screen_set_callbacks(VTermScreen *screen, const VTermScreenCallbacks *callbacks, void *user);
+void *vterm_screen_get_cbdata(VTermScreen *screen);
+
+// Only invokes control, csi, osc, dcs
+void vterm_screen_set_unrecognised_fallbacks(VTermScreen *screen, const VTermParserCallbacks *fallbacks, void *user);
+void *vterm_screen_get_unrecognised_fbdata(VTermScreen *screen);
+
+/* Enable support for using the alternate screen if "altscreen" is non-zero.
+ * Before that switching to the alternate screen won't work.
+ * Calling with "altscreen" zero has no effect. */
+void vterm_screen_enable_altscreen(VTermScreen *screen, int altscreen);
+
+typedef enum {
+ VTERM_DAMAGE_CELL, /* every cell */
+ VTERM_DAMAGE_ROW, /* entire rows */
+ VTERM_DAMAGE_SCREEN, /* entire screen */
+ VTERM_DAMAGE_SCROLL, /* entire screen + scrollrect */
+
+ VTERM_N_DAMAGES
+} VTermDamageSize;
+
+/* Invoke the relevant callbacks to update the screen. */
+void vterm_screen_flush_damage(VTermScreen *screen);
+
+void vterm_screen_set_damage_merge(VTermScreen *screen, VTermDamageSize size);
+
+/*
+ * Reset the screen. Also invokes vterm_state_reset().
+ * Must be called before the terminal can be used.
+ */
+void vterm_screen_reset(VTermScreen *screen, int hard);
+
+/* Neither of these functions NUL-terminate the buffer */
+size_t vterm_screen_get_chars(const VTermScreen *screen, uint32_t *chars, size_t len, const VTermRect rect);
+size_t vterm_screen_get_text(const VTermScreen *screen, char *str, size_t len, const VTermRect rect);
+
+typedef enum {
+ VTERM_ATTR_BOLD_MASK = 1 << 0,
+ VTERM_ATTR_UNDERLINE_MASK = 1 << 1,
+ VTERM_ATTR_ITALIC_MASK = 1 << 2,
+ VTERM_ATTR_BLINK_MASK = 1 << 3,
+ VTERM_ATTR_REVERSE_MASK = 1 << 4,
+ VTERM_ATTR_STRIKE_MASK = 1 << 5,
+ VTERM_ATTR_FONT_MASK = 1 << 6,
+ VTERM_ATTR_FOREGROUND_MASK = 1 << 7,
+ VTERM_ATTR_BACKGROUND_MASK = 1 << 8,
+
+ VTERM_ALL_ATTRS_MASK = (1 << 9) - 1
+} VTermAttrMask;
+
+int vterm_screen_get_attrs_extent(const VTermScreen *screen, VTermRect *extent, VTermPos pos, VTermAttrMask attrs);
+
+int vterm_screen_get_cell(const VTermScreen *screen, VTermPos pos, VTermScreenCell *cell);
+
+int vterm_screen_is_eol(const VTermScreen *screen, VTermPos pos);
+
+// ---------
+// Utilities
+// ---------
+
+VTermValueType vterm_get_attr_type(VTermAttr attr);
+VTermValueType vterm_get_prop_type(VTermProp prop);
+
+void vterm_scroll_rect(VTermRect rect,
+ int downward,
+ int rightward,
+ int (*moverect)(VTermRect src, VTermRect dest, void *user),
+ int (*eraserect)(VTermRect rect, int selective, void *user),
+ void *user);
+
+void vterm_copy_cells(VTermRect dest,
+ VTermRect src,
+ void (*copycell)(VTermPos dest, VTermPos src, void *user),
+ void *user);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/libvterm/include/vterm_keycodes.h b/src/libvterm/include/vterm_keycodes.h
new file mode 100644
index 0000000..22bdf91
--- /dev/null
+++ b/src/libvterm/include/vterm_keycodes.h
@@ -0,0 +1,64 @@
+#ifndef __VTERM_INPUT_H__
+#define __VTERM_INPUT_H__
+
+typedef enum {
+ VTERM_MOD_NONE = 0x00,
+ VTERM_MOD_SHIFT = 0x01,
+ VTERM_MOD_ALT = 0x02,
+ VTERM_MOD_CTRL = 0x04,
+
+ VTERM_ALL_MODS_MASK = 0x07
+} VTermModifier;
+
+/* The order here must match keycodes[] in src/keyboard.c! */
+typedef enum {
+ VTERM_KEY_NONE,
+
+ VTERM_KEY_ENTER,
+ VTERM_KEY_TAB,
+ VTERM_KEY_BACKSPACE,
+ VTERM_KEY_ESCAPE,
+
+ VTERM_KEY_UP,
+ VTERM_KEY_DOWN,
+ VTERM_KEY_LEFT,
+ VTERM_KEY_RIGHT,
+
+ VTERM_KEY_INS,
+ VTERM_KEY_DEL,
+ VTERM_KEY_HOME,
+ VTERM_KEY_END,
+ VTERM_KEY_PAGEUP,
+ VTERM_KEY_PAGEDOWN,
+
+ /* F1 is VTERM_KEY_FUNCTION(1), F2 VTERM_KEY_FUNCTION(2), etc. */
+ VTERM_KEY_FUNCTION_0 = 256,
+ VTERM_KEY_FUNCTION_MAX = VTERM_KEY_FUNCTION_0 + 255,
+
+ /* keypad keys */
+ VTERM_KEY_KP_0,
+ VTERM_KEY_KP_1,
+ VTERM_KEY_KP_2,
+ VTERM_KEY_KP_3,
+ VTERM_KEY_KP_4,
+ VTERM_KEY_KP_5,
+ VTERM_KEY_KP_6,
+ VTERM_KEY_KP_7,
+ VTERM_KEY_KP_8,
+ VTERM_KEY_KP_9,
+ VTERM_KEY_KP_MULT,
+ VTERM_KEY_KP_PLUS,
+ VTERM_KEY_KP_COMMA,
+ VTERM_KEY_KP_MINUS,
+ VTERM_KEY_KP_PERIOD,
+ VTERM_KEY_KP_DIVIDE,
+ VTERM_KEY_KP_ENTER,
+ VTERM_KEY_KP_EQUAL,
+
+ VTERM_KEY_MAX, // Must be last
+ VTERM_N_KEYS = VTERM_KEY_MAX
+} VTermKey;
+
+#define VTERM_KEY_FUNCTION(n) (VTERM_KEY_FUNCTION_0+(n))
+
+#endif
diff --git a/src/libvterm/src/encoding.c b/src/libvterm/src/encoding.c
new file mode 100644
index 0000000..dbe129c
--- /dev/null
+++ b/src/libvterm/src/encoding.c
@@ -0,0 +1,234 @@
+#include "vterm_internal.h"
+
+#define UNICODE_INVALID 0xFFFD
+
+#if defined(DEBUG) && DEBUG > 1
+# define DEBUG_PRINT_UTF8
+#endif
+
+struct UTF8DecoderData {
+ // number of bytes remaining in this codepoint
+ int bytes_remaining;
+
+ // number of bytes total in this codepoint once it's finished
+ // (for detecting overlongs)
+ int bytes_total;
+
+ int this_cp;
+};
+
+static void init_utf8(VTermEncoding *enc UNUSED, void *data_)
+{
+ struct UTF8DecoderData *data = data_;
+
+ data->bytes_remaining = 0;
+ data->bytes_total = 0;
+}
+
+static void decode_utf8(VTermEncoding *enc UNUSED, void *data_,
+ uint32_t cp[], int *cpi, int cplen,
+ const char bytes[], size_t *pos, size_t bytelen)
+{
+ struct UTF8DecoderData *data = data_;
+
+#ifdef DEBUG_PRINT_UTF8
+ printf("BEGIN UTF-8\n");
+#endif
+
+ for(; *pos < bytelen && *cpi < cplen; (*pos)++) {
+ unsigned char c = bytes[*pos];
+
+#ifdef DEBUG_PRINT_UTF8
+ printf(" pos=%zd c=%02x rem=%d\n", *pos, c, data->bytes_remaining);
+#endif
+
+ if(c < 0x20) // C0
+ return;
+
+ else if(c >= 0x20 && c < 0x7f) {
+ if(data->bytes_remaining) {
+ data->bytes_remaining = 0;
+ cp[(*cpi)++] = UNICODE_INVALID;
+ if (*cpi >= cplen)
+ break;
+ }
+ cp[(*cpi)++] = c;
+#ifdef DEBUG_PRINT_UTF8
+ printf(" UTF-8 char: U+%04x\n", c);
+#endif
+ }
+
+ else if(c == 0x7f) // DEL
+ return;
+
+ else if(c >= 0x80 && c < 0xc0) {
+ if(!data->bytes_remaining) {
+ cp[(*cpi)++] = UNICODE_INVALID;
+ continue;
+ }
+
+ data->this_cp <<= 6;
+ data->this_cp |= c & 0x3f;
+ data->bytes_remaining--;
+
+ if(!data->bytes_remaining) {
+#ifdef DEBUG_PRINT_UTF8
+ printf(" UTF-8 raw char U+%04x bytelen=%d ", data->this_cp, data->bytes_total);
+#endif
+ // Check for overlong sequences
+ switch(data->bytes_total) {
+ case 2:
+ if(data->this_cp < 0x0080) data->this_cp = UNICODE_INVALID;
+ break;
+ case 3:
+ if(data->this_cp < 0x0800) data->this_cp = UNICODE_INVALID;
+ break;
+ case 4:
+ if(data->this_cp < 0x10000) data->this_cp = UNICODE_INVALID;
+ break;
+ case 5:
+ if(data->this_cp < 0x200000) data->this_cp = UNICODE_INVALID;
+ break;
+ case 6:
+ if(data->this_cp < 0x4000000) data->this_cp = UNICODE_INVALID;
+ break;
+ }
+ // Now look for plain invalid ones
+ if((data->this_cp >= 0xD800 && data->this_cp <= 0xDFFF) ||
+ data->this_cp == 0xFFFE ||
+ data->this_cp == 0xFFFF)
+ data->this_cp = UNICODE_INVALID;
+#ifdef DEBUG_PRINT_UTF8
+ printf(" char: U+%04x\n", data->this_cp);
+#endif
+ cp[(*cpi)++] = data->this_cp;
+ }
+ }
+
+ else if(c >= 0xc0 && c < 0xe0) {
+ if(data->bytes_remaining)
+ cp[(*cpi)++] = UNICODE_INVALID;
+
+ data->this_cp = c & 0x1f;
+ data->bytes_total = 2;
+ data->bytes_remaining = 1;
+ }
+
+ else if(c >= 0xe0 && c < 0xf0) {
+ if(data->bytes_remaining)
+ cp[(*cpi)++] = UNICODE_INVALID;
+
+ data->this_cp = c & 0x0f;
+ data->bytes_total = 3;
+ data->bytes_remaining = 2;
+ }
+
+ else if(c >= 0xf0 && c < 0xf8) {
+ if(data->bytes_remaining)
+ cp[(*cpi)++] = UNICODE_INVALID;
+
+ data->this_cp = c & 0x07;
+ data->bytes_total = 4;
+ data->bytes_remaining = 3;
+ }
+
+ else if(c >= 0xf8 && c < 0xfc) {
+ if(data->bytes_remaining)
+ cp[(*cpi)++] = UNICODE_INVALID;
+
+ data->this_cp = c & 0x03;
+ data->bytes_total = 5;
+ data->bytes_remaining = 4;
+ }
+
+ else if(c >= 0xfc && c < 0xfe) {
+ if(data->bytes_remaining)
+ cp[(*cpi)++] = UNICODE_INVALID;
+
+ data->this_cp = c & 0x01;
+ data->bytes_total = 6;
+ data->bytes_remaining = 5;
+ }
+
+ else {
+ cp[(*cpi)++] = UNICODE_INVALID;
+ }
+ }
+}
+
+static VTermEncoding encoding_utf8 = {
+ &init_utf8, /* init */
+ &decode_utf8 /* decode */
+};
+
+static void decode_usascii(VTermEncoding *enc UNUSED, void *data UNUSED,
+ uint32_t cp[], int *cpi, int cplen,
+ const char bytes[], size_t *pos, size_t bytelen)
+{
+ int is_gr = bytes[*pos] & 0x80;
+
+ for(; *pos < bytelen && *cpi < cplen; (*pos)++) {
+ unsigned char c = bytes[*pos] ^ is_gr;
+
+ if(c < 0x20 || c == 0x7f || c >= 0x80)
+ return;
+
+ cp[(*cpi)++] = c;
+ }
+}
+
+static VTermEncoding encoding_usascii = {
+ NULL, /* init */
+ &decode_usascii /* decode */
+};
+
+struct StaticTableEncoding {
+ const VTermEncoding enc;
+ const uint32_t chars[128];
+};
+
+static void decode_table(VTermEncoding *enc, void *data UNUSED,
+ uint32_t cp[], int *cpi, int cplen,
+ const char bytes[], size_t *pos, size_t bytelen)
+{
+ struct StaticTableEncoding *table = (struct StaticTableEncoding *)enc;
+ int is_gr = bytes[*pos] & 0x80;
+
+ for(; *pos < bytelen && *cpi < cplen; (*pos)++) {
+ unsigned char c = bytes[*pos] ^ is_gr;
+
+ if(c < 0x20 || c == 0x7f || c >= 0x80)
+ return;
+
+ if(table->chars[c])
+ cp[(*cpi)++] = table->chars[c];
+ else
+ cp[(*cpi)++] = c;
+ }
+}
+
+#include "encoding/DECdrawing.inc"
+#include "encoding/uk.inc"
+
+static struct {
+ VTermEncodingType type;
+ char designation;
+ VTermEncoding *enc;
+}
+encodings[] = {
+ { ENC_UTF8, 'u', &encoding_utf8 },
+ { ENC_SINGLE_94, '0', (VTermEncoding*)&encoding_DECdrawing },
+ { ENC_SINGLE_94, 'A', (VTermEncoding*)&encoding_uk },
+ { ENC_SINGLE_94, 'B', &encoding_usascii },
+ { 0, 0, NULL },
+};
+
+/* This ought to be INTERNAL but isn't because it's used by unit testing */
+VTermEncoding *vterm_lookup_encoding(VTermEncodingType type, char designation)
+{
+ int i;
+ for(i = 0; encodings[i].designation; i++)
+ if(encodings[i].type == type && encodings[i].designation == designation)
+ return encodings[i].enc;
+ return NULL;
+}
diff --git a/src/libvterm/src/encoding/DECdrawing.inc b/src/libvterm/src/encoding/DECdrawing.inc
new file mode 100644
index 0000000..978f98c
--- /dev/null
+++ b/src/libvterm/src/encoding/DECdrawing.inc
@@ -0,0 +1,136 @@
+static const struct StaticTableEncoding encoding_DECdrawing = {
+ {
+ NULL, /* init */
+ &decode_table /* decode */
+ },
+ {
+ 0x0, /* 0 */
+ 0x0, /* 1 */
+ 0x0, /* 2 */
+ 0x0, /* 3 */
+ 0x0, /* 4 */
+ 0x0, /* 5 */
+ 0x0, /* 6 */
+ 0x0, /* 7 */
+ 0x0, /* 8 */
+ 0x0, /* 9 */
+ 0x0, /* 10 */
+ 0x0, /* 11 */
+ 0x0, /* 12 */
+ 0x0, /* 13 */
+ 0x0, /* 14 */
+ 0x0, /* 15 */
+ 0x0, /* 16 */
+ 0x0, /* 17 */
+ 0x0, /* 18 */
+ 0x0, /* 19 */
+ 0x0, /* 20 */
+ 0x0, /* 21 */
+ 0x0, /* 22 */
+ 0x0, /* 23 */
+ 0x0, /* 24 */
+ 0x0, /* 25 */
+ 0x0, /* 26 */
+ 0x0, /* 27 */
+ 0x0, /* 28 */
+ 0x0, /* 29 */
+ 0x0, /* 30 */
+ 0x0, /* 31 */
+ 0x0, /* 32 */
+ 0x0, /* 33 */
+ 0x0, /* 34 */
+ 0x0, /* 35 */
+ 0x0, /* 36 */
+ 0x0, /* 37 */
+ 0x0, /* 38 */
+ 0x0, /* 39 */
+ 0x0, /* 40 */
+ 0x0, /* 41 */
+ 0x0, /* 42 */
+ 0x0, /* 43 */
+ 0x0, /* 44 */
+ 0x0, /* 45 */
+ 0x0, /* 46 */
+ 0x0, /* 47 */
+ 0x0, /* 48 */
+ 0x0, /* 49 */
+ 0x0, /* 50 */
+ 0x0, /* 51 */
+ 0x0, /* 52 */
+ 0x0, /* 53 */
+ 0x0, /* 54 */
+ 0x0, /* 55 */
+ 0x0, /* 56 */
+ 0x0, /* 57 */
+ 0x0, /* 58 */
+ 0x0, /* 59 */
+ 0x0, /* 60 */
+ 0x0, /* 61 */
+ 0x0, /* 62 */
+ 0x0, /* 63 */
+ 0x0, /* 64 */
+ 0x0, /* 65 */
+ 0x0, /* 66 */
+ 0x0, /* 67 */
+ 0x0, /* 68 */
+ 0x0, /* 69 */
+ 0x0, /* 70 */
+ 0x0, /* 71 */
+ 0x0, /* 72 */
+ 0x0, /* 73 */
+ 0x0, /* 74 */
+ 0x0, /* 75 */
+ 0x0, /* 76 */
+ 0x0, /* 77 */
+ 0x0, /* 78 */
+ 0x0, /* 79 */
+ 0x0, /* 80 */
+ 0x0, /* 81 */
+ 0x0, /* 82 */
+ 0x0, /* 83 */
+ 0x0, /* 84 */
+ 0x0, /* 85 */
+ 0x0, /* 86 */
+ 0x0, /* 87 */
+ 0x0, /* 88 */
+ 0x0, /* 89 */
+ 0x0, /* 90 */
+ 0x0, /* 91 */
+ 0x0, /* 92 */
+ 0x0, /* 93 */
+ 0x0, /* 94 */
+ 0x0, /* 95 */
+ 0x25C6, /* 96 */
+ 0x2592, /* 97 */
+ 0x2409, /* 98 */
+ 0x240C, /* 99 */
+ 0x240D, /* 100 */
+ 0x240A, /* 101 */
+ 0x00B0, /* 102 */
+ 0x00B1, /* 103 */
+ 0x2424, /* 104 */
+ 0x240B, /* 105 */
+ 0x2518, /* 106 */
+ 0x2510, /* 107 */
+ 0x250C, /* 108 */
+ 0x2514, /* 109 */
+ 0x253C, /* 110 */
+ 0x23BA, /* 111 */
+ 0x23BB, /* 112 */
+ 0x2500, /* 113 */
+ 0x23BC, /* 114 */
+ 0x23BD, /* 115 */
+ 0x251C, /* 116 */
+ 0x2524, /* 117 */
+ 0x2534, /* 118 */
+ 0x252C, /* 119 */
+ 0x2502, /* 120 */
+ 0x2A7D, /* 121 */
+ 0x2A7E, /* 122 */
+ 0x03C0, /* 123 */
+ 0x2260, /* 124 */
+ 0x00A3, /* 125 */
+ 0x00B7, /* 126 */
+ 0x0, /* 127 */
+ }
+};
diff --git a/src/libvterm/src/encoding/DECdrawing.tbl b/src/libvterm/src/encoding/DECdrawing.tbl
new file mode 100644
index 0000000..6e19c50
--- /dev/null
+++ b/src/libvterm/src/encoding/DECdrawing.tbl
@@ -0,0 +1,31 @@
+6/0 = U+25C6 # BLACK DIAMOND
+6/1 = U+2592 # MEDIUM SHADE (checkerboard)
+6/2 = U+2409 # SYMBOL FOR HORIZONTAL TAB
+6/3 = U+240C # SYMBOL FOR FORM FEED
+6/4 = U+240D # SYMBOL FOR CARRIAGE RETURN
+6/5 = U+240A # SYMBOL FOR LINE FEED
+6/6 = U+00B0 # DEGREE SIGN
+6/7 = U+00B1 # PLUS-MINUS SIGN (plus or minus)
+6/8 = U+2424 # SYMBOL FOR NEW LINE
+6/9 = U+240B # SYMBOL FOR VERTICAL TAB
+6/10 = U+2518 # BOX DRAWINGS LIGHT UP AND LEFT (bottom-right corner)
+6/11 = U+2510 # BOX DRAWINGS LIGHT DOWN AND LEFT (top-right corner)
+6/12 = U+250C # BOX DRAWINGS LIGHT DOWN AND RIGHT (top-left corner)
+6/13 = U+2514 # BOX DRAWINGS LIGHT UP AND RIGHT (bottom-left corner)
+6/14 = U+253C # BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL (crossing lines)
+6/15 = U+23BA # HORIZONTAL SCAN LINE-1
+7/0 = U+23BB # HORIZONTAL SCAN LINE-3
+7/1 = U+2500 # BOX DRAWINGS LIGHT HORIZONTAL
+7/2 = U+23BC # HORIZONTAL SCAN LINE-7
+7/3 = U+23BD # HORIZONTAL SCAN LINE-9
+7/4 = U+251C # BOX DRAWINGS LIGHT VERTICAL AND RIGHT
+7/5 = U+2524 # BOX DRAWINGS LIGHT VERTICAL AND LEFT
+7/6 = U+2534 # BOX DRAWINGS LIGHT UP AND HORIZONTAL
+7/7 = U+252C # BOX DRAWINGS LIGHT DOWN AND HORIZONTAL
+7/8 = U+2502 # BOX DRAWINGS LIGHT VERTICAL
+7/9 = U+2A7D # LESS-THAN OR SLANTED EQUAL-TO
+7/10 = U+2A7E # GREATER-THAN OR SLANTED EQUAL-TO
+7/11 = U+03C0 # GREEK SMALL LETTER PI
+7/12 = U+2260 # NOT EQUAL TO
+7/13 = U+00A3 # POUND SIGN
+7/14 = U+00B7 # MIDDLE DOT
diff --git a/src/libvterm/src/encoding/uk.inc b/src/libvterm/src/encoding/uk.inc
new file mode 100644
index 0000000..49ced5a
--- /dev/null
+++ b/src/libvterm/src/encoding/uk.inc
@@ -0,0 +1,136 @@
+static const struct StaticTableEncoding encoding_uk = {
+ {
+ NULL, /* init */
+ &decode_table /* decode */
+ },
+ {
+ 0x0, /* 0 */
+ 0x0, /* 1 */
+ 0x0, /* 2 */
+ 0x0, /* 3 */
+ 0x0, /* 4 */
+ 0x0, /* 5 */
+ 0x0, /* 6 */
+ 0x0, /* 7 */
+ 0x0, /* 8 */
+ 0x0, /* 9 */
+ 0x0, /* 10 */
+ 0x0, /* 11 */
+ 0x0, /* 12 */
+ 0x0, /* 13 */
+ 0x0, /* 14 */
+ 0x0, /* 15 */
+ 0x0, /* 16 */
+ 0x0, /* 17 */
+ 0x0, /* 18 */
+ 0x0, /* 19 */
+ 0x0, /* 20 */
+ 0x0, /* 21 */
+ 0x0, /* 22 */
+ 0x0, /* 23 */
+ 0x0, /* 24 */
+ 0x0, /* 25 */
+ 0x0, /* 26 */
+ 0x0, /* 27 */
+ 0x0, /* 28 */
+ 0x0, /* 29 */
+ 0x0, /* 30 */
+ 0x0, /* 31 */
+ 0x0, /* 32 */
+ 0x0, /* 33 */
+ 0x0, /* 34 */
+ 0x00a3, /* 35 */
+ 0x0, /* 36 */
+ 0x0, /* 37 */
+ 0x0, /* 38 */
+ 0x0, /* 39 */
+ 0x0, /* 40 */
+ 0x0, /* 41 */
+ 0x0, /* 42 */
+ 0x0, /* 43 */
+ 0x0, /* 44 */
+ 0x0, /* 45 */
+ 0x0, /* 46 */
+ 0x0, /* 47 */
+ 0x0, /* 48 */
+ 0x0, /* 49 */
+ 0x0, /* 50 */
+ 0x0, /* 51 */
+ 0x0, /* 52 */
+ 0x0, /* 53 */
+ 0x0, /* 54 */
+ 0x0, /* 55 */
+ 0x0, /* 56 */
+ 0x0, /* 57 */
+ 0x0, /* 58 */
+ 0x0, /* 59 */
+ 0x0, /* 60 */
+ 0x0, /* 61 */
+ 0x0, /* 62 */
+ 0x0, /* 63 */
+ 0x0, /* 64 */
+ 0x0, /* 65 */
+ 0x0, /* 66 */
+ 0x0, /* 67 */
+ 0x0, /* 68 */
+ 0x0, /* 69 */
+ 0x0, /* 70 */
+ 0x0, /* 71 */
+ 0x0, /* 72 */
+ 0x0, /* 73 */
+ 0x0, /* 74 */
+ 0x0, /* 75 */
+ 0x0, /* 76 */
+ 0x0, /* 77 */
+ 0x0, /* 78 */
+ 0x0, /* 79 */
+ 0x0, /* 80 */
+ 0x0, /* 81 */
+ 0x0, /* 82 */
+ 0x0, /* 83 */
+ 0x0, /* 84 */
+ 0x0, /* 85 */
+ 0x0, /* 86 */
+ 0x0, /* 87 */
+ 0x0, /* 88 */
+ 0x0, /* 89 */
+ 0x0, /* 90 */
+ 0x0, /* 91 */
+ 0x0, /* 92 */
+ 0x0, /* 93 */
+ 0x0, /* 94 */
+ 0x0, /* 95 */
+ 0x0, /* 96 */
+ 0x0, /* 97 */
+ 0x0, /* 98 */
+ 0x0, /* 99 */
+ 0x0, /* 100 */
+ 0x0, /* 101 */
+ 0x0, /* 102 */
+ 0x0, /* 103 */
+ 0x0, /* 104 */
+ 0x0, /* 105 */
+ 0x0, /* 106 */
+ 0x0, /* 107 */
+ 0x0, /* 108 */
+ 0x0, /* 109 */
+ 0x0, /* 110 */
+ 0x0, /* 111 */
+ 0x0, /* 112 */
+ 0x0, /* 113 */
+ 0x0, /* 114 */
+ 0x0, /* 115 */
+ 0x0, /* 116 */
+ 0x0, /* 117 */
+ 0x0, /* 118 */
+ 0x0, /* 119 */
+ 0x0, /* 120 */
+ 0x0, /* 121 */
+ 0x0, /* 122 */
+ 0x0, /* 123 */
+ 0x0, /* 124 */
+ 0x0, /* 125 */
+ 0x0, /* 126 */
+ 0x0, /* 127 */
+ }
+};
diff --git a/src/libvterm/src/encoding/uk.tbl b/src/libvterm/src/encoding/uk.tbl
new file mode 100644
index 0000000..b27b1a2
--- /dev/null
+++ b/src/libvterm/src/encoding/uk.tbl
@@ -0,0 +1 @@
+2/3 = "£"
diff --git a/src/libvterm/src/keyboard.c b/src/libvterm/src/keyboard.c
new file mode 100644
index 0000000..8cc781f
--- /dev/null
+++ b/src/libvterm/src/keyboard.c
@@ -0,0 +1,229 @@
+#include "vterm_internal.h"
+
+#include <stdio.h>
+
+#include "utf8.h"
+
+void vterm_keyboard_unichar(VTerm *vt, uint32_t c, VTermModifier mod)
+{
+ int needs_CSIu;
+
+ /* The shift modifier is never important for Unicode characters
+ * apart from Space
+ */
+ if(c != ' ')
+ mod &= ~VTERM_MOD_SHIFT;
+
+ if(mod == 0) {
+ // Normal text - ignore just shift
+ char str[6];
+ int seqlen = fill_utf8(c, str);
+ vterm_push_output_bytes(vt, str, seqlen);
+ return;
+ }
+
+ switch(c) {
+ /* Special Ctrl- letters that can't be represented elsewise */
+ case 'i': case 'j': case 'm': case '[':
+ needs_CSIu = 1;
+ break;
+ /* Ctrl-\ ] ^ _ don't need CSUu */
+ case '\\': case ']': case '^': case '_':
+ needs_CSIu = 0;
+ break;
+ /* Shift-space needs CSIu */
+ case ' ':
+ needs_CSIu = !!(mod & VTERM_MOD_SHIFT);
+ break;
+ /* All other characters needs CSIu except for letters a-z */
+ default:
+ needs_CSIu = (c < 'a' || c > 'z');
+ }
+
+ /* ALT we can just prefix with ESC; anything else requires CSI u */
+ if(needs_CSIu && (mod & ~VTERM_MOD_ALT)) {
+ vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d;%du", c, mod+1);
+ return;
+ }
+
+ if(mod & VTERM_MOD_CTRL)
+ c &= 0x1f;
+
+ vterm_push_output_sprintf(vt, "%s%c", mod & VTERM_MOD_ALT ? ESC_S : "", c);
+}
+
+typedef struct {
+ enum {
+ KEYCODE_NONE,
+ KEYCODE_LITERAL,
+ KEYCODE_TAB,
+ KEYCODE_ENTER,
+ KEYCODE_SS3,
+ KEYCODE_CSI,
+ KEYCODE_CSI_CURSOR,
+ KEYCODE_CSINUM,
+ KEYCODE_KEYPAD,
+ } type;
+ char literal;
+ int csinum;
+} keycodes_s;
+
+/* Order here must be exactly the same as VTermKey enum! */
+static keycodes_s keycodes[] = {
+ { KEYCODE_NONE, 0, 0 }, // NONE
+
+ { KEYCODE_ENTER, '\r', 0 }, // ENTER
+ { KEYCODE_TAB, '\t', 0 }, // TAB
+ { KEYCODE_LITERAL, '\x7f', 0 }, // BACKSPACE == ASCII DEL
+ { KEYCODE_LITERAL, '\x1b', 0 }, // ESCAPE
+
+ { KEYCODE_CSI_CURSOR, 'A', 0 }, // UP
+ { KEYCODE_CSI_CURSOR, 'B', 0 }, // DOWN
+ { KEYCODE_CSI_CURSOR, 'D', 0 }, // LEFT
+ { KEYCODE_CSI_CURSOR, 'C', 0 }, // RIGHT
+
+ { KEYCODE_CSINUM, '~', 2 }, // INS
+ { KEYCODE_CSINUM, '~', 3 }, // DEL
+ { KEYCODE_CSI_CURSOR, 'H', 0 }, // HOME
+ { KEYCODE_CSI_CURSOR, 'F', 0 }, // END
+ { KEYCODE_CSINUM, '~', 5 }, // PAGEUP
+ { KEYCODE_CSINUM, '~', 6 }, // PAGEDOWN
+};
+
+static keycodes_s keycodes_fn[] = {
+ { KEYCODE_NONE, 0, 0 }, // F0 - shouldn't happen
+ { KEYCODE_CSI_CURSOR, 'P', 0 }, // F1
+ { KEYCODE_CSI_CURSOR, 'Q', 0 }, // F2
+ { KEYCODE_CSI_CURSOR, 'R', 0 }, // F3
+ { KEYCODE_CSI_CURSOR, 'S', 0 }, // F4
+ { KEYCODE_CSINUM, '~', 15 }, // F5
+ { KEYCODE_CSINUM, '~', 17 }, // F6
+ { KEYCODE_CSINUM, '~', 18 }, // F7
+ { KEYCODE_CSINUM, '~', 19 }, // F8
+ { KEYCODE_CSINUM, '~', 20 }, // F9
+ { KEYCODE_CSINUM, '~', 21 }, // F10
+ { KEYCODE_CSINUM, '~', 23 }, // F11
+ { KEYCODE_CSINUM, '~', 24 }, // F12
+};
+
+static keycodes_s keycodes_kp[] = {
+ { KEYCODE_KEYPAD, '0', 'p' }, // KP_0
+ { KEYCODE_KEYPAD, '1', 'q' }, // KP_1
+ { KEYCODE_KEYPAD, '2', 'r' }, // KP_2
+ { KEYCODE_KEYPAD, '3', 's' }, // KP_3
+ { KEYCODE_KEYPAD, '4', 't' }, // KP_4
+ { KEYCODE_KEYPAD, '5', 'u' }, // KP_5
+ { KEYCODE_KEYPAD, '6', 'v' }, // KP_6
+ { KEYCODE_KEYPAD, '7', 'w' }, // KP_7
+ { KEYCODE_KEYPAD, '8', 'x' }, // KP_8
+ { KEYCODE_KEYPAD, '9', 'y' }, // KP_9
+ { KEYCODE_KEYPAD, '*', 'j' }, // KP_MULT
+ { KEYCODE_KEYPAD, '+', 'k' }, // KP_PLUS
+ { KEYCODE_KEYPAD, ',', 'l' }, // KP_COMMA
+ { KEYCODE_KEYPAD, '-', 'm' }, // KP_MINUS
+ { KEYCODE_KEYPAD, '.', 'n' }, // KP_PERIOD
+ { KEYCODE_KEYPAD, '/', 'o' }, // KP_DIVIDE
+ { KEYCODE_KEYPAD, '\n', 'M' }, // KP_ENTER
+ { KEYCODE_KEYPAD, '=', 'X' }, // KP_EQUAL
+};
+
+void vterm_keyboard_key(VTerm *vt, VTermKey key, VTermModifier mod)
+{
+ keycodes_s k;
+
+ if(key == VTERM_KEY_NONE)
+ return;
+
+ if(key < VTERM_KEY_FUNCTION_0) {
+ if(key >= sizeof(keycodes)/sizeof(keycodes[0]))
+ return;
+ k = keycodes[key];
+ }
+ else if(key >= VTERM_KEY_FUNCTION_0 && key <= VTERM_KEY_FUNCTION_MAX) {
+ if((key - VTERM_KEY_FUNCTION_0) >= sizeof(keycodes_fn)/sizeof(keycodes_fn[0]))
+ return;
+ k = keycodes_fn[key - VTERM_KEY_FUNCTION_0];
+ }
+ else if(key >= VTERM_KEY_KP_0) {
+ if((key - VTERM_KEY_KP_0) >= sizeof(keycodes_kp)/sizeof(keycodes_kp[0]))
+ return;
+ k = keycodes_kp[key - VTERM_KEY_KP_0];
+ }
+
+ switch(k.type) {
+ case KEYCODE_NONE:
+ break;
+
+ case KEYCODE_TAB:
+ /* Shift-Tab is CSI Z but plain Tab is 0x09 */
+ if(mod == VTERM_MOD_SHIFT)
+ vterm_push_output_sprintf_ctrl(vt, C1_CSI, "Z");
+ else if(mod & VTERM_MOD_SHIFT)
+ vterm_push_output_sprintf_ctrl(vt, C1_CSI, "1;%dZ", mod+1);
+ else
+ goto case_LITERAL;
+ break;
+
+ case KEYCODE_ENTER:
+ /* Enter is CRLF in newline mode, but just LF in linefeed */
+ if(vt->state->mode.newline)
+ vterm_push_output_sprintf(vt, "\r\n");
+ else
+ goto case_LITERAL;
+ break;
+
+ case KEYCODE_LITERAL: case_LITERAL:
+ if(mod & (VTERM_MOD_SHIFT|VTERM_MOD_CTRL))
+ vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d;%du", k.literal, mod+1);
+ else
+ vterm_push_output_sprintf(vt, mod & VTERM_MOD_ALT ? ESC_S "%c" : "%c", k.literal);
+ break;
+
+ case KEYCODE_SS3: case_SS3:
+ if(mod == 0)
+ vterm_push_output_sprintf_ctrl(vt, C1_SS3, "%c", k.literal);
+ else
+ goto case_CSI;
+ break;
+
+ case KEYCODE_CSI: case_CSI:
+ if(mod == 0)
+ vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%c", k.literal);
+ else
+ vterm_push_output_sprintf_ctrl(vt, C1_CSI, "1;%d%c", mod + 1, k.literal);
+ break;
+
+ case KEYCODE_CSINUM:
+ if(mod == 0)
+ vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d%c", k.csinum, k.literal);
+ else
+ vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d;%d%c", k.csinum, mod + 1, k.literal);
+ break;
+
+ case KEYCODE_CSI_CURSOR:
+ if(vt->state->mode.cursor)
+ goto case_SS3;
+ else
+ goto case_CSI;
+
+ case KEYCODE_KEYPAD:
+ if(vt->state->mode.keypad) {
+ k.literal = k.csinum;
+ goto case_SS3;
+ }
+ else
+ goto case_LITERAL;
+ }
+}
+
+void vterm_keyboard_start_paste(VTerm *vt)
+{
+ if(vt->state->mode.bracketpaste)
+ vterm_push_output_sprintf_ctrl(vt, C1_CSI, "200~");
+}
+
+void vterm_keyboard_end_paste(VTerm *vt)
+{
+ if(vt->state->mode.bracketpaste)
+ vterm_push_output_sprintf_ctrl(vt, C1_CSI, "201~");
+}
diff --git a/src/libvterm/src/mouse.c b/src/libvterm/src/mouse.c
new file mode 100644
index 0000000..4e36313
--- /dev/null
+++ b/src/libvterm/src/mouse.c
@@ -0,0 +1,98 @@
+#include "vterm_internal.h"
+
+#include "utf8.h"
+
+static void output_mouse(VTermState *state, int code, int pressed, int modifiers, int col, int row)
+{
+ modifiers <<= 2;
+
+ switch(state->mouse_protocol) {
+ case MOUSE_X10:
+ if(col + 0x21 > 0xff)
+ col = 0xff - 0x21;
+ if(row + 0x21 > 0xff)
+ row = 0xff - 0x21;
+
+ if(!pressed)
+ code = 3;
+
+ vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "M%c%c%c",
+ (code | modifiers) + 0x20, col + 0x21, row + 0x21);
+ break;
+
+ case MOUSE_UTF8:
+ {
+ char utf8[18]; size_t len = 0;
+
+ if(!pressed)
+ code = 3;
+
+ len += fill_utf8((code | modifiers) + 0x20, utf8 + len);
+ len += fill_utf8(col + 0x21, utf8 + len);
+ len += fill_utf8(row + 0x21, utf8 + len);
+ utf8[len] = 0;
+
+ vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "M%s", utf8);
+ }
+ break;
+
+ case MOUSE_SGR:
+ vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "<%d;%d;%d%c",
+ code | modifiers, col + 1, row + 1, pressed ? 'M' : 'm');
+ break;
+
+ case MOUSE_RXVT:
+ if(!pressed)
+ code = 3;
+
+ vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "%d;%d;%dM",
+ code | modifiers, col + 1, row + 1);
+ break;
+ }
+}
+
+void vterm_mouse_move(VTerm *vt, int row, int col, VTermModifier mod)
+{
+ VTermState *state = vt->state;
+
+ if(col == state->mouse_col && row == state->mouse_row)
+ return;
+
+ state->mouse_col = col;
+ state->mouse_row = row;
+
+ if((state->mouse_flags & MOUSE_WANT_DRAG && state->mouse_buttons) ||
+ (state->mouse_flags & MOUSE_WANT_MOVE)) {
+ int button = state->mouse_buttons & MOUSE_BUTTON_LEFT ? 1 :
+ state->mouse_buttons & MOUSE_BUTTON_MIDDLE ? 2 :
+ state->mouse_buttons & MOUSE_BUTTON_RIGHT ? 3 : 4;
+ output_mouse(state, button-1 + 0x20, 1, mod, col, row);
+ }
+}
+
+void vterm_mouse_button(VTerm *vt, int button, int pressed, VTermModifier mod)
+{
+ VTermState *state = vt->state;
+
+ int old_buttons = state->mouse_buttons;
+
+ if(button > 0 && button <= 3) {
+ if(pressed)
+ state->mouse_buttons |= (1 << (button-1));
+ else
+ state->mouse_buttons &= ~(1 << (button-1));
+ }
+
+ /* Most of the time we don't get button releases from 4/5 */
+ if(state->mouse_buttons == old_buttons && button < 4)
+ return;
+ if (!(state->mouse_flags & MOUSE_WANT_CLICK))
+ return;
+
+ if(button < 4) {
+ output_mouse(state, button-1, pressed, mod, state->mouse_col, state->mouse_row);
+ }
+ else if(button < 6) {
+ output_mouse(state, button-4 + 0x40, pressed, mod, state->mouse_col, state->mouse_row);
+ }
+}
diff --git a/src/libvterm/src/parser.c b/src/libvterm/src/parser.c
new file mode 100644
index 0000000..db45692
--- /dev/null
+++ b/src/libvterm/src/parser.c
@@ -0,0 +1,346 @@
+#include "vterm_internal.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#undef DEBUG_PARSER
+
+static int is_intermed(unsigned char c)
+{
+ return c >= 0x20 && c <= 0x2f;
+}
+
+static void do_control(VTerm *vt, unsigned char control)
+{
+ if(vt->parser.callbacks && vt->parser.callbacks->control)
+ if((*vt->parser.callbacks->control)(control, vt->parser.cbdata))
+ return;
+
+ DEBUG_LOG1("libvterm: Unhandled control 0x%02x\n", control);
+}
+
+static void do_csi(VTerm *vt, char command)
+{
+#ifdef DEBUG_PARSER
+ printf("Parsed CSI args as:\n", arglen, args);
+ printf(" leader: %s\n", vt->parser.csi_leader);
+ for(int argi = 0; argi < vt->parser.csi_argi; argi++) {
+ printf(" %lu", CSI_ARG(vt->parser.csi_args[argi]));
+ if(!CSI_ARG_HAS_MORE(vt->parser.csi_args[argi]))
+ printf("\n");
+ printf(" intermed: %s\n", vt->parser.intermed);
+ }
+#endif
+
+ if(vt->parser.callbacks && vt->parser.callbacks->csi)
+ if((*vt->parser.callbacks->csi)(
+ vt->parser.csi_leaderlen ? vt->parser.csi_leader : NULL,
+ vt->parser.csi_args,
+ vt->parser.csi_argi,
+ vt->parser.intermedlen ? vt->parser.intermed : NULL,
+ command,
+ vt->parser.cbdata))
+ return;
+
+ DEBUG_LOG1("libvterm: Unhandled CSI %c\n", command);
+}
+
+static void do_escape(VTerm *vt, char command)
+{
+ char seq[INTERMED_MAX+1];
+
+ size_t len = vt->parser.intermedlen;
+ strncpy(seq, vt->parser.intermed, len);
+ seq[len++] = command;
+ seq[len] = 0;
+
+ if(vt->parser.callbacks && vt->parser.callbacks->escape)
+ if((*vt->parser.callbacks->escape)(seq, len, vt->parser.cbdata))
+ return;
+
+ DEBUG_LOG1("libvterm: Unhandled escape ESC 0x%02x\n", command);
+}
+
+static void append_strbuffer(VTerm *vt, const char *str, size_t len)
+{
+ if(len > vt->parser.strbuffer_len - vt->parser.strbuffer_cur) {
+ len = vt->parser.strbuffer_len - vt->parser.strbuffer_cur;
+ DEBUG_LOG1("Truncating strbuffer preserve to %zd bytes\n", len);
+ }
+
+ if(len > 0) {
+ strncpy(vt->parser.strbuffer + vt->parser.strbuffer_cur, str, len);
+ vt->parser.strbuffer_cur += len;
+ }
+}
+
+static void start_string(VTerm *vt, VTermParserStringType type)
+{
+ vt->parser.stringtype = type;
+
+ vt->parser.strbuffer_cur = 0;
+}
+
+static void more_string(VTerm *vt, const char *str, size_t len)
+{
+ append_strbuffer(vt, str, len);
+}
+
+static void done_string(VTerm *vt, const char *str, size_t len)
+{
+ if(vt->parser.strbuffer_cur) {
+ if(str)
+ append_strbuffer(vt, str, len);
+
+ str = vt->parser.strbuffer;
+ len = vt->parser.strbuffer_cur;
+ }
+ else if(!str) {
+ DEBUG_LOG("parser.c: TODO: No strbuffer _and_ no final fragment???\n");
+ len = 0;
+ }
+
+ switch(vt->parser.stringtype) {
+ case VTERM_PARSER_OSC:
+ if(vt->parser.callbacks && vt->parser.callbacks->osc)
+ if((*vt->parser.callbacks->osc)(str, len, vt->parser.cbdata))
+ return;
+
+ DEBUG_LOG2("libvterm: Unhandled OSC %.*s\n", (int)len, str);
+ return;
+
+ case VTERM_PARSER_DCS:
+ if(vt->parser.callbacks && vt->parser.callbacks->dcs)
+ if((*vt->parser.callbacks->dcs)(str, len, vt->parser.cbdata))
+ return;
+
+ DEBUG_LOG2("libvterm: Unhandled DCS %.*s\n", (int)len, str);
+ return;
+
+ case VTERM_N_PARSER_TYPES:
+ return;
+ }
+}
+
+size_t vterm_input_write(VTerm *vt, const char *bytes, size_t len)
+{
+ size_t pos = 0;
+ const char *string_start = NULL; /* init to avoid gcc warning */
+
+ switch(vt->parser.state) {
+ case NORMAL:
+ case CSI_LEADER:
+ case CSI_ARGS:
+ case CSI_INTERMED:
+ case ESC:
+ string_start = NULL;
+ break;
+ case STRING:
+ case ESC_IN_STRING:
+ string_start = bytes;
+ break;
+ }
+
+#define ENTER_STRING_STATE() do { vt->parser.state = STRING; string_start = bytes + pos + 1; } while(0)
+#define ENTER_STATE(st) do { vt->parser.state = st; string_start = NULL; } while(0)
+#define ENTER_NORMAL_STATE() ENTER_STATE(NORMAL)
+
+ for( ; pos < len; pos++) {
+ unsigned char c = bytes[pos];
+
+ if(c == 0x00 || c == 0x7f) { // NUL, DEL
+ if(vt->parser.state >= STRING) {
+ more_string(vt, string_start, bytes + pos - string_start);
+ string_start = bytes + pos + 1;
+ }
+ continue;
+ }
+ if(c == 0x18 || c == 0x1a) { // CAN, SUB
+ ENTER_NORMAL_STATE();
+ continue;
+ }
+ else if(c == 0x1b) { // ESC
+ vt->parser.intermedlen = 0;
+ if(vt->parser.state == STRING)
+ vt->parser.state = ESC_IN_STRING;
+ else
+ ENTER_STATE(ESC);
+ continue;
+ }
+ else if(c == 0x07 && // BEL, can stand for ST in OSC or DCS state
+ vt->parser.state == STRING) {
+ // fallthrough
+ }
+ else if(c < 0x20) { // other C0
+ if(vt->parser.state >= STRING)
+ more_string(vt, string_start, bytes + pos - string_start);
+ do_control(vt, c);
+ if(vt->parser.state >= STRING)
+ string_start = bytes + pos + 1;
+ continue;
+ }
+ // else fallthrough
+
+ switch(vt->parser.state) {
+ case ESC_IN_STRING:
+ if(c == 0x5c) { // ST
+ vt->parser.state = STRING;
+ done_string(vt, string_start, bytes + pos - string_start - 1);
+ ENTER_NORMAL_STATE();
+ break;
+ }
+ vt->parser.state = ESC;
+ // else fallthrough
+
+ case ESC:
+ switch(c) {
+ case 0x50: // DCS
+ start_string(vt, VTERM_PARSER_DCS);
+ ENTER_STRING_STATE();
+ break;
+ case 0x5b: // CSI
+ vt->parser.csi_leaderlen = 0;
+ ENTER_STATE(CSI_LEADER);
+ break;
+ case 0x5d: // OSC
+ start_string(vt, VTERM_PARSER_OSC);
+ ENTER_STRING_STATE();
+ break;
+ default:
+ if(is_intermed(c)) {
+ if(vt->parser.intermedlen < INTERMED_MAX-1)
+ vt->parser.intermed[vt->parser.intermedlen++] = c;
+ }
+ else if(!vt->parser.intermedlen && c >= 0x40 && c < 0x60) {
+ do_control(vt, c + 0x40);
+ ENTER_NORMAL_STATE();
+ }
+ else if(c >= 0x30 && c < 0x7f) {
+ do_escape(vt, c);
+ ENTER_NORMAL_STATE();
+ }
+ else {
+ DEBUG_LOG1("TODO: Unhandled byte %02x in Escape\n", c);
+ }
+ }
+ break;
+
+ case CSI_LEADER:
+ /* Extract leader bytes 0x3c to 0x3f */
+ if(c >= 0x3c && c <= 0x3f) {
+ if(vt->parser.csi_leaderlen < CSI_LEADER_MAX-1)
+ vt->parser.csi_leader[vt->parser.csi_leaderlen++] = c;
+ break;
+ }
+
+ /* else fallthrough */
+ vt->parser.csi_leader[vt->parser.csi_leaderlen] = 0;
+
+ vt->parser.csi_argi = 0;
+ vt->parser.csi_args[0] = CSI_ARG_MISSING;
+ vt->parser.state = CSI_ARGS;
+
+ /* fallthrough */
+ case CSI_ARGS:
+ /* Numerical value of argument */
+ if(c >= '0' && c <= '9') {
+ if(vt->parser.csi_args[vt->parser.csi_argi] == CSI_ARG_MISSING)
+ vt->parser.csi_args[vt->parser.csi_argi] = 0;
+ vt->parser.csi_args[vt->parser.csi_argi] *= 10;
+ vt->parser.csi_args[vt->parser.csi_argi] += c - '0';
+ break;
+ }
+ if(c == ':') {
+ vt->parser.csi_args[vt->parser.csi_argi] |= CSI_ARG_FLAG_MORE;
+ c = ';';
+ }
+ if(c == ';') {
+ vt->parser.csi_argi++;
+ vt->parser.csi_args[vt->parser.csi_argi] = CSI_ARG_MISSING;
+ break;
+ }
+
+ /* else fallthrough */
+ vt->parser.csi_argi++;
+ vt->parser.intermedlen = 0;
+ vt->parser.state = CSI_INTERMED;
+ /* fallthrough */
+ case CSI_INTERMED:
+ if(is_intermed(c)) {
+ if(vt->parser.intermedlen < INTERMED_MAX-1)
+ vt->parser.intermed[vt->parser.intermedlen++] = c;
+ break;
+ }
+ else if(c == 0x1b) {
+ /* ESC in CSI cancels */
+ }
+ else if(c >= 0x40 && c <= 0x7e) {
+ vt->parser.intermed[vt->parser.intermedlen] = 0;
+ do_csi(vt, c);
+ }
+ /* else was invalid CSI */
+
+ ENTER_NORMAL_STATE();
+ break;
+
+ case STRING:
+ if(c == 0x07 || (c == 0x9c && !vt->mode.utf8)) {
+ done_string(vt, string_start, bytes + pos - string_start);
+ ENTER_NORMAL_STATE();
+ }
+ else if (pos + 1 == len) {
+ /* end of input but OSC string isn't finished yet, copy it to
+ * vt->parser.strbuffer to continue it later */
+ more_string(vt, string_start, bytes + pos + 1 - string_start);
+ }
+ break;
+
+ case NORMAL:
+ if(c >= 0x80 && c < 0xa0 && !vt->mode.utf8) {
+ switch(c) {
+ case 0x90: // DCS
+ start_string(vt, VTERM_PARSER_DCS);
+ ENTER_STRING_STATE();
+ break;
+ case 0x9b: // CSI
+ ENTER_STATE(CSI_LEADER);
+ break;
+ case 0x9d: // OSC
+ start_string(vt, VTERM_PARSER_OSC);
+ ENTER_STRING_STATE();
+ break;
+ default:
+ do_control(vt, c);
+ break;
+ }
+ }
+ else {
+ size_t eaten = 0;
+ if(vt->parser.callbacks && vt->parser.callbacks->text)
+ eaten = (*vt->parser.callbacks->text)(bytes + pos, len - pos, vt->parser.cbdata);
+
+ if(!eaten) {
+ DEBUG_LOG("libvterm: Text callback did not consume any input\n");
+ /* force it to make progress */
+ eaten = 1;
+ }
+
+ pos += (eaten - 1); // we'll ++ it again in a moment
+ }
+ break;
+ }
+ }
+
+ return len;
+}
+
+void vterm_parser_set_callbacks(VTerm *vt, const VTermParserCallbacks *callbacks, void *user)
+{
+ vt->parser.callbacks = callbacks;
+ vt->parser.cbdata = user;
+}
+
+void *vterm_parser_get_cbdata(VTerm *vt)
+{
+ return vt->parser.cbdata;
+}
diff --git a/src/libvterm/src/pen.c b/src/libvterm/src/pen.c
new file mode 100644
index 0000000..b780095
--- /dev/null
+++ b/src/libvterm/src/pen.c
@@ -0,0 +1,516 @@
+#include "vterm_internal.h"
+
+#include <stdio.h>
+
+static const VTermColor ansi_colors[] = {
+ /* R G B index */
+ { 0, 0, 0, 1 }, // black
+ { 224, 0, 0, 2 }, // red
+ { 0, 224, 0, 3 }, // green
+ { 224, 224, 0, 4 }, // yellow
+ { 0, 0, 224, 5 }, // blue
+ { 224, 0, 224, 6 }, // magenta
+ { 0, 224, 224, 7 }, // cyan
+ { 224, 224, 224, 8 }, // white == light grey
+
+ // high intensity
+ { 128, 128, 128, 9 }, // black
+ { 255, 64, 64, 10 }, // red
+ { 64, 255, 64, 11 }, // green
+ { 255, 255, 64, 12 }, // yellow
+ { 64, 64, 255, 13 }, // blue
+ { 255, 64, 255, 14 }, // magenta
+ { 64, 255, 255, 15 }, // cyan
+ { 255, 255, 255, 16 }, // white for real
+};
+
+static int ramp6[] = {
+ 0x00, 0x5F, 0x87, 0xAF, 0xD7, 0xFF,
+};
+
+/* Use 0x81 instead of 0x80 to be able to distinguish from ansi black */
+static int ramp24[] = {
+ 0x08, 0x12, 0x1C, 0x26, 0x30, 0x3A, 0x44, 0x4E, 0x58, 0x62, 0x6C, 0x76,
+ 0x81, 0x8A, 0x94, 0x9E, 0xA8, 0xB2, 0xBC, 0xC6, 0xD0, 0xDA, 0xE4, 0xEE,
+};
+
+static int lookup_colour_ansi(const VTermState *state, long index, VTermColor *col)
+{
+ if(index >= 0 && index < 16) {
+ *col = state->colors[index];
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static int lookup_colour_palette(const VTermState *state, long index, VTermColor *col)
+{
+ if(index >= 0 && index < 16) {
+ // Normal 8 colours or high intensity - parse as palette 0
+ return lookup_colour_ansi(state, index, col);
+ }
+ else if(index >= 16 && index < 232) {
+ // 216-colour cube
+ index -= 16;
+
+ col->blue = ramp6[index % 6];
+ col->green = ramp6[index/6 % 6];
+ col->red = ramp6[index/6/6 % 6];
+ col->ansi_index = VTERM_ANSI_INDEX_NONE;
+
+ return TRUE;
+ }
+ else if(index >= 232 && index < 256) {
+ // 24 greyscales
+ index -= 232;
+
+ col->blue = ramp24[index];
+ col->green = ramp24[index];
+ col->red = ramp24[index];
+ col->ansi_index = VTERM_ANSI_INDEX_NONE;
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static int lookup_colour(const VTermState *state, int palette, const long args[], int argcount, VTermColor *col, int *index)
+{
+ switch(palette) {
+ case 2: // RGB mode - 3 args contain colour values directly
+ if(argcount < 3)
+ return argcount;
+
+ col->red = (uint8_t)CSI_ARG(args[0]);
+ col->green = (uint8_t)CSI_ARG(args[1]);
+ col->blue = (uint8_t)CSI_ARG(args[2]);
+ col->ansi_index = VTERM_ANSI_INDEX_NONE;
+
+ return 3;
+
+ case 5: // XTerm 256-colour mode
+ if(index)
+ *index = CSI_ARG_OR(args[0], -1);
+
+ lookup_colour_palette(state, argcount ? CSI_ARG_OR(args[0], -1) : -1, col);
+
+ return argcount ? 1 : 0;
+
+ default:
+ DEBUG_LOG1("Unrecognised colour palette %d\n", palette);
+ return 0;
+ }
+}
+
+// Some conveniences
+
+static void setpenattr(VTermState *state, VTermAttr attr, VTermValueType type UNUSED, VTermValue *val)
+{
+#ifdef DEBUG
+ if(type != vterm_get_attr_type(attr)) {
+ DEBUG_LOG3("Cannot set attr %d as it has type %d, not type %d\n",
+ attr, vterm_get_attr_type(attr), type);
+ return;
+ }
+#endif
+ if(state->callbacks && state->callbacks->setpenattr)
+ (*state->callbacks->setpenattr)(attr, val, state->cbdata);
+}
+
+static void setpenattr_bool(VTermState *state, VTermAttr attr, int boolean)
+{
+ VTermValue val;
+ val.boolean = boolean;
+ setpenattr(state, attr, VTERM_VALUETYPE_BOOL, &val);
+}
+
+static void setpenattr_int(VTermState *state, VTermAttr attr, int number)
+{
+ VTermValue val;
+ val.number = number;
+ setpenattr(state, attr, VTERM_VALUETYPE_INT, &val);
+}
+
+static void setpenattr_col(VTermState *state, VTermAttr attr, VTermColor color)
+{
+ VTermValue val;
+ val.color = color;
+ setpenattr(state, attr, VTERM_VALUETYPE_COLOR, &val);
+}
+
+static void set_pen_col_ansi(VTermState *state, VTermAttr attr, long col)
+{
+ VTermColor *colp = (attr == VTERM_ATTR_BACKGROUND) ? &state->pen.bg : &state->pen.fg;
+
+ lookup_colour_ansi(state, col, colp);
+
+ setpenattr_col(state, attr, *colp);
+}
+
+INTERNAL void vterm_state_newpen(VTermState *state)
+{
+ int col;
+
+ // 90% grey so that pure white is brighter
+ state->default_fg.red = state->default_fg.green = state->default_fg.blue = 240;
+ state->default_fg.ansi_index = VTERM_ANSI_INDEX_DEFAULT;
+ state->default_bg.red = state->default_bg.green = state->default_bg.blue = 0;
+ state->default_bg.ansi_index = VTERM_ANSI_INDEX_DEFAULT;
+
+ for(col = 0; col < 16; col++)
+ state->colors[col] = ansi_colors[col];
+}
+
+INTERNAL void vterm_state_resetpen(VTermState *state)
+{
+ state->pen.bold = 0; setpenattr_bool(state, VTERM_ATTR_BOLD, 0);
+ state->pen.underline = 0; setpenattr_int( state, VTERM_ATTR_UNDERLINE, 0);
+ state->pen.italic = 0; setpenattr_bool(state, VTERM_ATTR_ITALIC, 0);
+ state->pen.blink = 0; setpenattr_bool(state, VTERM_ATTR_BLINK, 0);
+ state->pen.reverse = 0; setpenattr_bool(state, VTERM_ATTR_REVERSE, 0);
+ state->pen.strike = 0; setpenattr_bool(state, VTERM_ATTR_STRIKE, 0);
+ state->pen.font = 0; setpenattr_int( state, VTERM_ATTR_FONT, 0);
+
+ state->fg_index = -1;
+ state->bg_index = -1;
+ state->pen.fg = state->default_fg; setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->default_fg);
+ state->pen.bg = state->default_bg; setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->default_bg);
+}
+
+INTERNAL void vterm_state_savepen(VTermState *state, int save)
+{
+ if(save) {
+ state->saved.pen = state->pen;
+ }
+ else {
+ state->pen = state->saved.pen;
+
+ setpenattr_bool(state, VTERM_ATTR_BOLD, state->pen.bold);
+ setpenattr_int( state, VTERM_ATTR_UNDERLINE, state->pen.underline);
+ setpenattr_bool(state, VTERM_ATTR_ITALIC, state->pen.italic);
+ setpenattr_bool(state, VTERM_ATTR_BLINK, state->pen.blink);
+ setpenattr_bool(state, VTERM_ATTR_REVERSE, state->pen.reverse);
+ setpenattr_bool(state, VTERM_ATTR_STRIKE, state->pen.strike);
+ setpenattr_int( state, VTERM_ATTR_FONT, state->pen.font);
+ setpenattr_col( state, VTERM_ATTR_FOREGROUND, state->pen.fg);
+ setpenattr_col( state, VTERM_ATTR_BACKGROUND, state->pen.bg);
+ }
+}
+
+void vterm_state_get_default_colors(const VTermState *state, VTermColor *default_fg, VTermColor *default_bg)
+{
+ *default_fg = state->default_fg;
+ *default_bg = state->default_bg;
+}
+
+void vterm_state_get_palette_color(const VTermState *state, int index, VTermColor *col)
+{
+ lookup_colour_palette(state, index, col);
+}
+
+void vterm_state_set_default_colors(VTermState *state, const VTermColor *default_fg, const VTermColor *default_bg)
+{
+ state->default_fg = *default_fg;
+ state->default_bg = *default_bg;
+}
+
+void vterm_state_set_palette_color(VTermState *state, int index, const VTermColor *col)
+{
+ if(index >= 0 && index < 16)
+ {
+ state->colors[index] = *col;
+ state->colors[index].ansi_index = index + VTERM_ANSI_INDEX_MIN;
+ }
+}
+
+void vterm_state_set_bold_highbright(VTermState *state, int bold_is_highbright)
+{
+ state->bold_is_highbright = bold_is_highbright;
+}
+
+INTERNAL void vterm_state_setpen(VTermState *state, const long args[], int argcount)
+{
+ // SGR - ECMA-48 8.3.117
+
+ int argi = 0;
+ int value;
+
+ while(argi < argcount) {
+ // This logic is easier to do 'done' backwards; set it true, and make it
+ // false again in the 'default' case
+ int done = 1;
+
+ long arg;
+ switch(arg = CSI_ARG(args[argi])) {
+ case CSI_ARG_MISSING:
+ case 0: // Reset
+ vterm_state_resetpen(state);
+ break;
+
+ case 1: // Bold on
+ state->pen.bold = 1;
+ setpenattr_bool(state, VTERM_ATTR_BOLD, 1);
+ if(state->fg_index > -1 && state->fg_index < 8 && state->bold_is_highbright)
+ set_pen_col_ansi(state, VTERM_ATTR_FOREGROUND, state->fg_index + (state->pen.bold ? 8 : 0));
+ break;
+
+ case 3: // Italic on
+ state->pen.italic = 1;
+ setpenattr_bool(state, VTERM_ATTR_ITALIC, 1);
+ break;
+
+ case 4: // Underline single
+ state->pen.underline = 1;
+ setpenattr_int(state, VTERM_ATTR_UNDERLINE, 1);
+ break;
+
+ case 5: // Blink
+ state->pen.blink = 1;
+ setpenattr_bool(state, VTERM_ATTR_BLINK, 1);
+ break;
+
+ case 7: // Reverse on
+ state->pen.reverse = 1;
+ setpenattr_bool(state, VTERM_ATTR_REVERSE, 1);
+ break;
+
+ case 9: // Strikethrough on
+ state->pen.strike = 1;
+ setpenattr_bool(state, VTERM_ATTR_STRIKE, 1);
+ break;
+
+ case 10: case 11: case 12: case 13: case 14:
+ case 15: case 16: case 17: case 18: case 19: // Select font
+ state->pen.font = CSI_ARG(args[argi]) - 10;
+ setpenattr_int(state, VTERM_ATTR_FONT, state->pen.font);
+ break;
+
+ case 21: // Underline double
+ state->pen.underline = 2;
+ setpenattr_int(state, VTERM_ATTR_UNDERLINE, 2);
+ break;
+
+ case 22: // Bold off
+ state->pen.bold = 0;
+ setpenattr_bool(state, VTERM_ATTR_BOLD, 0);
+ break;
+
+ case 23: // Italic and Gothic (currently unsupported) off
+ state->pen.italic = 0;
+ setpenattr_bool(state, VTERM_ATTR_ITALIC, 0);
+ break;
+
+ case 24: // Underline off
+ state->pen.underline = 0;
+ setpenattr_int(state, VTERM_ATTR_UNDERLINE, 0);
+ break;
+
+ case 25: // Blink off
+ state->pen.blink = 0;
+ setpenattr_bool(state, VTERM_ATTR_BLINK, 0);
+ break;
+
+ case 27: // Reverse off
+ state->pen.reverse = 0;
+ setpenattr_bool(state, VTERM_ATTR_REVERSE, 0);
+ break;
+
+ case 29: // Strikethrough off
+ state->pen.strike = 0;
+ setpenattr_bool(state, VTERM_ATTR_STRIKE, 0);
+ break;
+
+ case 30: case 31: case 32: case 33:
+ case 34: case 35: case 36: case 37: // Foreground colour palette
+ value = CSI_ARG(args[argi]) - 30;
+ state->fg_index = value;
+ if(state->pen.bold && state->bold_is_highbright)
+ value += 8;
+ set_pen_col_ansi(state, VTERM_ATTR_FOREGROUND, value);
+ break;
+
+ case 38: // Foreground colour alternative palette
+ state->fg_index = -1;
+ if(argcount - argi < 1)
+ return;
+ argi += 1 + lookup_colour(state, CSI_ARG(args[argi+1]), args+argi+2, argcount-argi-2, &state->pen.fg, &state->fg_index);
+ setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->pen.fg);
+ break;
+
+ case 39: // Foreground colour default
+ state->fg_index = -1;
+ state->pen.fg = state->default_fg;
+ setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->pen.fg);
+ break;
+
+ case 40: case 41: case 42: case 43:
+ case 44: case 45: case 46: case 47: // Background colour palette
+ value = CSI_ARG(args[argi]) - 40;
+ state->bg_index = value;
+ set_pen_col_ansi(state, VTERM_ATTR_BACKGROUND, value);
+ break;
+
+ case 48: // Background colour alternative palette
+ state->bg_index = -1;
+ if(argcount - argi < 1)
+ return;
+ argi += 1 + lookup_colour(state, CSI_ARG(args[argi+1]), args+argi+2, argcount-argi-2, &state->pen.bg, &state->bg_index);
+ setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->pen.bg);
+ break;
+
+ case 49: // Default background
+ state->bg_index = -1;
+ state->pen.bg = state->default_bg;
+ setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->pen.bg);
+ break;
+
+ case 90: case 91: case 92: case 93:
+ case 94: case 95: case 96: case 97: // Foreground colour high-intensity palette
+ value = CSI_ARG(args[argi]) - 90 + 8;
+ state->fg_index = value;
+ set_pen_col_ansi(state, VTERM_ATTR_FOREGROUND, value);
+ break;
+
+ case 100: case 101: case 102: case 103:
+ case 104: case 105: case 106: case 107: // Background colour high-intensity palette
+ value = CSI_ARG(args[argi]) - 100 + 8;
+ state->bg_index = value;
+ set_pen_col_ansi(state, VTERM_ATTR_BACKGROUND, value);
+ break;
+
+ default:
+ done = 0;
+ break;
+ }
+
+ if(!done)
+ {
+ DEBUG_LOG1("libvterm: Unhandled CSI SGR %lu\n", arg);
+ }
+
+ while(CSI_ARG_HAS_MORE(args[argi++]));
+ }
+}
+
+INTERNAL int vterm_state_getpen(VTermState *state, long args[], int argcount UNUSED)
+{
+ int argi = 0;
+
+ if(state->pen.bold)
+ args[argi++] = 1;
+
+ if(state->pen.italic)
+ args[argi++] = 3;
+
+ if(state->pen.underline == 1)
+ args[argi++] = 4;
+
+ if(state->pen.blink)
+ args[argi++] = 5;
+
+ if(state->pen.reverse)
+ args[argi++] = 7;
+
+ if(state->pen.strike)
+ args[argi++] = 9;
+
+ if(state->pen.font)
+ args[argi++] = 10 + state->pen.font;
+
+ if(state->pen.underline == 2)
+ args[argi++] = 21;
+
+ if(state->fg_index >= 0 && state->fg_index < 8)
+ args[argi++] = 30 + state->fg_index;
+ else if(state->fg_index >= 8 && state->fg_index < 16)
+ args[argi++] = 90 + state->fg_index - 8;
+ else if(state->fg_index >= 16 && state->fg_index < 256) {
+ args[argi++] = CSI_ARG_FLAG_MORE|38;
+ args[argi++] = CSI_ARG_FLAG_MORE|5;
+ args[argi++] = state->fg_index;
+ }
+ else if(state->fg_index == -1) {
+ // Send palette 2 if the actual FG colour is not default
+ if(state->pen.fg.red != state->default_fg.red ||
+ state->pen.fg.green != state->default_fg.green ||
+ state->pen.fg.blue != state->default_fg.blue ) {
+ args[argi++] = CSI_ARG_FLAG_MORE|38;
+ args[argi++] = CSI_ARG_FLAG_MORE|2;
+ args[argi++] = CSI_ARG_FLAG_MORE | state->pen.fg.red;
+ args[argi++] = CSI_ARG_FLAG_MORE | state->pen.fg.green;
+ args[argi++] = state->pen.fg.blue;
+ }
+ }
+
+ if(state->bg_index >= 0 && state->bg_index < 8)
+ args[argi++] = 40 + state->bg_index;
+ else if(state->bg_index >= 8 && state->bg_index < 16)
+ args[argi++] = 100 + state->bg_index - 8;
+ else if(state->bg_index >= 16 && state->bg_index < 256) {
+ args[argi++] = CSI_ARG_FLAG_MORE|48;
+ args[argi++] = CSI_ARG_FLAG_MORE|5;
+ args[argi++] = state->bg_index;
+ }
+ else if(state->bg_index == -1) {
+ // Send palette 2 if the actual BG colour is not default
+ if(state->pen.bg.red != state->default_bg.red ||
+ state->pen.bg.green != state->default_bg.green ||
+ state->pen.bg.blue != state->default_bg.blue ) {
+ args[argi++] = CSI_ARG_FLAG_MORE|48;
+ args[argi++] = CSI_ARG_FLAG_MORE|2;
+ args[argi++] = CSI_ARG_FLAG_MORE | state->pen.bg.red;
+ args[argi++] = CSI_ARG_FLAG_MORE | state->pen.bg.green;
+ args[argi++] = state->pen.bg.blue;
+ }
+ }
+
+ return argi;
+}
+
+int vterm_state_get_penattr(const VTermState *state, VTermAttr attr, VTermValue *val)
+{
+ switch(attr) {
+ case VTERM_ATTR_BOLD:
+ val->boolean = state->pen.bold;
+ return 1;
+
+ case VTERM_ATTR_UNDERLINE:
+ val->number = state->pen.underline;
+ return 1;
+
+ case VTERM_ATTR_ITALIC:
+ val->boolean = state->pen.italic;
+ return 1;
+
+ case VTERM_ATTR_BLINK:
+ val->boolean = state->pen.blink;
+ return 1;
+
+ case VTERM_ATTR_REVERSE:
+ val->boolean = state->pen.reverse;
+ return 1;
+
+ case VTERM_ATTR_STRIKE:
+ val->boolean = state->pen.strike;
+ return 1;
+
+ case VTERM_ATTR_FONT:
+ val->number = state->pen.font;
+ return 1;
+
+ case VTERM_ATTR_FOREGROUND:
+ val->color = state->pen.fg;
+ return 1;
+
+ case VTERM_ATTR_BACKGROUND:
+ val->color = state->pen.bg;
+ return 1;
+
+ case VTERM_N_ATTRS:
+ return 0;
+ }
+
+ return 0;
+}
diff --git a/src/libvterm/src/rect.h b/src/libvterm/src/rect.h
new file mode 100644
index 0000000..2114f24
--- /dev/null
+++ b/src/libvterm/src/rect.h
@@ -0,0 +1,56 @@
+/*
+ * Some utility functions on VTermRect structures
+ */
+
+#define STRFrect "(%d,%d-%d,%d)"
+#define ARGSrect(r) (r).start_row, (r).start_col, (r).end_row, (r).end_col
+
+/* Expand dst to contain src as well */
+static void rect_expand(VTermRect *dst, VTermRect *src)
+{
+ if(dst->start_row > src->start_row) dst->start_row = src->start_row;
+ if(dst->start_col > src->start_col) dst->start_col = src->start_col;
+ if(dst->end_row < src->end_row) dst->end_row = src->end_row;
+ if(dst->end_col < src->end_col) dst->end_col = src->end_col;
+}
+
+/* Clip the dst to ensure it does not step outside of bounds */
+static void rect_clip(VTermRect *dst, VTermRect *bounds)
+{
+ if(dst->start_row < bounds->start_row) dst->start_row = bounds->start_row;
+ if(dst->start_col < bounds->start_col) dst->start_col = bounds->start_col;
+ if(dst->end_row > bounds->end_row) dst->end_row = bounds->end_row;
+ if(dst->end_col > bounds->end_col) dst->end_col = bounds->end_col;
+ /* Ensure it doesn't end up negatively-sized */
+ if(dst->end_row < dst->start_row) dst->end_row = dst->start_row;
+ if(dst->end_col < dst->start_col) dst->end_col = dst->start_col;
+}
+
+/* True if the two rectangles are equal */
+static int rect_equal(VTermRect *a, VTermRect *b)
+{
+ return (a->start_row == b->start_row) &&
+ (a->start_col == b->start_col) &&
+ (a->end_row == b->end_row) &&
+ (a->end_col == b->end_col);
+}
+
+/* True if small is contained entirely within big */
+static int rect_contains(VTermRect *big, VTermRect *small)
+{
+ if(small->start_row < big->start_row) return 0;
+ if(small->start_col < big->start_col) return 0;
+ if(small->end_row > big->end_row) return 0;
+ if(small->end_col > big->end_col) return 0;
+ return 1;
+}
+
+/* True if the rectangles overlap at all */
+static int rect_intersects(VTermRect *a, VTermRect *b)
+{
+ if(a->start_row > b->end_row || b->start_row > a->end_row)
+ return 0;
+ if(a->start_col > b->end_col || b->start_col > a->end_col)
+ return 0;
+ return 1;
+}
diff --git a/src/libvterm/src/state.c b/src/libvterm/src/state.c
new file mode 100644
index 0000000..8b02093
--- /dev/null
+++ b/src/libvterm/src/state.c
@@ -0,0 +1,1925 @@
+#include "vterm_internal.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#define strneq(a,b,n) (strncmp(a,b,n)==0)
+
+#if defined(DEBUG) && DEBUG > 1
+# define DEBUG_GLYPH_COMBINE
+#endif
+
+static int on_resize(int rows, int cols, void *user);
+
+/* Some convenient wrappers to make callback functions easier */
+
+static void putglyph(VTermState *state, const uint32_t chars[], int width, VTermPos pos)
+{
+ VTermGlyphInfo info;
+ info.chars = chars;
+ info.width = width;
+ info.protected_cell = state->protected_cell;
+ info.dwl = state->lineinfo[pos.row].doublewidth;
+ info.dhl = state->lineinfo[pos.row].doubleheight;
+
+ if(state->callbacks && state->callbacks->putglyph)
+ if((*state->callbacks->putglyph)(&info, pos, state->cbdata))
+ return;
+
+ DEBUG_LOG3("libvterm: Unhandled putglyph U+%04x at (%d,%d)\n", chars[0], pos.col, pos.row);
+}
+
+static void updatecursor(VTermState *state, VTermPos *oldpos, int cancel_phantom)
+{
+ if(state->pos.col == oldpos->col && state->pos.row == oldpos->row)
+ return;
+
+ if(cancel_phantom)
+ state->at_phantom = 0;
+
+ if(state->callbacks && state->callbacks->movecursor)
+ if((*state->callbacks->movecursor)(state->pos, *oldpos, state->mode.cursor_visible, state->cbdata))
+ return;
+}
+
+static void erase(VTermState *state, VTermRect rect, int selective)
+{
+ if(state->callbacks && state->callbacks->erase)
+ if((*state->callbacks->erase)(rect, selective, state->cbdata))
+ return;
+}
+
+static VTermState *vterm_state_new(VTerm *vt)
+{
+ VTermState *state = vterm_allocator_malloc(vt, sizeof(VTermState));
+
+ if (state == NULL)
+ return NULL;
+ state->vt = vt;
+
+ state->rows = vt->rows;
+ state->cols = vt->cols;
+
+ state->mouse_col = 0;
+ state->mouse_row = 0;
+ state->mouse_buttons = 0;
+
+ state->mouse_protocol = MOUSE_X10;
+
+ state->callbacks = NULL;
+ state->cbdata = NULL;
+
+ vterm_state_newpen(state);
+
+ state->bold_is_highbright = 0;
+
+ return state;
+}
+
+INTERNAL void vterm_state_free(VTermState *state)
+{
+ vterm_allocator_free(state->vt, state->tabstops);
+ vterm_allocator_free(state->vt, state->lineinfo);
+ vterm_allocator_free(state->vt, state->combine_chars);
+ vterm_allocator_free(state->vt, state);
+}
+
+static void scroll(VTermState *state, VTermRect rect, int downward, int rightward)
+{
+ int rows;
+ int cols;
+ if(!downward && !rightward)
+ return;
+
+ rows = rect.end_row - rect.start_row;
+ if(downward > rows)
+ downward = rows;
+ else if(downward < -rows)
+ downward = -rows;
+
+ cols = rect.end_col - rect.start_col;
+ if(rightward > cols)
+ rightward = cols;
+ else if(rightward < -cols)
+ rightward = -cols;
+
+ // Update lineinfo if full line
+ if(rect.start_col == 0 && rect.end_col == state->cols && rightward == 0) {
+ int height = rect.end_row - rect.start_row - abs(downward);
+
+ if(downward > 0)
+ memmove(state->lineinfo + rect.start_row,
+ state->lineinfo + rect.start_row + downward,
+ height * sizeof(state->lineinfo[0]));
+ else
+ memmove(state->lineinfo + rect.start_row - downward,
+ state->lineinfo + rect.start_row,
+ height * sizeof(state->lineinfo[0]));
+ }
+
+ if(state->callbacks && state->callbacks->scrollrect)
+ if((*state->callbacks->scrollrect)(rect, downward, rightward, state->cbdata))
+ return;
+
+ if(state->callbacks)
+ vterm_scroll_rect(rect, downward, rightward,
+ state->callbacks->moverect, state->callbacks->erase, state->cbdata);
+}
+
+static void linefeed(VTermState *state)
+{
+ if(state->pos.row == SCROLLREGION_BOTTOM(state) - 1) {
+ VTermRect rect;
+ rect.start_row = state->scrollregion_top;
+ rect.end_row = SCROLLREGION_BOTTOM(state);
+ rect.start_col = SCROLLREGION_LEFT(state);
+ rect.end_col = SCROLLREGION_RIGHT(state);
+
+ scroll(state, rect, 1, 0);
+ }
+ else if(state->pos.row < state->rows-1)
+ state->pos.row++;
+}
+
+static void grow_combine_buffer(VTermState *state)
+{
+ size_t new_size = state->combine_chars_size * 2;
+ uint32_t *new_chars = vterm_allocator_malloc(state->vt, new_size * sizeof(new_chars[0]));
+
+ memcpy(new_chars, state->combine_chars, state->combine_chars_size * sizeof(new_chars[0]));
+
+ vterm_allocator_free(state->vt, state->combine_chars);
+
+ state->combine_chars = new_chars;
+ state->combine_chars_size = new_size;
+}
+
+static void set_col_tabstop(VTermState *state, int col)
+{
+ unsigned char mask = 1 << (col & 7);
+ state->tabstops[col >> 3] |= mask;
+}
+
+static void clear_col_tabstop(VTermState *state, int col)
+{
+ unsigned char mask = 1 << (col & 7);
+ state->tabstops[col >> 3] &= ~mask;
+}
+
+static int is_col_tabstop(VTermState *state, int col)
+{
+ unsigned char mask = 1 << (col & 7);
+ return state->tabstops[col >> 3] & mask;
+}
+
+static int is_cursor_in_scrollregion(const VTermState *state)
+{
+ if(state->pos.row < state->scrollregion_top ||
+ state->pos.row >= SCROLLREGION_BOTTOM(state))
+ return 0;
+ if(state->pos.col < SCROLLREGION_LEFT(state) ||
+ state->pos.col >= SCROLLREGION_RIGHT(state))
+ return 0;
+
+ return 1;
+}
+
+static void tab(VTermState *state, int count, int direction)
+{
+ while(count > 0) {
+ if(direction > 0) {
+ if(state->pos.col >= THISROWWIDTH(state)-1)
+ return;
+
+ state->pos.col++;
+ }
+ else if(direction < 0) {
+ if(state->pos.col < 1)
+ return;
+
+ state->pos.col--;
+ }
+
+ if(is_col_tabstop(state, state->pos.col))
+ count--;
+ }
+}
+
+#define NO_FORCE 0
+#define FORCE 1
+
+#define DWL_OFF 0
+#define DWL_ON 1
+
+#define DHL_OFF 0
+#define DHL_TOP 1
+#define DHL_BOTTOM 2
+
+static void set_lineinfo(VTermState *state, int row, int force, int dwl, int dhl)
+{
+ VTermLineInfo info = state->lineinfo[row];
+
+ if(dwl == DWL_OFF)
+ info.doublewidth = DWL_OFF;
+ else if(dwl == DWL_ON)
+ info.doublewidth = DWL_ON;
+ // else -1 to ignore
+
+ if(dhl == DHL_OFF)
+ info.doubleheight = DHL_OFF;
+ else if(dhl == DHL_TOP)
+ info.doubleheight = DHL_TOP;
+ else if(dhl == DHL_BOTTOM)
+ info.doubleheight = DHL_BOTTOM;
+
+ if((state->callbacks &&
+ state->callbacks->setlineinfo &&
+ (*state->callbacks->setlineinfo)(row, &info, state->lineinfo + row, state->cbdata))
+ || force)
+ state->lineinfo[row] = info;
+}
+
+static int on_text(const char bytes[], size_t len, void *user)
+{
+ VTermState *state = user;
+ uint32_t *codepoints;
+ int npoints = 0;
+ size_t eaten = 0;
+ VTermEncodingInstance *encoding;
+ int i = 0;
+
+ VTermPos oldpos = state->pos;
+
+ // We'll have at most len codepoints, plus one from a previous incomplete
+ // sequence.
+ codepoints = vterm_allocator_malloc(state->vt, (len + 1) * sizeof(uint32_t));
+
+ encoding =
+ state->gsingle_set ? &state->encoding[state->gsingle_set] :
+ !(bytes[eaten] & 0x80) ? &state->encoding[state->gl_set] :
+ state->vt->mode.utf8 ? &state->encoding_utf8 :
+ &state->encoding[state->gr_set];
+
+ (*encoding->enc->decode)(encoding->enc, encoding->data,
+ codepoints, &npoints, state->gsingle_set ? 1 : (int)len,
+ bytes, &eaten, len);
+
+ /* There's a chance an encoding (e.g. UTF-8) hasn't found enough bytes yet
+ * for even a single codepoint
+ */
+ if(!npoints)
+ {
+ vterm_allocator_free(state->vt, codepoints);
+ return (int)eaten;
+ }
+
+ if(state->gsingle_set && npoints)
+ state->gsingle_set = 0;
+
+ /* This is a combining char. that needs to be merged with the previous
+ * glyph output */
+ if(vterm_unicode_is_combining(codepoints[i])) {
+ /* See if the cursor has moved since */
+ if(state->pos.row == state->combine_pos.row && state->pos.col == state->combine_pos.col + state->combine_width) {
+#ifdef DEBUG_GLYPH_COMBINE
+ int printpos;
+ printf("DEBUG: COMBINING SPLIT GLYPH of chars {");
+ for(printpos = 0; state->combine_chars[printpos]; printpos++)
+ printf("U+%04x ", state->combine_chars[printpos]);
+ printf("} + {");
+#endif
+
+ /* Find where we need to append these combining chars */
+ int saved_i = 0;
+ while(state->combine_chars[saved_i])
+ saved_i++;
+
+ /* Add extra ones */
+ while(i < npoints && vterm_unicode_is_combining(codepoints[i])) {
+ if(saved_i >= (int)state->combine_chars_size)
+ grow_combine_buffer(state);
+ state->combine_chars[saved_i++] = codepoints[i++];
+ }
+ if(saved_i >= (int)state->combine_chars_size)
+ grow_combine_buffer(state);
+ state->combine_chars[saved_i] = 0;
+
+#ifdef DEBUG_GLYPH_COMBINE
+ for(; state->combine_chars[printpos]; printpos++)
+ printf("U+%04x ", state->combine_chars[printpos]);
+ printf("}\n");
+#endif
+
+ /* Now render it */
+ putglyph(state, state->combine_chars, state->combine_width, state->combine_pos);
+ }
+ else {
+ DEBUG_LOG("libvterm: TODO: Skip over split char+combining\n");
+ }
+ }
+
+ for(; i < npoints; i++) {
+ // Try to find combining characters following this
+ int glyph_starts = i;
+ int glyph_ends;
+ int width = 0;
+ uint32_t *chars;
+
+ for(glyph_ends = i + 1; glyph_ends < npoints; glyph_ends++)
+ if(!vterm_unicode_is_combining(codepoints[glyph_ends]))
+ break;
+
+ chars = vterm_allocator_malloc(state->vt, (glyph_ends - glyph_starts + 1) * sizeof(uint32_t));
+
+ for( ; i < glyph_ends; i++) {
+ int this_width;
+ chars[i - glyph_starts] = codepoints[i];
+ this_width = vterm_unicode_width(codepoints[i]);
+#ifdef DEBUG
+ if(this_width < 0) {
+ fprintf(stderr, "Text with negative-width codepoint U+%04x\n", codepoints[i]);
+ abort();
+ }
+#endif
+ width += this_width;
+ }
+
+ chars[glyph_ends - glyph_starts] = 0;
+ i--;
+
+#ifdef DEBUG_GLYPH_COMBINE
+ int printpos;
+ printf("DEBUG: COMBINED GLYPH of %d chars {", glyph_ends - glyph_starts);
+ for(printpos = 0; printpos < glyph_ends - glyph_starts; printpos++)
+ printf("U+%04x ", chars[printpos]);
+ printf("}, onscreen width %d\n", width);
+#endif
+
+ if(state->at_phantom || state->pos.col + width > THISROWWIDTH(state)) {
+ linefeed(state);
+ state->pos.col = 0;
+ state->at_phantom = 0;
+ }
+
+ if(state->mode.insert) {
+ /* TODO: This will be a little inefficient for large bodies of text, as
+ * it'll have to 'ICH' effectively before every glyph. We should scan
+ * ahead and ICH as many times as required
+ */
+ VTermRect rect;
+ rect.start_row = state->pos.row;
+ rect.end_row = state->pos.row + 1;
+ rect.start_col = state->pos.col;
+ rect.end_col = THISROWWIDTH(state);
+ scroll(state, rect, 0, -1);
+ }
+
+ putglyph(state, chars, width, state->pos);
+
+ if(i == npoints - 1) {
+ /* End of the buffer. Save the chars in case we have to combine with
+ * more on the next call */
+ int save_i;
+ for(save_i = 0; chars[save_i]; save_i++) {
+ if(save_i >= (int)state->combine_chars_size)
+ grow_combine_buffer(state);
+ state->combine_chars[save_i] = chars[save_i];
+ }
+ if(save_i >= (int)state->combine_chars_size)
+ grow_combine_buffer(state);
+ state->combine_chars[save_i] = 0;
+ state->combine_width = width;
+ state->combine_pos = state->pos;
+ }
+
+ if(state->pos.col + width >= THISROWWIDTH(state)) {
+ if(state->mode.autowrap)
+ state->at_phantom = 1;
+ }
+ else {
+ state->pos.col += width;
+ }
+ vterm_allocator_free(state->vt, chars);
+ }
+
+ updatecursor(state, &oldpos, 0);
+
+#ifdef DEBUG
+ if(state->pos.row < 0 || state->pos.row >= state->rows ||
+ state->pos.col < 0 || state->pos.col >= state->cols) {
+ fprintf(stderr, "Position out of bounds after text: (%d,%d)\n",
+ state->pos.row, state->pos.col);
+ abort();
+ }
+#endif
+
+ vterm_allocator_free(state->vt, codepoints);
+ return (int)eaten;
+}
+
+static int on_control(unsigned char control, void *user)
+{
+ VTermState *state = user;
+
+ VTermPos oldpos = state->pos;
+
+ switch(control) {
+ case 0x07: // BEL - ECMA-48 8.3.3
+ if(state->callbacks && state->callbacks->bell)
+ (*state->callbacks->bell)(state->cbdata);
+ break;
+
+ case 0x08: // BS - ECMA-48 8.3.5
+ if(state->pos.col > 0)
+ state->pos.col--;
+ break;
+
+ case 0x09: // HT - ECMA-48 8.3.60
+ tab(state, 1, +1);
+ break;
+
+ case 0x0a: // LF - ECMA-48 8.3.74
+ case 0x0b: // VT
+ case 0x0c: // FF
+ linefeed(state);
+ if(state->mode.newline)
+ state->pos.col = 0;
+ break;
+
+ case 0x0d: // CR - ECMA-48 8.3.15
+ state->pos.col = 0;
+ break;
+
+ case 0x0e: // LS1 - ECMA-48 8.3.76
+ state->gl_set = 1;
+ break;
+
+ case 0x0f: // LS0 - ECMA-48 8.3.75
+ state->gl_set = 0;
+ break;
+
+ case 0x84: // IND - DEPRECATED but implemented for completeness
+ linefeed(state);
+ break;
+
+ case 0x85: // NEL - ECMA-48 8.3.86
+ linefeed(state);
+ state->pos.col = 0;
+ break;
+
+ case 0x88: // HTS - ECMA-48 8.3.62
+ set_col_tabstop(state, state->pos.col);
+ break;
+
+ case 0x8d: // RI - ECMA-48 8.3.104
+ if(state->pos.row == state->scrollregion_top) {
+ VTermRect rect;
+ rect.start_row = state->scrollregion_top;
+ rect.end_row = SCROLLREGION_BOTTOM(state);
+ rect.start_col = SCROLLREGION_LEFT(state);
+ rect.end_col = SCROLLREGION_RIGHT(state);
+
+ scroll(state, rect, -1, 0);
+ }
+ else if(state->pos.row > 0)
+ state->pos.row--;
+ break;
+
+ case 0x8e: // SS2 - ECMA-48 8.3.141
+ state->gsingle_set = 2;
+ break;
+
+ case 0x8f: // SS3 - ECMA-48 8.3.142
+ state->gsingle_set = 3;
+ break;
+
+ default:
+ if(state->fallbacks && state->fallbacks->control)
+ if((*state->fallbacks->control)(control, state->fbdata))
+ return 1;
+
+ return 0;
+ }
+
+ updatecursor(state, &oldpos, 1);
+
+#ifdef DEBUG
+ if(state->pos.row < 0 || state->pos.row >= state->rows ||
+ state->pos.col < 0 || state->pos.col >= state->cols) {
+ fprintf(stderr, "Position out of bounds after Ctrl %02x: (%d,%d)\n",
+ control, state->pos.row, state->pos.col);
+ abort();
+ }
+#endif
+
+ return 1;
+}
+
+static int settermprop_bool(VTermState *state, VTermProp prop, int v)
+{
+ VTermValue val;
+ val.boolean = v;
+ return vterm_state_set_termprop(state, prop, &val);
+}
+
+static int settermprop_int(VTermState *state, VTermProp prop, int v)
+{
+ VTermValue val;
+ val.number = v;
+ return vterm_state_set_termprop(state, prop, &val);
+}
+
+static int settermprop_string(VTermState *state, VTermProp prop, const char *str, size_t len)
+{
+ char *strvalue;
+ int r;
+ VTermValue val;
+ strvalue = vterm_allocator_malloc(state->vt, (len+1) * sizeof(char));
+ strncpy(strvalue, str, len);
+ strvalue[len] = 0;
+
+ val.string = strvalue;
+ r = vterm_state_set_termprop(state, prop, &val);
+ vterm_allocator_free(state->vt, strvalue);
+ return r;
+}
+
+static void savecursor(VTermState *state, int save)
+{
+ if(save) {
+ state->saved.pos = state->pos;
+ state->saved.mode.cursor_visible = state->mode.cursor_visible;
+ state->saved.mode.cursor_blink = state->mode.cursor_blink;
+ state->saved.mode.cursor_shape = state->mode.cursor_shape;
+
+ vterm_state_savepen(state, 1);
+ }
+ else {
+ VTermPos oldpos = state->pos;
+
+ state->pos = state->saved.pos;
+
+ settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, state->saved.mode.cursor_visible);
+ settermprop_bool(state, VTERM_PROP_CURSORBLINK, state->saved.mode.cursor_blink);
+ settermprop_int (state, VTERM_PROP_CURSORSHAPE, state->saved.mode.cursor_shape);
+
+ vterm_state_savepen(state, 0);
+
+ updatecursor(state, &oldpos, 1);
+ }
+}
+
+static int on_escape(const char *bytes, size_t len, void *user)
+{
+ VTermState *state = user;
+
+ /* Easier to decode this from the first byte, even though the final
+ * byte terminates it
+ */
+ switch(bytes[0]) {
+ case ' ':
+ if(len != 2)
+ return 0;
+
+ switch(bytes[1]) {
+ case 'F': // S7C1T
+ state->vt->mode.ctrl8bit = 0;
+ break;
+
+ case 'G': // S8C1T
+ state->vt->mode.ctrl8bit = 1;
+ break;
+
+ default:
+ return 0;
+ }
+ return 2;
+
+ case '#':
+ if(len != 2)
+ return 0;
+
+ switch(bytes[1]) {
+ case '3': // DECDHL top
+ if(state->mode.leftrightmargin)
+ break;
+ set_lineinfo(state, state->pos.row, NO_FORCE, DWL_ON, DHL_TOP);
+ break;
+
+ case '4': // DECDHL bottom
+ if(state->mode.leftrightmargin)
+ break;
+ set_lineinfo(state, state->pos.row, NO_FORCE, DWL_ON, DHL_BOTTOM);
+ break;
+
+ case '5': // DECSWL
+ if(state->mode.leftrightmargin)
+ break;
+ set_lineinfo(state, state->pos.row, NO_FORCE, DWL_OFF, DHL_OFF);
+ break;
+
+ case '6': // DECDWL
+ if(state->mode.leftrightmargin)
+ break;
+ set_lineinfo(state, state->pos.row, NO_FORCE, DWL_ON, DHL_OFF);
+ break;
+
+ case '8': // DECALN
+ {
+ VTermPos pos;
+ uint32_t E[] = { 'E', 0 };
+ for(pos.row = 0; pos.row < state->rows; pos.row++)
+ for(pos.col = 0; pos.col < ROWWIDTH(state, pos.row); pos.col++)
+ putglyph(state, E, 1, pos);
+ break;
+ }
+
+ default:
+ return 0;
+ }
+ return 2;
+
+ case '(': case ')': case '*': case '+': // SCS
+ if(len != 2)
+ return 0;
+
+ {
+ int setnum = bytes[0] - 0x28;
+ VTermEncoding *newenc = vterm_lookup_encoding(ENC_SINGLE_94, bytes[1]);
+
+ if(newenc) {
+ state->encoding[setnum].enc = newenc;
+
+ if(newenc->init)
+ (*newenc->init)(newenc, state->encoding[setnum].data);
+ }
+ }
+
+ return 2;
+
+ case '7': // DECSC
+ savecursor(state, 1);
+ return 1;
+
+ case '8': // DECRC
+ savecursor(state, 0);
+ return 1;
+
+ case '<': // Ignored by VT100. Used in VT52 mode to switch up to VT100
+ return 1;
+
+ case '=': // DECKPAM
+ state->mode.keypad = 1;
+ return 1;
+
+ case '>': // DECKPNM
+ state->mode.keypad = 0;
+ return 1;
+
+ case 'c': // RIS - ECMA-48 8.3.105
+ {
+ VTermPos oldpos = state->pos;
+ vterm_state_reset(state, 1);
+ if(state->callbacks && state->callbacks->movecursor)
+ (*state->callbacks->movecursor)(state->pos, oldpos, state->mode.cursor_visible, state->cbdata);
+ return 1;
+ }
+
+ case 'n': // LS2 - ECMA-48 8.3.78
+ state->gl_set = 2;
+ return 1;
+
+ case 'o': // LS3 - ECMA-48 8.3.80
+ state->gl_set = 3;
+ return 1;
+
+ case '~': // LS1R - ECMA-48 8.3.77
+ state->gr_set = 1;
+ return 1;
+
+ case '}': // LS2R - ECMA-48 8.3.79
+ state->gr_set = 2;
+ return 1;
+
+ case '|': // LS3R - ECMA-48 8.3.81
+ state->gr_set = 3;
+ return 1;
+
+ default:
+ return 0;
+ }
+}
+
+static void set_mode(VTermState *state, int num, int val)
+{
+ switch(num) {
+ case 4: // IRM - ECMA-48 7.2.10
+ state->mode.insert = val;
+ break;
+
+ case 20: // LNM - ANSI X3.4-1977
+ state->mode.newline = val;
+ break;
+
+ default:
+ DEBUG_LOG1("libvterm: Unknown mode %d\n", num);
+ return;
+ }
+}
+
+static void set_dec_mode(VTermState *state, int num, int val)
+{
+ switch(num) {
+ case 1:
+ state->mode.cursor = val;
+ break;
+
+ case 5: // DECSCNM - screen mode
+ settermprop_bool(state, VTERM_PROP_REVERSE, val);
+ break;
+
+ case 6: // DECOM - origin mode
+ {
+ VTermPos oldpos = state->pos;
+ state->mode.origin = val;
+ state->pos.row = state->mode.origin ? state->scrollregion_top : 0;
+ state->pos.col = state->mode.origin ? SCROLLREGION_LEFT(state) : 0;
+ updatecursor(state, &oldpos, 1);
+ }
+ break;
+
+ case 7:
+ state->mode.autowrap = val;
+ break;
+
+ case 12:
+ settermprop_bool(state, VTERM_PROP_CURSORBLINK, val);
+ break;
+
+ case 25:
+ settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, val);
+ break;
+
+ case 69: // DECVSSM - vertical split screen mode
+ // DECLRMM - left/right margin mode
+ state->mode.leftrightmargin = val;
+ if(val) {
+ int row;
+
+ // Setting DECVSSM must clear doublewidth/doubleheight state of every line
+ for(row = 0; row < state->rows; row++)
+ set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF);
+ }
+
+ break;
+
+ case 1000:
+ case 1002:
+ case 1003:
+ settermprop_int(state, VTERM_PROP_MOUSE,
+ !val ? VTERM_PROP_MOUSE_NONE :
+ (num == 1000) ? VTERM_PROP_MOUSE_CLICK :
+ (num == 1002) ? VTERM_PROP_MOUSE_DRAG :
+ VTERM_PROP_MOUSE_MOVE);
+ break;
+
+ case 1004:
+ state->mode.report_focus = val;
+ break;
+
+ case 1005:
+ state->mouse_protocol = val ? MOUSE_UTF8 : MOUSE_X10;
+ break;
+
+ case 1006:
+ state->mouse_protocol = val ? MOUSE_SGR : MOUSE_X10;
+ break;
+
+ case 1015:
+ state->mouse_protocol = val ? MOUSE_RXVT : MOUSE_X10;
+ break;
+
+ case 1047:
+ settermprop_bool(state, VTERM_PROP_ALTSCREEN, val);
+ break;
+
+ case 1048:
+ savecursor(state, val);
+ break;
+
+ case 1049:
+ settermprop_bool(state, VTERM_PROP_ALTSCREEN, val);
+ savecursor(state, val);
+ break;
+
+ case 2004:
+ state->mode.bracketpaste = val;
+ break;
+
+ default:
+ DEBUG_LOG1("libvterm: Unknown DEC mode %d\n", num);
+ return;
+ }
+}
+
+static void request_dec_mode(VTermState *state, int num)
+{
+ int reply;
+
+ switch(num) {
+ case 1:
+ reply = state->mode.cursor;
+ break;
+
+ case 5:
+ reply = state->mode.screen;
+ break;
+
+ case 6:
+ reply = state->mode.origin;
+ break;
+
+ case 7:
+ reply = state->mode.autowrap;
+ break;
+
+ case 12:
+ reply = state->mode.cursor_blink;
+ break;
+
+ case 25:
+ reply = state->mode.cursor_visible;
+ break;
+
+ case 69:
+ reply = state->mode.leftrightmargin;
+ break;
+
+ case 1000:
+ reply = state->mouse_flags == MOUSE_WANT_CLICK;
+ break;
+
+ case 1002:
+ reply = state->mouse_flags == (MOUSE_WANT_CLICK|MOUSE_WANT_DRAG);
+ break;
+
+ case 1003:
+ reply = state->mouse_flags == (MOUSE_WANT_CLICK|MOUSE_WANT_MOVE);
+ break;
+
+ case 1004:
+ reply = state->mode.report_focus;
+ break;
+
+ case 1005:
+ reply = state->mouse_protocol == MOUSE_UTF8;
+ break;
+
+ case 1006:
+ reply = state->mouse_protocol == MOUSE_SGR;
+ break;
+
+ case 1015:
+ reply = state->mouse_protocol == MOUSE_RXVT;
+ break;
+
+ case 1047:
+ reply = state->mode.alt_screen;
+ break;
+
+ case 2004:
+ reply = state->mode.bracketpaste;
+ break;
+
+ default:
+ vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?%d;%d$y", num, 0);
+ return;
+ }
+
+ vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?%d;%d$y", num, reply ? 1 : 2);
+}
+
+static int on_csi(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user)
+{
+ VTermState *state = user;
+ int leader_byte = 0;
+ int intermed_byte = 0;
+ VTermPos oldpos = state->pos;
+
+ /* Some temporaries for later code */
+ int count, val;
+ int row, col;
+ VTermRect rect;
+ int selective;
+
+ if(leader && leader[0]) {
+ if(leader[1]) // longer than 1 char
+ return 0;
+
+ switch(leader[0]) {
+ case '?':
+ case '>':
+ leader_byte = leader[0];
+ break;
+ default:
+ return 0;
+ }
+ }
+
+ if(intermed && intermed[0]) {
+ if(intermed[1]) // longer than 1 char
+ return 0;
+
+ switch(intermed[0]) {
+ case ' ':
+ case '"':
+ case '$':
+ case '\'':
+ intermed_byte = intermed[0];
+ break;
+ default:
+ return 0;
+ }
+ }
+
+ oldpos = state->pos;
+
+#define LBOUND(v,min) if((v) < (min)) (v) = (min)
+#define UBOUND(v,max) if((v) > (max)) (v) = (max)
+
+#define LEADER(l,b) ((l << 8) | b)
+#define INTERMED(i,b) ((i << 16) | b)
+
+ switch(intermed_byte << 16 | leader_byte << 8 | command) {
+ case 0x40: // ICH - ECMA-48 8.3.64
+ count = CSI_ARG_COUNT(args[0]);
+
+ if(!is_cursor_in_scrollregion(state))
+ break;
+
+ rect.start_row = state->pos.row;
+ rect.end_row = state->pos.row + 1;
+ rect.start_col = state->pos.col;
+ if(state->mode.leftrightmargin)
+ rect.end_col = SCROLLREGION_RIGHT(state);
+ else
+ rect.end_col = THISROWWIDTH(state);
+
+ scroll(state, rect, 0, -count);
+
+ break;
+
+ case 0x41: // CUU - ECMA-48 8.3.22
+ count = CSI_ARG_COUNT(args[0]);
+ state->pos.row -= count;
+ state->at_phantom = 0;
+ break;
+
+ case 0x42: // CUD - ECMA-48 8.3.19
+ count = CSI_ARG_COUNT(args[0]);
+ state->pos.row += count;
+ state->at_phantom = 0;
+ break;
+
+ case 0x43: // CUF - ECMA-48 8.3.20
+ count = CSI_ARG_COUNT(args[0]);
+ state->pos.col += count;
+ state->at_phantom = 0;
+ break;
+
+ case 0x44: // CUB - ECMA-48 8.3.18
+ count = CSI_ARG_COUNT(args[0]);
+ state->pos.col -= count;
+ state->at_phantom = 0;
+ break;
+
+ case 0x45: // CNL - ECMA-48 8.3.12
+ count = CSI_ARG_COUNT(args[0]);
+ state->pos.col = 0;
+ state->pos.row += count;
+ state->at_phantom = 0;
+ break;
+
+ case 0x46: // CPL - ECMA-48 8.3.13
+ count = CSI_ARG_COUNT(args[0]);
+ state->pos.col = 0;
+ state->pos.row -= count;
+ state->at_phantom = 0;
+ break;
+
+ case 0x47: // CHA - ECMA-48 8.3.9
+ val = CSI_ARG_OR(args[0], 1);
+ state->pos.col = val-1;
+ state->at_phantom = 0;
+ break;
+
+ case 0x48: // CUP - ECMA-48 8.3.21
+ row = CSI_ARG_OR(args[0], 1);
+ col = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? 1 : CSI_ARG(args[1]);
+ // zero-based
+ state->pos.row = row-1;
+ state->pos.col = col-1;
+ if(state->mode.origin) {
+ state->pos.row += state->scrollregion_top;
+ state->pos.col += SCROLLREGION_LEFT(state);
+ }
+ state->at_phantom = 0;
+ break;
+
+ case 0x49: // CHT - ECMA-48 8.3.10
+ count = CSI_ARG_COUNT(args[0]);
+ tab(state, count, +1);
+ break;
+
+ case 0x4a: // ED - ECMA-48 8.3.39
+ case LEADER('?', 0x4a): // DECSED - Selective Erase in Display
+ selective = (leader_byte == '?');
+ switch(CSI_ARG(args[0])) {
+ case CSI_ARG_MISSING:
+ case 0:
+ rect.start_row = state->pos.row; rect.end_row = state->pos.row + 1;
+ rect.start_col = state->pos.col; rect.end_col = state->cols;
+ if(rect.end_col > rect.start_col)
+ erase(state, rect, selective);
+
+ rect.start_row = state->pos.row + 1; rect.end_row = state->rows;
+ rect.start_col = 0;
+ for(row = rect.start_row; row < rect.end_row; row++)
+ set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF);
+ if(rect.end_row > rect.start_row)
+ erase(state, rect, selective);
+ break;
+
+ case 1:
+ rect.start_row = 0; rect.end_row = state->pos.row;
+ rect.start_col = 0; rect.end_col = state->cols;
+ for(row = rect.start_row; row < rect.end_row; row++)
+ set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF);
+ if(rect.end_col > rect.start_col)
+ erase(state, rect, selective);
+
+ rect.start_row = state->pos.row; rect.end_row = state->pos.row + 1;
+ rect.end_col = state->pos.col + 1;
+ if(rect.end_row > rect.start_row)
+ erase(state, rect, selective);
+ break;
+
+ case 2:
+ rect.start_row = 0; rect.end_row = state->rows;
+ rect.start_col = 0; rect.end_col = state->cols;
+ for(row = rect.start_row; row < rect.end_row; row++)
+ set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF);
+ erase(state, rect, selective);
+ break;
+ }
+ break;
+
+ case 0x4b: // EL - ECMA-48 8.3.41
+ case LEADER('?', 0x4b): // DECSEL - Selective Erase in Line
+ selective = (leader_byte == '?');
+ rect.start_row = state->pos.row;
+ rect.end_row = state->pos.row + 1;
+
+ switch(CSI_ARG(args[0])) {
+ case CSI_ARG_MISSING:
+ case 0:
+ rect.start_col = state->pos.col; rect.end_col = THISROWWIDTH(state); break;
+ case 1:
+ rect.start_col = 0; rect.end_col = state->pos.col + 1; break;
+ case 2:
+ rect.start_col = 0; rect.end_col = THISROWWIDTH(state); break;
+ default:
+ return 0;
+ }
+
+ if(rect.end_col > rect.start_col)
+ erase(state, rect, selective);
+
+ break;
+
+ case 0x4c: // IL - ECMA-48 8.3.67
+ count = CSI_ARG_COUNT(args[0]);
+
+ if(!is_cursor_in_scrollregion(state))
+ break;
+
+ rect.start_row = state->pos.row;
+ rect.end_row = SCROLLREGION_BOTTOM(state);
+ rect.start_col = SCROLLREGION_LEFT(state);
+ rect.end_col = SCROLLREGION_RIGHT(state);
+
+ scroll(state, rect, -count, 0);
+
+ break;
+
+ case 0x4d: // DL - ECMA-48 8.3.32
+ count = CSI_ARG_COUNT(args[0]);
+
+ if(!is_cursor_in_scrollregion(state))
+ break;
+
+ rect.start_row = state->pos.row;
+ rect.end_row = SCROLLREGION_BOTTOM(state);
+ rect.start_col = SCROLLREGION_LEFT(state);
+ rect.end_col = SCROLLREGION_RIGHT(state);
+
+ scroll(state, rect, count, 0);
+
+ break;
+
+ case 0x50: // DCH - ECMA-48 8.3.26
+ count = CSI_ARG_COUNT(args[0]);
+
+ if(!is_cursor_in_scrollregion(state))
+ break;
+
+ rect.start_row = state->pos.row;
+ rect.end_row = state->pos.row + 1;
+ rect.start_col = state->pos.col;
+ if(state->mode.leftrightmargin)
+ rect.end_col = SCROLLREGION_RIGHT(state);
+ else
+ rect.end_col = THISROWWIDTH(state);
+
+ scroll(state, rect, 0, count);
+
+ break;
+
+ case 0x53: // SU - ECMA-48 8.3.147
+ count = CSI_ARG_COUNT(args[0]);
+
+ rect.start_row = state->scrollregion_top;
+ rect.end_row = SCROLLREGION_BOTTOM(state);
+ rect.start_col = SCROLLREGION_LEFT(state);
+ rect.end_col = SCROLLREGION_RIGHT(state);
+
+ scroll(state, rect, count, 0);
+
+ break;
+
+ case 0x54: // SD - ECMA-48 8.3.113
+ count = CSI_ARG_COUNT(args[0]);
+
+ rect.start_row = state->scrollregion_top;
+ rect.end_row = SCROLLREGION_BOTTOM(state);
+ rect.start_col = SCROLLREGION_LEFT(state);
+ rect.end_col = SCROLLREGION_RIGHT(state);
+
+ scroll(state, rect, -count, 0);
+
+ break;
+
+ case 0x58: // ECH - ECMA-48 8.3.38
+ count = CSI_ARG_COUNT(args[0]);
+
+ rect.start_row = state->pos.row;
+ rect.end_row = state->pos.row + 1;
+ rect.start_col = state->pos.col;
+ rect.end_col = state->pos.col + count;
+ UBOUND(rect.end_col, THISROWWIDTH(state));
+
+ erase(state, rect, 0);
+ break;
+
+ case 0x5a: // CBT - ECMA-48 8.3.7
+ count = CSI_ARG_COUNT(args[0]);
+ tab(state, count, -1);
+ break;
+
+ case 0x60: // HPA - ECMA-48 8.3.57
+ col = CSI_ARG_OR(args[0], 1);
+ state->pos.col = col-1;
+ state->at_phantom = 0;
+ break;
+
+ case 0x61: // HPR - ECMA-48 8.3.59
+ count = CSI_ARG_COUNT(args[0]);
+ state->pos.col += count;
+ state->at_phantom = 0;
+ break;
+
+ case 0x63: // DA - ECMA-48 8.3.24
+ val = CSI_ARG_OR(args[0], 0);
+ if(val == 0)
+ // DEC VT100 response
+ vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?1;2c");
+ break;
+
+ case LEADER('>', 0x63): // DEC secondary Device Attributes
+ // This returns xterm version number 100.
+ vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, ">%d;%d;%dc", 0, 100, 0);
+ break;
+
+ case 0x64: // VPA - ECMA-48 8.3.158
+ row = CSI_ARG_OR(args[0], 1);
+ state->pos.row = row-1;
+ if(state->mode.origin)
+ state->pos.row += state->scrollregion_top;
+ state->at_phantom = 0;
+ break;
+
+ case 0x65: // VPR - ECMA-48 8.3.160
+ count = CSI_ARG_COUNT(args[0]);
+ state->pos.row += count;
+ state->at_phantom = 0;
+ break;
+
+ case 0x66: // HVP - ECMA-48 8.3.63
+ row = CSI_ARG_OR(args[0], 1);
+ col = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? 1 : CSI_ARG(args[1]);
+ // zero-based
+ state->pos.row = row-1;
+ state->pos.col = col-1;
+ if(state->mode.origin) {
+ state->pos.row += state->scrollregion_top;
+ state->pos.col += SCROLLREGION_LEFT(state);
+ }
+ state->at_phantom = 0;
+ break;
+
+ case 0x67: // TBC - ECMA-48 8.3.154
+ val = CSI_ARG_OR(args[0], 0);
+
+ switch(val) {
+ case 0:
+ clear_col_tabstop(state, state->pos.col);
+ break;
+ case 3:
+ case 5:
+ for(col = 0; col < state->cols; col++)
+ clear_col_tabstop(state, col);
+ break;
+ case 1:
+ case 2:
+ case 4:
+ break;
+ /* TODO: 1, 2 and 4 aren't meaningful yet without line tab stops */
+ default:
+ return 0;
+ }
+ break;
+
+ case 0x68: // SM - ECMA-48 8.3.125
+ if(!CSI_ARG_IS_MISSING(args[0]))
+ set_mode(state, CSI_ARG(args[0]), 1);
+ break;
+
+ case LEADER('?', 0x68): // DEC private mode set
+ if(!CSI_ARG_IS_MISSING(args[0]))
+ set_dec_mode(state, CSI_ARG(args[0]), 1);
+ break;
+
+ case 0x6a: // HPB - ECMA-48 8.3.58
+ count = CSI_ARG_COUNT(args[0]);
+ state->pos.col -= count;
+ state->at_phantom = 0;
+ break;
+
+ case 0x6b: // VPB - ECMA-48 8.3.159
+ count = CSI_ARG_COUNT(args[0]);
+ state->pos.row -= count;
+ state->at_phantom = 0;
+ break;
+
+ case 0x6c: // RM - ECMA-48 8.3.106
+ if(!CSI_ARG_IS_MISSING(args[0]))
+ set_mode(state, CSI_ARG(args[0]), 0);
+ break;
+
+ case LEADER('?', 0x6c): // DEC private mode reset
+ if(!CSI_ARG_IS_MISSING(args[0]))
+ set_dec_mode(state, CSI_ARG(args[0]), 0);
+ break;
+
+ case 0x6d: // SGR - ECMA-48 8.3.117
+ vterm_state_setpen(state, args, argcount);
+ break;
+
+ case 0x6e: // DSR - ECMA-48 8.3.35
+ case LEADER('?', 0x6e): // DECDSR
+ val = CSI_ARG_OR(args[0], 0);
+
+ {
+ char *qmark = (leader_byte == '?') ? "?" : "";
+
+ switch(val) {
+ case 0: case 1: case 2: case 3: case 4:
+ // ignore - these are replies
+ break;
+ case 5:
+ vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "%s0n", qmark);
+ break;
+ case 6: // CPR - cursor position report
+ vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "%s%d;%dR", qmark, state->pos.row + 1, state->pos.col + 1);
+ break;
+ }
+ }
+ break;
+
+
+ case LEADER('!', 0x70): // DECSTR - DEC soft terminal reset
+ vterm_state_reset(state, 0);
+ break;
+
+ case LEADER('?', INTERMED('$', 0x70)):
+ request_dec_mode(state, CSI_ARG(args[0]));
+ break;
+
+ case INTERMED(' ', 0x71): // DECSCUSR - DEC set cursor shape
+ val = CSI_ARG_OR(args[0], 1);
+
+ switch(val) {
+ case 0: case 1:
+ settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1);
+ settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BLOCK);
+ break;
+ case 2:
+ settermprop_bool(state, VTERM_PROP_CURSORBLINK, 0);
+ settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BLOCK);
+ break;
+ case 3:
+ settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1);
+ settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_UNDERLINE);
+ break;
+ case 4:
+ settermprop_bool(state, VTERM_PROP_CURSORBLINK, 0);
+ settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_UNDERLINE);
+ break;
+ case 5:
+ settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1);
+ settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BAR_LEFT);
+ break;
+ case 6:
+ settermprop_bool(state, VTERM_PROP_CURSORBLINK, 0);
+ settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BAR_LEFT);
+ break;
+ }
+
+ break;
+
+ case INTERMED('"', 0x71): // DECSCA - DEC select character protection attribute
+ val = CSI_ARG_OR(args[0], 0);
+
+ switch(val) {
+ case 0: case 2:
+ state->protected_cell = 0;
+ break;
+ case 1:
+ state->protected_cell = 1;
+ break;
+ }
+
+ break;
+
+ case 0x72: // DECSTBM - DEC custom
+ state->scrollregion_top = CSI_ARG_OR(args[0], 1) - 1;
+ state->scrollregion_bottom = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? -1 : CSI_ARG(args[1]);
+ LBOUND(state->scrollregion_top, 0);
+ UBOUND(state->scrollregion_top, state->rows);
+ LBOUND(state->scrollregion_bottom, -1);
+ if(state->scrollregion_top == 0 && state->scrollregion_bottom == state->rows)
+ state->scrollregion_bottom = -1;
+ else
+ UBOUND(state->scrollregion_bottom, state->rows);
+
+ if(SCROLLREGION_BOTTOM(state) <= state->scrollregion_top) {
+ // Invalid
+ state->scrollregion_top = 0;
+ state->scrollregion_bottom = -1;
+ }
+
+ break;
+
+ case 0x73: // DECSLRM - DEC custom
+ // Always allow setting these margins, just they won't take effect without DECVSSM
+ state->scrollregion_left = CSI_ARG_OR(args[0], 1) - 1;
+ state->scrollregion_right = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? -1 : CSI_ARG(args[1]);
+ LBOUND(state->scrollregion_left, 0);
+ UBOUND(state->scrollregion_left, state->cols);
+ LBOUND(state->scrollregion_right, -1);
+ if(state->scrollregion_left == 0 && state->scrollregion_right == state->cols)
+ state->scrollregion_right = -1;
+ else
+ UBOUND(state->scrollregion_right, state->cols);
+
+ if(state->scrollregion_right > -1 &&
+ state->scrollregion_right <= state->scrollregion_left) {
+ // Invalid
+ state->scrollregion_left = 0;
+ state->scrollregion_right = -1;
+ }
+
+ break;
+
+ case 0x74:
+ switch(CSI_ARG(args[0])) {
+ case 8: /* CSI 8 ; rows ; cols t set size */
+ if (argcount == 3)
+ on_resize(CSI_ARG(args[1]), CSI_ARG(args[2]), state);
+ }
+ break;
+
+ case INTERMED('\'', 0x7D): // DECIC
+ count = CSI_ARG_COUNT(args[0]);
+
+ if(!is_cursor_in_scrollregion(state))
+ break;
+
+ rect.start_row = state->scrollregion_top;
+ rect.end_row = SCROLLREGION_BOTTOM(state);
+ rect.start_col = state->pos.col;
+ rect.end_col = SCROLLREGION_RIGHT(state);
+
+ scroll(state, rect, 0, -count);
+
+ break;
+
+ case INTERMED('\'', 0x7E): // DECDC
+ count = CSI_ARG_COUNT(args[0]);
+
+ if(!is_cursor_in_scrollregion(state))
+ break;
+
+ rect.start_row = state->scrollregion_top;
+ rect.end_row = SCROLLREGION_BOTTOM(state);
+ rect.start_col = state->pos.col;
+ rect.end_col = SCROLLREGION_RIGHT(state);
+
+ scroll(state, rect, 0, count);
+
+ break;
+
+ default:
+ if(state->fallbacks && state->fallbacks->csi)
+ if((*state->fallbacks->csi)(leader, args, argcount, intermed, command, state->fbdata))
+ return 1;
+
+ return 0;
+ }
+
+ if(state->mode.origin) {
+ LBOUND(state->pos.row, state->scrollregion_top);
+ UBOUND(state->pos.row, SCROLLREGION_BOTTOM(state)-1);
+ LBOUND(state->pos.col, SCROLLREGION_LEFT(state));
+ UBOUND(state->pos.col, SCROLLREGION_RIGHT(state)-1);
+ }
+ else {
+ LBOUND(state->pos.row, 0);
+ UBOUND(state->pos.row, state->rows-1);
+ LBOUND(state->pos.col, 0);
+ UBOUND(state->pos.col, THISROWWIDTH(state)-1);
+ }
+
+ updatecursor(state, &oldpos, 1);
+
+#ifdef DEBUG
+ if(state->pos.row < 0 || state->pos.row >= state->rows ||
+ state->pos.col < 0 || state->pos.col >= state->cols) {
+ fprintf(stderr, "Position out of bounds after CSI %c: (%d,%d)\n",
+ command, state->pos.row, state->pos.col);
+ abort();
+ }
+
+ if(SCROLLREGION_BOTTOM(state) <= state->scrollregion_top) {
+ fprintf(stderr, "Scroll region height out of bounds after CSI %c: %d <= %d\n",
+ command, SCROLLREGION_BOTTOM(state), state->scrollregion_top);
+ abort();
+ }
+
+ if(SCROLLREGION_RIGHT(state) <= SCROLLREGION_LEFT(state)) {
+ fprintf(stderr, "Scroll region width out of bounds after CSI %c: %d <= %d\n",
+ command, SCROLLREGION_RIGHT(state), SCROLLREGION_LEFT(state));
+ abort();
+ }
+#endif
+
+ return 1;
+}
+
+static int on_osc(const char *command, size_t cmdlen, void *user)
+{
+ VTermState *state = user;
+
+ if(cmdlen < 2)
+ return 0;
+
+ if(strneq(command, "0;", 2)) {
+ settermprop_string(state, VTERM_PROP_ICONNAME, command + 2, cmdlen - 2);
+ settermprop_string(state, VTERM_PROP_TITLE, command + 2, cmdlen - 2);
+ return 1;
+ }
+ else if(strneq(command, "1;", 2)) {
+ settermprop_string(state, VTERM_PROP_ICONNAME, command + 2, cmdlen - 2);
+ return 1;
+ }
+ else if(strneq(command, "2;", 2)) {
+ settermprop_string(state, VTERM_PROP_TITLE, command + 2, cmdlen - 2);
+ return 1;
+ }
+ else if(strneq(command, "10;", 3)) {
+ /* request foreground color: <Esc>]10;?<0x07> */
+ int red = state->default_fg.red;
+ int blue = state->default_fg.blue;
+ int green = state->default_fg.green;
+ vterm_push_output_sprintf_ctrl(state->vt, C1_OSC, "10;rgb:%02x%02x/%02x%02x/%02x%02x\x07", red, red, green, green, blue, blue);
+ return 1;
+ }
+ else if(strneq(command, "11;", 3)) {
+ /* request background color: <Esc>]11;?<0x07> */
+ int red = state->default_bg.red;
+ int blue = state->default_bg.blue;
+ int green = state->default_bg.green;
+ vterm_push_output_sprintf_ctrl(state->vt, C1_OSC, "11;rgb:%02x%02x/%02x%02x/%02x%02x\x07", red, red, green, green, blue, blue);
+ return 1;
+ }
+ else if(strneq(command, "12;", 3)) {
+ settermprop_string(state, VTERM_PROP_CURSORCOLOR, command + 3, cmdlen - 3);
+ return 1;
+ }
+ else if(state->fallbacks && state->fallbacks->osc)
+ if((*state->fallbacks->osc)(command, cmdlen, state->fbdata))
+ return 1;
+
+ return 0;
+}
+
+static void request_status_string(VTermState *state, const char *command, size_t cmdlen)
+{
+ if(cmdlen == 1)
+ switch(command[0]) {
+ case 'm': // Query SGR
+ {
+ long args[20];
+ int argc = vterm_state_getpen(state, args, sizeof(args)/sizeof(args[0]));
+ int argi;
+ vterm_push_output_sprintf_ctrl(state->vt, C1_DCS, "1$r");
+ for(argi = 0; argi < argc; argi++)
+ vterm_push_output_sprintf(state->vt,
+ argi == argc - 1 ? "%d" :
+ CSI_ARG_HAS_MORE(args[argi]) ? "%d:" :
+ "%d;",
+ CSI_ARG(args[argi]));
+ vterm_push_output_sprintf(state->vt, "m");
+ vterm_push_output_sprintf_ctrl(state->vt, C1_ST, "");
+ }
+ return;
+ case 'r': // Query DECSTBM
+ vterm_push_output_sprintf_dcs(state->vt, "1$r%d;%dr", state->scrollregion_top+1, SCROLLREGION_BOTTOM(state));
+ return;
+ case 's': // Query DECSLRM
+ vterm_push_output_sprintf_dcs(state->vt, "1$r%d;%ds", SCROLLREGION_LEFT(state)+1, SCROLLREGION_RIGHT(state));
+ return;
+ }
+
+ if(cmdlen == 2) {
+ if(strneq(command, " q", 2)) {
+ int reply;
+ switch(state->mode.cursor_shape) {
+ case VTERM_PROP_CURSORSHAPE_BLOCK: reply = 2; break;
+ case VTERM_PROP_CURSORSHAPE_UNDERLINE: reply = 4; break;
+ default: /* VTERM_PROP_CURSORSHAPE_BAR_LEFT */ reply = 6; break;
+ }
+ if(state->mode.cursor_blink)
+ reply--;
+ vterm_push_output_sprintf_dcs(state->vt, "1$r%d q", reply);
+ return;
+ }
+ else if(strneq(command, "\"q", 2)) {
+ vterm_push_output_sprintf_dcs(state->vt, "1$r%d\"q", state->protected_cell ? 1 : 2);
+ return;
+ }
+ }
+
+ vterm_push_output_sprintf_dcs(state->vt, "0$r%.s", (int)cmdlen, command);
+}
+
+static int on_dcs(const char *command, size_t cmdlen, void *user)
+{
+ VTermState *state = user;
+
+ if(cmdlen >= 2 && strneq(command, "$q", 2)) {
+ request_status_string(state, command+2, cmdlen-2);
+ return 1;
+ }
+ else if(state->fallbacks && state->fallbacks->dcs)
+ if((*state->fallbacks->dcs)(command, cmdlen, state->fbdata))
+ return 1;
+
+ return 0;
+}
+
+static int on_resize(int rows, int cols, void *user)
+{
+ VTermState *state = user;
+ VTermPos oldpos = state->pos;
+ VTermPos delta = { 0, 0 };
+
+ if(cols != state->cols) {
+ unsigned char *newtabstops = vterm_allocator_malloc(state->vt, (cols + 7) / 8);
+
+ /* TODO: This can all be done much more efficiently bytewise */
+ int col;
+ for(col = 0; col < state->cols && col < cols; col++) {
+ unsigned char mask = 1 << (col & 7);
+ if(state->tabstops[col >> 3] & mask)
+ newtabstops[col >> 3] |= mask;
+ else
+ newtabstops[col >> 3] &= ~mask;
+ }
+
+ for( ; col < cols; col++) {
+ unsigned char mask = 1 << (col & 7);
+ if(col % 8 == 0)
+ newtabstops[col >> 3] |= mask;
+ else
+ newtabstops[col >> 3] &= ~mask;
+ }
+
+ vterm_allocator_free(state->vt, state->tabstops);
+ state->tabstops = newtabstops;
+ }
+
+ if(rows != state->rows) {
+ VTermLineInfo *newlineinfo = vterm_allocator_malloc(state->vt, rows * sizeof(VTermLineInfo));
+
+ int row;
+ for(row = 0; row < state->rows && row < rows; row++) {
+ newlineinfo[row] = state->lineinfo[row];
+ }
+
+ for( ; row < rows; row++) {
+ newlineinfo[row].doublewidth = 0;
+ newlineinfo[row].doubleheight = 0;
+ }
+
+ vterm_allocator_free(state->vt, state->lineinfo);
+ state->lineinfo = newlineinfo;
+ }
+
+ state->rows = rows;
+ state->cols = cols;
+
+ if(state->scrollregion_bottom > -1)
+ UBOUND(state->scrollregion_bottom, state->rows);
+ if(state->scrollregion_right > -1)
+ UBOUND(state->scrollregion_right, state->cols);
+
+ if(state->callbacks && state->callbacks->resize)
+ (*state->callbacks->resize)(rows, cols, &delta, state->cbdata);
+
+ if(state->at_phantom && state->pos.col < cols-1) {
+ state->at_phantom = 0;
+ state->pos.col++;
+ }
+
+ state->pos.row += delta.row;
+ state->pos.col += delta.col;
+
+ if(state->pos.row >= rows)
+ state->pos.row = rows - 1;
+ if(state->pos.col >= cols)
+ state->pos.col = cols - 1;
+
+ updatecursor(state, &oldpos, 1);
+
+ return 1;
+}
+
+static const VTermParserCallbacks parser_callbacks = {
+ on_text, /* text */
+ on_control, /* control */
+ on_escape, /* escape */
+ on_csi, /* csi */
+ on_osc, /* osc */
+ on_dcs, /* dcs */
+ on_resize /* resize */
+};
+
+/*
+ * Return the existing state or create a new one.
+ * Returns NULL when out of memory.
+ */
+VTermState *vterm_obtain_state(VTerm *vt)
+{
+ VTermState *state;
+ if(vt->state)
+ return vt->state;
+
+ state = vterm_state_new(vt);
+ if (state == NULL)
+ return NULL;
+ vt->state = state;
+
+ state->combine_chars_size = 16;
+ state->combine_chars = vterm_allocator_malloc(state->vt, state->combine_chars_size * sizeof(state->combine_chars[0]));
+
+ state->tabstops = vterm_allocator_malloc(state->vt, (state->cols + 7) / 8);
+
+ state->lineinfo = vterm_allocator_malloc(state->vt, state->rows * sizeof(VTermLineInfo));
+
+ state->encoding_utf8.enc = vterm_lookup_encoding(ENC_UTF8, 'u');
+ if(*state->encoding_utf8.enc->init != NULL)
+ (*state->encoding_utf8.enc->init)(state->encoding_utf8.enc, state->encoding_utf8.data);
+
+ vterm_parser_set_callbacks(vt, &parser_callbacks, state);
+
+ return state;
+}
+
+void vterm_state_reset(VTermState *state, int hard)
+{
+ VTermEncoding *default_enc;
+
+ state->scrollregion_top = 0;
+ state->scrollregion_bottom = -1;
+ state->scrollregion_left = 0;
+ state->scrollregion_right = -1;
+
+ state->mode.keypad = 0;
+ state->mode.cursor = 0;
+ state->mode.autowrap = 1;
+ state->mode.insert = 0;
+ state->mode.newline = 0;
+ state->mode.alt_screen = 0;
+ state->mode.origin = 0;
+ state->mode.leftrightmargin = 0;
+ state->mode.bracketpaste = 0;
+ state->mode.report_focus = 0;
+
+ state->vt->mode.ctrl8bit = 0;
+
+ {
+ int col;
+ for(col = 0; col < state->cols; col++)
+ if(col % 8 == 0)
+ set_col_tabstop(state, col);
+ else
+ clear_col_tabstop(state, col);
+ }
+
+ {
+ int row;
+ for(row = 0; row < state->rows; row++)
+ set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF);
+ }
+
+ if(state->callbacks && state->callbacks->initpen)
+ (*state->callbacks->initpen)(state->cbdata);
+
+ vterm_state_resetpen(state);
+
+ default_enc = state->vt->mode.utf8 ?
+ vterm_lookup_encoding(ENC_UTF8, 'u') :
+ vterm_lookup_encoding(ENC_SINGLE_94, 'B');
+
+ {
+ int i;
+ for(i = 0; i < 4; i++) {
+ state->encoding[i].enc = default_enc;
+ if(default_enc->init)
+ (*default_enc->init)(default_enc, state->encoding[i].data);
+ }
+ }
+
+ state->gl_set = 0;
+ state->gr_set = 1;
+ state->gsingle_set = 0;
+
+ state->protected_cell = 0;
+
+ // Initialise the props
+ settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, 1);
+ settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1);
+ settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BLOCK);
+
+ if(hard) {
+ VTermRect rect = { 0, 0, 0, 0 };
+
+ state->pos.row = 0;
+ state->pos.col = 0;
+ state->at_phantom = 0;
+
+ rect.end_row = state->rows;
+ rect.end_col = state->cols;
+ erase(state, rect, 0);
+ }
+}
+
+void vterm_state_get_cursorpos(const VTermState *state, VTermPos *cursorpos)
+{
+ *cursorpos = state->pos;
+}
+
+void vterm_state_get_mousestate(const VTermState *state, VTermMouseState *mousestate)
+{
+ mousestate->pos.col = state->mouse_col;
+ mousestate->pos.row = state->mouse_row;
+ mousestate->buttons = state->mouse_buttons;
+ mousestate->flags = state->mouse_flags;
+}
+
+void vterm_state_set_callbacks(VTermState *state, const VTermStateCallbacks *callbacks, void *user)
+{
+ if(callbacks) {
+ state->callbacks = callbacks;
+ state->cbdata = user;
+
+ if(state->callbacks && state->callbacks->initpen)
+ (*state->callbacks->initpen)(state->cbdata);
+ }
+ else {
+ state->callbacks = NULL;
+ state->cbdata = NULL;
+ }
+}
+
+void *vterm_state_get_cbdata(VTermState *state)
+{
+ return state->cbdata;
+}
+
+void vterm_state_set_unrecognised_fallbacks(VTermState *state, const VTermParserCallbacks *fallbacks, void *user)
+{
+ if(fallbacks) {
+ state->fallbacks = fallbacks;
+ state->fbdata = user;
+ }
+ else {
+ state->fallbacks = NULL;
+ state->fbdata = NULL;
+ }
+}
+
+void *vterm_state_get_unrecognised_fbdata(VTermState *state)
+{
+ return state->fbdata;
+}
+
+int vterm_state_set_termprop(VTermState *state, VTermProp prop, VTermValue *val)
+{
+ /* Only store the new value of the property if usercode said it was happy.
+ * This is especially important for altscreen switching */
+ if(state->callbacks && state->callbacks->settermprop)
+ if(!(*state->callbacks->settermprop)(prop, val, state->cbdata))
+ return 0;
+
+ switch(prop) {
+ case VTERM_PROP_TITLE:
+ case VTERM_PROP_ICONNAME:
+ case VTERM_PROP_CURSORCOLOR:
+ // we don't store these, just transparently pass through
+ return 1;
+ case VTERM_PROP_CURSORVISIBLE:
+ state->mode.cursor_visible = val->boolean;
+ return 1;
+ case VTERM_PROP_CURSORBLINK:
+ state->mode.cursor_blink = val->boolean;
+ return 1;
+ case VTERM_PROP_CURSORSHAPE:
+ state->mode.cursor_shape = val->number;
+ return 1;
+ case VTERM_PROP_REVERSE:
+ state->mode.screen = val->boolean;
+ return 1;
+ case VTERM_PROP_ALTSCREEN:
+ state->mode.alt_screen = val->boolean;
+ if(state->mode.alt_screen) {
+ VTermRect rect = {0, 0, 0, 0};
+ rect.end_row = state->rows;
+ rect.end_col = state->cols;
+ erase(state, rect, 0);
+ }
+ return 1;
+ case VTERM_PROP_MOUSE:
+ state->mouse_flags = 0;
+ if(val->number)
+ state->mouse_flags |= MOUSE_WANT_CLICK;
+ if(val->number == VTERM_PROP_MOUSE_DRAG)
+ state->mouse_flags |= MOUSE_WANT_DRAG;
+ if(val->number == VTERM_PROP_MOUSE_MOVE)
+ state->mouse_flags |= MOUSE_WANT_MOVE;
+ return 1;
+
+ case VTERM_N_PROPS:
+ return 0;
+ }
+
+ return 0;
+}
+
+void vterm_state_focus_in(VTermState *state)
+{
+ if(state->mode.report_focus)
+ vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "I");
+}
+
+void vterm_state_focus_out(VTermState *state)
+{
+ if(state->mode.report_focus)
+ vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "O");
+}
+
+const VTermLineInfo *vterm_state_get_lineinfo(const VTermState *state, int row)
+{
+ return state->lineinfo + row;
+}
diff --git a/src/libvterm/src/termscreen.c b/src/libvterm/src/termscreen.c
new file mode 100644
index 0000000..0cd31ce
--- /dev/null
+++ b/src/libvterm/src/termscreen.c
@@ -0,0 +1,939 @@
+#include "vterm_internal.h"
+
+/* vim: set sw=2 : */
+#include <stdio.h>
+#include <string.h>
+
+#include "rect.h"
+#include "utf8.h"
+
+#define UNICODE_SPACE 0x20
+#define UNICODE_LINEFEED 0x0a
+
+/* State of the pen at some moment in time, also used in a cell */
+typedef struct
+{
+ /* After the bitfield */
+ VTermColor fg, bg;
+
+ unsigned int bold : 1;
+ unsigned int underline : 2;
+ unsigned int italic : 1;
+ unsigned int blink : 1;
+ unsigned int reverse : 1;
+ unsigned int strike : 1;
+ unsigned int font : 4; /* 0 to 9 */
+
+ /* Extra state storage that isn't strictly pen-related */
+ unsigned int protected_cell : 1;
+ unsigned int dwl : 1; /* on a DECDWL or DECDHL line */
+ unsigned int dhl : 2; /* on a DECDHL line (1=top 2=bottom) */
+} ScreenPen;
+
+/* Internal representation of a screen cell */
+typedef struct
+{
+ uint32_t chars[VTERM_MAX_CHARS_PER_CELL];
+ ScreenPen pen;
+} ScreenCell;
+
+static int vterm_screen_set_cell(VTermScreen *screen, VTermPos pos, const VTermScreenCell *cell);
+
+struct VTermScreen
+{
+ VTerm *vt;
+ VTermState *state;
+
+ const VTermScreenCallbacks *callbacks;
+ void *cbdata;
+
+ VTermDamageSize damage_merge;
+ /* start_row == -1 => no damage */
+ VTermRect damaged;
+ VTermRect pending_scrollrect;
+ int pending_scroll_downward, pending_scroll_rightward;
+
+ int rows;
+ int cols;
+ int global_reverse;
+
+ /* Primary and Altscreen. buffers[1] is lazily allocated as needed */
+ ScreenCell *buffers[2];
+
+ /* buffer will == buffers[0] or buffers[1], depending on altscreen */
+ ScreenCell *buffer;
+
+ /* buffer for a single screen row used in scrollback storage callbacks */
+ VTermScreenCell *sb_buffer;
+
+ ScreenPen pen;
+};
+
+static ScreenCell *getcell(const VTermScreen *screen, int row, int col)
+{
+ if(row < 0 || row >= screen->rows)
+ return NULL;
+ if(col < 0 || col >= screen->cols)
+ return NULL;
+ return screen->buffer + (screen->cols * row) + col;
+}
+
+static ScreenCell *realloc_buffer(VTermScreen *screen, ScreenCell *buffer, int new_rows, int new_cols)
+{
+ ScreenCell *new_buffer = vterm_allocator_malloc(screen->vt, sizeof(ScreenCell) * new_rows * new_cols);
+ int row, col;
+
+ for(row = 0; row < new_rows; row++) {
+ for(col = 0; col < new_cols; col++) {
+ ScreenCell *new_cell = new_buffer + row*new_cols + col;
+
+ if(buffer && row < screen->rows && col < screen->cols)
+ *new_cell = buffer[row * screen->cols + col];
+ else {
+ new_cell->chars[0] = 0;
+ new_cell->pen = screen->pen;
+ }
+ }
+ }
+
+ vterm_allocator_free(screen->vt, buffer);
+
+ return new_buffer;
+}
+
+static void damagerect(VTermScreen *screen, VTermRect rect)
+{
+ VTermRect emit;
+
+ switch(screen->damage_merge) {
+ case VTERM_DAMAGE_CELL:
+ /* Always emit damage event */
+ emit = rect;
+ break;
+
+ case VTERM_DAMAGE_ROW:
+ /* Emit damage longer than one row. Try to merge with existing damage in
+ * the same row */
+ if(rect.end_row > rect.start_row + 1) {
+ // Bigger than 1 line - flush existing, emit this
+ vterm_screen_flush_damage(screen);
+ emit = rect;
+ }
+ else if(screen->damaged.start_row == -1) {
+ // None stored yet
+ screen->damaged = rect;
+ return;
+ }
+ else if(rect.start_row == screen->damaged.start_row) {
+ // Merge with the stored line
+ if(screen->damaged.start_col > rect.start_col)
+ screen->damaged.start_col = rect.start_col;
+ if(screen->damaged.end_col < rect.end_col)
+ screen->damaged.end_col = rect.end_col;
+ return;
+ }
+ else {
+ // Emit the currently stored line, store a new one
+ emit = screen->damaged;
+ screen->damaged = rect;
+ }
+ break;
+
+ case VTERM_DAMAGE_SCREEN:
+ case VTERM_DAMAGE_SCROLL:
+ /* Never emit damage event */
+ if(screen->damaged.start_row == -1)
+ screen->damaged = rect;
+ else {
+ rect_expand(&screen->damaged, &rect);
+ }
+ return;
+
+ default:
+ DEBUG_LOG1("TODO: Maybe merge damage for level %d\n", screen->damage_merge);
+ return;
+ }
+
+ if(screen->callbacks && screen->callbacks->damage)
+ (*screen->callbacks->damage)(emit, screen->cbdata);
+}
+
+static void damagescreen(VTermScreen *screen)
+{
+ VTermRect rect = {0,0,0,0};
+ rect.end_row = screen->rows;
+ rect.end_col = screen->cols;
+
+ damagerect(screen, rect);
+}
+
+static int putglyph(VTermGlyphInfo *info, VTermPos pos, void *user)
+{
+ int i;
+ int col;
+ VTermRect rect;
+
+ VTermScreen *screen = user;
+ ScreenCell *cell = getcell(screen, pos.row, pos.col);
+
+ if(!cell)
+ return 0;
+
+ for(i = 0; i < VTERM_MAX_CHARS_PER_CELL && info->chars[i]; i++) {
+ cell->chars[i] = info->chars[i];
+ cell->pen = screen->pen;
+ }
+ if(i < VTERM_MAX_CHARS_PER_CELL)
+ cell->chars[i] = 0;
+
+ for(col = 1; col < info->width; col++)
+ getcell(screen, pos.row, pos.col + col)->chars[0] = (uint32_t)-1;
+
+ rect.start_row = pos.row;
+ rect.end_row = pos.row+1;
+ rect.start_col = pos.col;
+ rect.end_col = pos.col+info->width;
+
+ cell->pen.protected_cell = info->protected_cell;
+ cell->pen.dwl = info->dwl;
+ cell->pen.dhl = info->dhl;
+
+ damagerect(screen, rect);
+
+ return 1;
+}
+
+static int moverect_internal(VTermRect dest, VTermRect src, void *user)
+{
+ VTermScreen *screen = user;
+
+ if(screen->callbacks && screen->callbacks->sb_pushline &&
+ dest.start_row == 0 && dest.start_col == 0 && // starts top-left corner
+ dest.end_col == screen->cols && // full width
+ screen->buffer == screen->buffers[0]) { // not altscreen
+ VTermPos pos;
+ for(pos.row = 0; pos.row < src.start_row; pos.row++) {
+ for(pos.col = 0; pos.col < screen->cols; pos.col++)
+ (void)vterm_screen_get_cell(screen, pos, screen->sb_buffer + pos.col);
+
+ (screen->callbacks->sb_pushline)(screen->cols, screen->sb_buffer, screen->cbdata);
+ }
+ }
+
+ {
+ int cols = src.end_col - src.start_col;
+ int downward = src.start_row - dest.start_row;
+ int init_row, test_row, inc_row;
+ int row;
+
+ if(downward < 0) {
+ init_row = dest.end_row - 1;
+ test_row = dest.start_row - 1;
+ inc_row = -1;
+ }
+ else {
+ init_row = dest.start_row;
+ test_row = dest.end_row;
+ inc_row = +1;
+ }
+
+ for(row = init_row; row != test_row; row += inc_row)
+ memmove(getcell(screen, row, dest.start_col),
+ getcell(screen, row + downward, src.start_col),
+ cols * sizeof(ScreenCell));
+ }
+
+ return 1;
+}
+
+static int moverect_user(VTermRect dest, VTermRect src, void *user)
+{
+ VTermScreen *screen = user;
+
+ if(screen->callbacks && screen->callbacks->moverect) {
+ if(screen->damage_merge != VTERM_DAMAGE_SCROLL)
+ // Avoid an infinite loop
+ vterm_screen_flush_damage(screen);
+
+ if((*screen->callbacks->moverect)(dest, src, screen->cbdata))
+ return 1;
+ }
+
+ damagerect(screen, dest);
+
+ return 1;
+}
+
+static int erase_internal(VTermRect rect, int selective, void *user)
+{
+ VTermScreen *screen = user;
+ int row, col;
+
+ for(row = rect.start_row; row < screen->state->rows && row < rect.end_row; row++) {
+ const VTermLineInfo *info = vterm_state_get_lineinfo(screen->state, row);
+
+ for(col = rect.start_col; col < rect.end_col; col++) {
+ ScreenCell *cell = getcell(screen, row, col);
+
+ if(selective && cell->pen.protected_cell)
+ continue;
+
+ cell->chars[0] = 0;
+ cell->pen = screen->pen;
+ cell->pen.dwl = info->doublewidth;
+ cell->pen.dhl = info->doubleheight;
+ }
+ }
+
+ return 1;
+}
+
+static int erase_user(VTermRect rect, int selective UNUSED, void *user)
+{
+ VTermScreen *screen = user;
+
+ damagerect(screen, rect);
+
+ return 1;
+}
+
+static int erase(VTermRect rect, int selective, void *user)
+{
+ erase_internal(rect, selective, user);
+ return erase_user(rect, 0, user);
+}
+
+static int scrollrect(VTermRect rect, int downward, int rightward, void *user)
+{
+ VTermScreen *screen = user;
+
+ if(screen->damage_merge != VTERM_DAMAGE_SCROLL) {
+ vterm_scroll_rect(rect, downward, rightward,
+ moverect_internal, erase_internal, screen);
+
+ vterm_screen_flush_damage(screen);
+
+ vterm_scroll_rect(rect, downward, rightward,
+ moverect_user, erase_user, screen);
+
+ return 1;
+ }
+
+ if(screen->damaged.start_row != -1 &&
+ !rect_intersects(&rect, &screen->damaged)) {
+ vterm_screen_flush_damage(screen);
+ }
+
+ if(screen->pending_scrollrect.start_row == -1) {
+ screen->pending_scrollrect = rect;
+ screen->pending_scroll_downward = downward;
+ screen->pending_scroll_rightward = rightward;
+ }
+ else if(rect_equal(&screen->pending_scrollrect, &rect) &&
+ ((screen->pending_scroll_downward == 0 && downward == 0) ||
+ (screen->pending_scroll_rightward == 0 && rightward == 0))) {
+ screen->pending_scroll_downward += downward;
+ screen->pending_scroll_rightward += rightward;
+ }
+ else {
+ vterm_screen_flush_damage(screen);
+
+ screen->pending_scrollrect = rect;
+ screen->pending_scroll_downward = downward;
+ screen->pending_scroll_rightward = rightward;
+ }
+
+ vterm_scroll_rect(rect, downward, rightward,
+ moverect_internal, erase_internal, screen);
+
+ if(screen->damaged.start_row == -1)
+ return 1;
+
+ if(rect_contains(&rect, &screen->damaged)) {
+ /* Scroll region entirely contains the damage; just move it */
+ vterm_rect_move(&screen->damaged, -downward, -rightward);
+ rect_clip(&screen->damaged, &rect);
+ }
+ /* There are a number of possible cases here, but lets restrict this to only
+ * the common case where we might actually gain some performance by
+ * optimising it. Namely, a vertical scroll that neatly cuts the damage
+ * region in half.
+ */
+ else if(rect.start_col <= screen->damaged.start_col &&
+ rect.end_col >= screen->damaged.end_col &&
+ rightward == 0) {
+ if(screen->damaged.start_row >= rect.start_row &&
+ screen->damaged.start_row < rect.end_row) {
+ screen->damaged.start_row -= downward;
+ if(screen->damaged.start_row < rect.start_row)
+ screen->damaged.start_row = rect.start_row;
+ if(screen->damaged.start_row > rect.end_row)
+ screen->damaged.start_row = rect.end_row;
+ }
+ if(screen->damaged.end_row >= rect.start_row &&
+ screen->damaged.end_row < rect.end_row) {
+ screen->damaged.end_row -= downward;
+ if(screen->damaged.end_row < rect.start_row)
+ screen->damaged.end_row = rect.start_row;
+ if(screen->damaged.end_row > rect.end_row)
+ screen->damaged.end_row = rect.end_row;
+ }
+ }
+ else {
+ DEBUG_LOG2("TODO: Just flush and redo damaged=" STRFrect " rect=" STRFrect "\n",
+ ARGSrect(screen->damaged), ARGSrect(rect));
+ }
+
+ return 1;
+}
+
+static int movecursor(VTermPos pos, VTermPos oldpos, int visible, void *user)
+{
+ VTermScreen *screen = user;
+
+ if(screen->callbacks && screen->callbacks->movecursor)
+ return (*screen->callbacks->movecursor)(pos, oldpos, visible, screen->cbdata);
+
+ return 0;
+}
+
+static int setpenattr(VTermAttr attr, VTermValue *val, void *user)
+{
+ VTermScreen *screen = user;
+
+ switch(attr) {
+ case VTERM_ATTR_BOLD:
+ screen->pen.bold = val->boolean;
+ return 1;
+ case VTERM_ATTR_UNDERLINE:
+ screen->pen.underline = val->number;
+ return 1;
+ case VTERM_ATTR_ITALIC:
+ screen->pen.italic = val->boolean;
+ return 1;
+ case VTERM_ATTR_BLINK:
+ screen->pen.blink = val->boolean;
+ return 1;
+ case VTERM_ATTR_REVERSE:
+ screen->pen.reverse = val->boolean;
+ return 1;
+ case VTERM_ATTR_STRIKE:
+ screen->pen.strike = val->boolean;
+ return 1;
+ case VTERM_ATTR_FONT:
+ screen->pen.font = val->number;
+ return 1;
+ case VTERM_ATTR_FOREGROUND:
+ screen->pen.fg = val->color;
+ return 1;
+ case VTERM_ATTR_BACKGROUND:
+ screen->pen.bg = val->color;
+ return 1;
+
+ case VTERM_N_ATTRS:
+ return 0;
+ }
+
+ return 0;
+}
+
+static int settermprop(VTermProp prop, VTermValue *val, void *user)
+{
+ VTermScreen *screen = user;
+
+ switch(prop) {
+ case VTERM_PROP_ALTSCREEN:
+ if(val->boolean && !screen->buffers[1])
+ return 0;
+
+ screen->buffer = val->boolean ? screen->buffers[1] : screen->buffers[0];
+ /* only send a damage event on disable; because during enable there's an
+ * erase that sends a damage anyway
+ */
+ if(!val->boolean)
+ damagescreen(screen);
+ break;
+ case VTERM_PROP_REVERSE:
+ screen->global_reverse = val->boolean;
+ damagescreen(screen);
+ break;
+ default:
+ ; /* ignore */
+ }
+
+ if(screen->callbacks && screen->callbacks->settermprop)
+ return (*screen->callbacks->settermprop)(prop, val, screen->cbdata);
+
+ return 1;
+}
+
+static int bell(void *user)
+{
+ VTermScreen *screen = user;
+
+ if(screen->callbacks && screen->callbacks->bell)
+ return (*screen->callbacks->bell)(screen->cbdata);
+
+ return 0;
+}
+
+static int resize(int new_rows, int new_cols, VTermPos *delta, void *user)
+{
+ VTermScreen *screen = user;
+
+ int is_altscreen = (screen->buffers[1] && screen->buffer == screen->buffers[1]);
+
+ int old_rows = screen->rows;
+ int old_cols = screen->cols;
+ int first_blank_row;
+
+ if(!is_altscreen && new_rows < old_rows) {
+ // Fewer rows - determine if we're going to scroll at all, and if so, push
+ // those lines to scrollback
+ VTermPos pos = { 0, 0 };
+ VTermPos cursor = screen->state->pos;
+ // Find the first blank row after the cursor.
+ for(pos.row = old_rows - 1; pos.row >= new_rows; pos.row--)
+ if(!vterm_screen_is_eol(screen, pos) || cursor.row == pos.row)
+ break;
+
+ first_blank_row = pos.row + 1;
+ if(first_blank_row > new_rows) {
+ VTermRect rect = {0,0,0,0};
+ rect.end_row = old_rows;
+ rect.end_col = old_cols;
+ scrollrect(rect, first_blank_row - new_rows, 0, user);
+ vterm_screen_flush_damage(screen);
+
+ delta->row -= first_blank_row - new_rows;
+ }
+ }
+
+ screen->buffers[0] = realloc_buffer(screen, screen->buffers[0], new_rows, new_cols);
+ if(screen->buffers[1])
+ screen->buffers[1] = realloc_buffer(screen, screen->buffers[1], new_rows, new_cols);
+
+ screen->buffer = is_altscreen ? screen->buffers[1] : screen->buffers[0];
+
+ screen->rows = new_rows;
+ screen->cols = new_cols;
+
+ vterm_allocator_free(screen->vt, screen->sb_buffer);
+
+ screen->sb_buffer = vterm_allocator_malloc(screen->vt, sizeof(VTermScreenCell) * new_cols);
+
+ if(new_cols > old_cols) {
+ VTermRect rect;
+ rect.start_row = 0;
+ rect.end_row = old_rows;
+ rect.start_col = old_cols;
+ rect.end_col = new_cols;
+ damagerect(screen, rect);
+ }
+
+ if(new_rows > old_rows) {
+ if(!is_altscreen && screen->callbacks && screen->callbacks->sb_popline) {
+ int rows = new_rows - old_rows;
+ while(rows) {
+ VTermRect rect = {0,0,0,0};
+ VTermPos pos = { 0, 0 };
+ if(!(screen->callbacks->sb_popline(screen->cols, screen->sb_buffer, screen->cbdata)))
+ break;
+
+ rect.end_row = screen->rows;
+ rect.end_col = screen->cols;
+ scrollrect(rect, -1, 0, user);
+
+ for(pos.col = 0; pos.col < screen->cols; pos.col += screen->sb_buffer[pos.col].width)
+ vterm_screen_set_cell(screen, pos, screen->sb_buffer + pos.col);
+
+ rect.end_row = 1;
+ damagerect(screen, rect);
+
+ vterm_screen_flush_damage(screen);
+
+ rows--;
+ delta->row++;
+ }
+ }
+
+ {
+ VTermRect rect;
+ rect.start_row = old_rows;
+ rect.end_row = new_rows;
+ rect.start_col = 0;
+ rect.end_col = new_cols;
+ damagerect(screen, rect);
+ }
+ }
+
+ if(screen->callbacks && screen->callbacks->resize)
+ return (*screen->callbacks->resize)(new_rows, new_cols, screen->cbdata);
+
+ return 1;
+}
+
+static int setlineinfo(int row, const VTermLineInfo *newinfo, const VTermLineInfo *oldinfo, void *user)
+{
+ VTermScreen *screen = user;
+ int col;
+ VTermRect rect;
+
+ if(newinfo->doublewidth != oldinfo->doublewidth ||
+ newinfo->doubleheight != oldinfo->doubleheight) {
+ for(col = 0; col < screen->cols; col++) {
+ ScreenCell *cell = getcell(screen, row, col);
+ cell->pen.dwl = newinfo->doublewidth;
+ cell->pen.dhl = newinfo->doubleheight;
+ }
+
+ rect.start_row = row;
+ rect.end_row = row + 1;
+ rect.start_col = 0;
+ rect.end_col = newinfo->doublewidth ? screen->cols / 2 : screen->cols;
+ damagerect(screen, rect);
+
+ if(newinfo->doublewidth) {
+ rect.start_col = screen->cols / 2;
+ rect.end_col = screen->cols;
+
+ erase_internal(rect, 0, user);
+ }
+ }
+
+ return 1;
+}
+
+static VTermStateCallbacks state_cbs = {
+ &putglyph, /* putglyph */
+ &movecursor, /* movecursor */
+ &scrollrect, /* scrollrect */
+ NULL, /* moverect */
+ &erase, /* erase */
+ NULL, /* initpen */
+ &setpenattr, /* setpenattr */
+ &settermprop, /* settermprop */
+ &bell, /* bell */
+ &resize, /* resize */
+ &setlineinfo /* setlineinfo */
+};
+
+/*
+ * Allocate a new screen and return it.
+ * Return NULL when out of memory.
+ */
+static VTermScreen *screen_new(VTerm *vt)
+{
+ VTermState *state = vterm_obtain_state(vt);
+ VTermScreen *screen;
+ int rows, cols;
+
+ if (state == NULL)
+ return NULL;
+ screen = vterm_allocator_malloc(vt, sizeof(VTermScreen));
+ if (screen == NULL)
+ return NULL;
+
+ vterm_get_size(vt, &rows, &cols);
+
+ screen->vt = vt;
+ screen->state = state;
+
+ screen->damage_merge = VTERM_DAMAGE_CELL;
+ screen->damaged.start_row = -1;
+ screen->pending_scrollrect.start_row = -1;
+
+ screen->rows = rows;
+ screen->cols = cols;
+
+ screen->callbacks = NULL;
+ screen->cbdata = NULL;
+
+ screen->buffers[0] = realloc_buffer(screen, NULL, rows, cols);
+ screen->buffer = screen->buffers[0];
+ screen->sb_buffer = vterm_allocator_malloc(screen->vt, sizeof(VTermScreenCell) * cols);
+ if (screen->buffer == NULL || screen->sb_buffer == NULL)
+ {
+ vterm_screen_free(screen);
+ return NULL;
+ }
+
+ vterm_state_set_callbacks(screen->state, &state_cbs, screen);
+
+ return screen;
+}
+
+INTERNAL void vterm_screen_free(VTermScreen *screen)
+{
+ vterm_allocator_free(screen->vt, screen->buffers[0]);
+ vterm_allocator_free(screen->vt, screen->buffers[1]);
+ vterm_allocator_free(screen->vt, screen->sb_buffer);
+ vterm_allocator_free(screen->vt, screen);
+}
+
+void vterm_screen_reset(VTermScreen *screen, int hard)
+{
+ screen->damaged.start_row = -1;
+ screen->pending_scrollrect.start_row = -1;
+ vterm_state_reset(screen->state, hard);
+ vterm_screen_flush_damage(screen);
+}
+
+static size_t _get_chars(const VTermScreen *screen, const int utf8, void *buffer, size_t len, const VTermRect rect)
+{
+ size_t outpos = 0;
+ int padding = 0;
+ int row, col;
+
+#define PUT(c) \
+ if(utf8) { \
+ size_t thislen = utf8_seqlen(c); \
+ if(buffer && outpos + thislen <= len) \
+ outpos += fill_utf8((c), (char *)buffer + outpos); \
+ else \
+ outpos += thislen; \
+ } \
+ else { \
+ if(buffer && outpos + 1 <= len) \
+ ((uint32_t*)buffer)[outpos++] = (c); \
+ else \
+ outpos++; \
+ }
+
+ for(row = rect.start_row; row < rect.end_row; row++) {
+ for(col = rect.start_col; col < rect.end_col; col++) {
+ ScreenCell *cell = getcell(screen, row, col);
+ int i;
+
+ if(cell->chars[0] == 0)
+ // Erased cell, might need a space
+ padding++;
+ else if(cell->chars[0] == (uint32_t)-1)
+ // Gap behind a double-width char, do nothing
+ ;
+ else {
+ while(padding) {
+ PUT(UNICODE_SPACE);
+ padding--;
+ }
+ for(i = 0; i < VTERM_MAX_CHARS_PER_CELL && cell->chars[i]; i++) {
+ PUT(cell->chars[i]);
+ }
+ }
+ }
+
+ if(row < rect.end_row - 1) {
+ PUT(UNICODE_LINEFEED);
+ padding = 0;
+ }
+ }
+
+ return outpos;
+}
+
+size_t vterm_screen_get_chars(const VTermScreen *screen, uint32_t *chars, size_t len, const VTermRect rect)
+{
+ return _get_chars(screen, 0, chars, len, rect);
+}
+
+size_t vterm_screen_get_text(const VTermScreen *screen, char *str, size_t len, const VTermRect rect)
+{
+ return _get_chars(screen, 1, str, len, rect);
+}
+
+/* Copy internal to external representation of a screen cell */
+int vterm_screen_get_cell(const VTermScreen *screen, VTermPos pos, VTermScreenCell *cell)
+{
+ ScreenCell *intcell = getcell(screen, pos.row, pos.col);
+ int i;
+
+ if(!intcell)
+ return 0;
+
+ for(i = 0; ; i++) {
+ cell->chars[i] = intcell->chars[i];
+ if(!intcell->chars[i])
+ break;
+ }
+
+ cell->attrs.bold = intcell->pen.bold;
+ cell->attrs.underline = intcell->pen.underline;
+ cell->attrs.italic = intcell->pen.italic;
+ cell->attrs.blink = intcell->pen.blink;
+ cell->attrs.reverse = intcell->pen.reverse ^ screen->global_reverse;
+ cell->attrs.strike = intcell->pen.strike;
+ cell->attrs.font = intcell->pen.font;
+
+ cell->attrs.dwl = intcell->pen.dwl;
+ cell->attrs.dhl = intcell->pen.dhl;
+
+ cell->fg = intcell->pen.fg;
+ cell->bg = intcell->pen.bg;
+
+ if(pos.col < (screen->cols - 1) &&
+ getcell(screen, pos.row, pos.col + 1)->chars[0] == (uint32_t)-1)
+ cell->width = 2;
+ else
+ cell->width = 1;
+
+ return 1;
+}
+
+/* Copy external to internal representation of a screen cell */
+/* static because it's only used internally for sb_popline during resize */
+static int vterm_screen_set_cell(VTermScreen *screen, VTermPos pos, const VTermScreenCell *cell)
+{
+ ScreenCell *intcell = getcell(screen, pos.row, pos.col);
+ int i;
+
+ if(!intcell)
+ return 0;
+
+ for(i = 0; ; i++) {
+ intcell->chars[i] = cell->chars[i];
+ if(!cell->chars[i])
+ break;
+ }
+
+ intcell->pen.bold = cell->attrs.bold;
+ intcell->pen.underline = cell->attrs.underline;
+ intcell->pen.italic = cell->attrs.italic;
+ intcell->pen.blink = cell->attrs.blink;
+ intcell->pen.reverse = cell->attrs.reverse ^ screen->global_reverse;
+ intcell->pen.strike = cell->attrs.strike;
+ intcell->pen.font = cell->attrs.font;
+
+ intcell->pen.fg = cell->fg;
+ intcell->pen.bg = cell->bg;
+
+ if(cell->width == 2)
+ getcell(screen, pos.row, pos.col + 1)->chars[0] = (uint32_t)-1;
+
+ return 1;
+}
+
+int vterm_screen_is_eol(const VTermScreen *screen, VTermPos pos)
+{
+ /* This cell is EOL if this and every cell to the right is black */
+ for(; pos.col < screen->cols; pos.col++) {
+ ScreenCell *cell = getcell(screen, pos.row, pos.col);
+ if(cell->chars[0] != 0)
+ return 0;
+ }
+
+ return 1;
+}
+
+VTermScreen *vterm_obtain_screen(VTerm *vt)
+{
+ if(!vt->screen)
+ vt->screen = screen_new(vt);
+ return vt->screen;
+}
+
+void vterm_screen_enable_altscreen(VTermScreen *screen, int altscreen)
+{
+
+ if(!screen->buffers[1] && altscreen) {
+ int rows, cols;
+ vterm_get_size(screen->vt, &rows, &cols);
+
+ screen->buffers[1] = realloc_buffer(screen, NULL, rows, cols);
+ }
+}
+
+void vterm_screen_set_callbacks(VTermScreen *screen, const VTermScreenCallbacks *callbacks, void *user)
+{
+ screen->callbacks = callbacks;
+ screen->cbdata = user;
+}
+
+void *vterm_screen_get_cbdata(VTermScreen *screen)
+{
+ return screen->cbdata;
+}
+
+void vterm_screen_set_unrecognised_fallbacks(VTermScreen *screen, const VTermParserCallbacks *fallbacks, void *user)
+{
+ vterm_state_set_unrecognised_fallbacks(screen->state, fallbacks, user);
+}
+
+void *vterm_screen_get_unrecognised_fbdata(VTermScreen *screen)
+{
+ return vterm_state_get_unrecognised_fbdata(screen->state);
+}
+
+void vterm_screen_flush_damage(VTermScreen *screen)
+{
+ if(screen->pending_scrollrect.start_row != -1) {
+ vterm_scroll_rect(screen->pending_scrollrect, screen->pending_scroll_downward, screen->pending_scroll_rightward,
+ moverect_user, erase_user, screen);
+
+ screen->pending_scrollrect.start_row = -1;
+ }
+
+ if(screen->damaged.start_row != -1) {
+ if(screen->callbacks && screen->callbacks->damage)
+ (*screen->callbacks->damage)(screen->damaged, screen->cbdata);
+
+ screen->damaged.start_row = -1;
+ }
+}
+
+void vterm_screen_set_damage_merge(VTermScreen *screen, VTermDamageSize size)
+{
+ vterm_screen_flush_damage(screen);
+ screen->damage_merge = size;
+}
+
+static int attrs_differ(VTermAttrMask attrs, ScreenCell *a, ScreenCell *b)
+{
+ if((attrs & VTERM_ATTR_BOLD_MASK) && (a->pen.bold != b->pen.bold))
+ return 1;
+ if((attrs & VTERM_ATTR_UNDERLINE_MASK) && (a->pen.underline != b->pen.underline))
+ return 1;
+ if((attrs & VTERM_ATTR_ITALIC_MASK) && (a->pen.italic != b->pen.italic))
+ return 1;
+ if((attrs & VTERM_ATTR_BLINK_MASK) && (a->pen.blink != b->pen.blink))
+ return 1;
+ if((attrs & VTERM_ATTR_REVERSE_MASK) && (a->pen.reverse != b->pen.reverse))
+ return 1;
+ if((attrs & VTERM_ATTR_STRIKE_MASK) && (a->pen.strike != b->pen.strike))
+ return 1;
+ if((attrs & VTERM_ATTR_FONT_MASK) && (a->pen.font != b->pen.font))
+ return 1;
+ if((attrs & VTERM_ATTR_FOREGROUND_MASK) && !vterm_color_equal(a->pen.fg, b->pen.fg))
+ return 1;
+ if((attrs & VTERM_ATTR_BACKGROUND_MASK) && !vterm_color_equal(a->pen.bg, b->pen.bg))
+ return 1;
+
+ return 0;
+}
+
+int vterm_screen_get_attrs_extent(const VTermScreen *screen, VTermRect *extent, VTermPos pos, VTermAttrMask attrs)
+{
+ int col;
+
+ ScreenCell *target = getcell(screen, pos.row, pos.col);
+
+ // TODO: bounds check
+ extent->start_row = pos.row;
+ extent->end_row = pos.row + 1;
+
+ if(extent->start_col < 0)
+ extent->start_col = 0;
+ if(extent->end_col < 0)
+ extent->end_col = screen->cols;
+
+ for(col = pos.col - 1; col >= extent->start_col; col--)
+ if(attrs_differ(attrs, target, getcell(screen, pos.row, col)))
+ break;
+ extent->start_col = col + 1;
+
+ for(col = pos.col + 1; col < extent->end_col; col++)
+ if(attrs_differ(attrs, target, getcell(screen, pos.row, col)))
+ break;
+ extent->end_col = col - 1;
+
+ return 1;
+}
diff --git a/src/libvterm/src/unicode.c b/src/libvterm/src/unicode.c
new file mode 100644
index 0000000..48d4a85
--- /dev/null
+++ b/src/libvterm/src/unicode.c
@@ -0,0 +1,349 @@
+#include "vterm_internal.h"
+
+// ### The following from http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c
+// With modifications:
+// made functions static
+// moved 'combining' table to file scope, so other functions can see it
+// ###################################################################
+
+/*
+ * This is an implementation of wcwidth() and wcswidth() (defined in
+ * IEEE Std 1002.1-2001) for Unicode.
+ *
+ * http://www.opengroup.org/onlinepubs/007904975/functions/wcwidth.html
+ * http://www.opengroup.org/onlinepubs/007904975/functions/wcswidth.html
+ *
+ * In fixed-width output devices, Latin characters all occupy a single
+ * "cell" position of equal width, whereas ideographic CJK characters
+ * occupy two such cells. Interoperability between terminal-line
+ * applications and (teletype-style) character terminals using the
+ * UTF-8 encoding requires agreement on which character should advance
+ * the cursor by how many cell positions. No established formal
+ * standards exist at present on which Unicode character shall occupy
+ * how many cell positions on character terminals. These routines are
+ * a first attempt of defining such behavior based on simple rules
+ * applied to data provided by the Unicode Consortium.
+ *
+ * For some graphical characters, the Unicode standard explicitly
+ * defines a character-cell width via the definition of the East Asian
+ * FullWidth (F), Wide (W), Half-width (H), and Narrow (Na) classes.
+ * In all these cases, there is no ambiguity about which width a
+ * terminal shall use. For characters in the East Asian Ambiguous (A)
+ * class, the width choice depends purely on a preference of backward
+ * compatibility with either historic CJK or Western practice.
+ * Choosing single-width for these characters is easy to justify as
+ * the appropriate long-term solution, as the CJK practice of
+ * displaying these characters as double-width comes from historic
+ * implementation simplicity (8-bit encoded characters were displayed
+ * single-width and 16-bit ones double-width, even for Greek,
+ * Cyrillic, etc.) and not any typographic considerations.
+ *
+ * Much less clear is the choice of width for the Not East Asian
+ * (Neutral) class. Existing practice does not dictate a width for any
+ * of these characters. It would nevertheless make sense
+ * typographically to allocate two character cells to characters such
+ * as for instance EM SPACE or VOLUME INTEGRAL, which cannot be
+ * represented adequately with a single-width glyph. The following
+ * routines at present merely assign a single-cell width to all
+ * neutral characters, in the interest of simplicity. This is not
+ * entirely satisfactory and should be reconsidered before
+ * establishing a formal standard in this area. At the moment, the
+ * decision which Not East Asian (Neutral) characters should be
+ * represented by double-width glyphs cannot yet be answered by
+ * applying a simple rule from the Unicode database content. Setting
+ * up a proper standard for the behavior of UTF-8 character terminals
+ * will require a careful analysis not only of each Unicode character,
+ * but also of each presentation form, something the author of these
+ * routines has avoided to do so far.
+ *
+ * http://www.unicode.org/unicode/reports/tr11/
+ *
+ * Markus Kuhn -- 2007-05-26 (Unicode 5.0)
+ *
+ * Permission to use, copy, modify, and distribute this software
+ * for any purpose and without fee is hereby granted. The author
+ * disclaims all warranties with regard to this software.
+ *
+ * Latest version: http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c
+ */
+
+#if !defined(IS_COMBINING_FUNCTION) || !defined(WCWIDTH_FUNCTION)
+struct interval {
+ int first;
+ int last;
+};
+
+/* sorted list of non-overlapping intervals of non-spacing characters */
+/* generated by "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c" */
+static const struct interval combining[] = {
+ { 0x0300, 0x036F }, { 0x0483, 0x0486 }, { 0x0488, 0x0489 },
+ { 0x0591, 0x05BD }, { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 },
+ { 0x05C4, 0x05C5 }, { 0x05C7, 0x05C7 }, { 0x0600, 0x0603 },
+ { 0x0610, 0x0615 }, { 0x064B, 0x065E }, { 0x0670, 0x0670 },
+ { 0x06D6, 0x06E4 }, { 0x06E7, 0x06E8 }, { 0x06EA, 0x06ED },
+ { 0x070F, 0x070F }, { 0x0711, 0x0711 }, { 0x0730, 0x074A },
+ { 0x07A6, 0x07B0 }, { 0x07EB, 0x07F3 }, { 0x0901, 0x0902 },
+ { 0x093C, 0x093C }, { 0x0941, 0x0948 }, { 0x094D, 0x094D },
+ { 0x0951, 0x0954 }, { 0x0962, 0x0963 }, { 0x0981, 0x0981 },
+ { 0x09BC, 0x09BC }, { 0x09C1, 0x09C4 }, { 0x09CD, 0x09CD },
+ { 0x09E2, 0x09E3 }, { 0x0A01, 0x0A02 }, { 0x0A3C, 0x0A3C },
+ { 0x0A41, 0x0A42 }, { 0x0A47, 0x0A48 }, { 0x0A4B, 0x0A4D },
+ { 0x0A70, 0x0A71 }, { 0x0A81, 0x0A82 }, { 0x0ABC, 0x0ABC },
+ { 0x0AC1, 0x0AC5 }, { 0x0AC7, 0x0AC8 }, { 0x0ACD, 0x0ACD },
+ { 0x0AE2, 0x0AE3 }, { 0x0B01, 0x0B01 }, { 0x0B3C, 0x0B3C },
+ { 0x0B3F, 0x0B3F }, { 0x0B41, 0x0B43 }, { 0x0B4D, 0x0B4D },
+ { 0x0B56, 0x0B56 }, { 0x0B82, 0x0B82 }, { 0x0BC0, 0x0BC0 },
+ { 0x0BCD, 0x0BCD }, { 0x0C3E, 0x0C40 }, { 0x0C46, 0x0C48 },
+ { 0x0C4A, 0x0C4D }, { 0x0C55, 0x0C56 }, { 0x0CBC, 0x0CBC },
+ { 0x0CBF, 0x0CBF }, { 0x0CC6, 0x0CC6 }, { 0x0CCC, 0x0CCD },
+ { 0x0CE2, 0x0CE3 }, { 0x0D41, 0x0D43 }, { 0x0D4D, 0x0D4D },
+ { 0x0DCA, 0x0DCA }, { 0x0DD2, 0x0DD4 }, { 0x0DD6, 0x0DD6 },
+ { 0x0E31, 0x0E31 }, { 0x0E34, 0x0E3A }, { 0x0E47, 0x0E4E },
+ { 0x0EB1, 0x0EB1 }, { 0x0EB4, 0x0EB9 }, { 0x0EBB, 0x0EBC },
+ { 0x0EC8, 0x0ECD }, { 0x0F18, 0x0F19 }, { 0x0F35, 0x0F35 },
+ { 0x0F37, 0x0F37 }, { 0x0F39, 0x0F39 }, { 0x0F71, 0x0F7E },
+ { 0x0F80, 0x0F84 }, { 0x0F86, 0x0F87 }, { 0x0F90, 0x0F97 },
+ { 0x0F99, 0x0FBC }, { 0x0FC6, 0x0FC6 }, { 0x102D, 0x1030 },
+ { 0x1032, 0x1032 }, { 0x1036, 0x1037 }, { 0x1039, 0x1039 },
+ { 0x1058, 0x1059 }, { 0x1160, 0x11FF }, { 0x135F, 0x135F },
+ { 0x1712, 0x1714 }, { 0x1732, 0x1734 }, { 0x1752, 0x1753 },
+ { 0x1772, 0x1773 }, { 0x17B4, 0x17B5 }, { 0x17B7, 0x17BD },
+ { 0x17C6, 0x17C6 }, { 0x17C9, 0x17D3 }, { 0x17DD, 0x17DD },
+ { 0x180B, 0x180D }, { 0x18A9, 0x18A9 }, { 0x1920, 0x1922 },
+ { 0x1927, 0x1928 }, { 0x1932, 0x1932 }, { 0x1939, 0x193B },
+ { 0x1A17, 0x1A18 }, { 0x1B00, 0x1B03 }, { 0x1B34, 0x1B34 },
+ { 0x1B36, 0x1B3A }, { 0x1B3C, 0x1B3C }, { 0x1B42, 0x1B42 },
+ { 0x1B6B, 0x1B73 }, { 0x1DC0, 0x1DCA }, { 0x1DFE, 0x1DFF },
+ { 0x200B, 0x200F }, { 0x202A, 0x202E }, { 0x2060, 0x2063 },
+ { 0x206A, 0x206F }, { 0x20D0, 0x20EF }, { 0x302A, 0x302F },
+ { 0x3099, 0x309A }, { 0xA806, 0xA806 }, { 0xA80B, 0xA80B },
+ { 0xA825, 0xA826 }, { 0xFB1E, 0xFB1E }, { 0xFE00, 0xFE0F },
+ { 0xFE20, 0xFE23 }, { 0xFEFF, 0xFEFF }, { 0xFFF9, 0xFFFB },
+ { 0x10A01, 0x10A03 }, { 0x10A05, 0x10A06 }, { 0x10A0C, 0x10A0F },
+ { 0x10A38, 0x10A3A }, { 0x10A3F, 0x10A3F }, { 0x1D167, 0x1D169 },
+ { 0x1D173, 0x1D182 }, { 0x1D185, 0x1D18B }, { 0x1D1AA, 0x1D1AD },
+ { 0x1D242, 0x1D244 }, { 0xE0001, 0xE0001 }, { 0xE0020, 0xE007F },
+ { 0xE0100, 0xE01EF }
+};
+
+/* auxiliary function for binary search in interval table */
+static int bisearch(uint32_t ucs, const struct interval *table, int max) {
+ int min = 0;
+ int mid;
+
+ if ((int)ucs < table[0].first || (int)ucs > table[max].last)
+ return 0;
+ while (max >= min) {
+ mid = (min + max) / 2;
+ if ((int)ucs > table[mid].last)
+ min = mid + 1;
+ else if ((int)ucs < table[mid].first)
+ max = mid - 1;
+ else
+ return 1;
+ }
+
+ return 0;
+}
+#endif
+
+
+/* The following two functions define the column width of an ISO 10646
+ * character as follows:
+ *
+ * - The null character (U+0000) has a column width of 0.
+ *
+ * - Other C0/C1 control characters and DEL will lead to a return
+ * value of -1.
+ *
+ * - Non-spacing and enclosing combining characters (general
+ * category code Mn or Me in the Unicode database) have a
+ * column width of 0.
+ *
+ * - SOFT HYPHEN (U+00AD) has a column width of 1.
+ *
+ * - Other format characters (general category code Cf in the Unicode
+ * database) and ZERO WIDTH SPACE (U+200B) have a column width of 0.
+ *
+ * - Hangul Jamo medial vowels and final consonants (U+1160-U+11FF)
+ * have a column width of 0.
+ *
+ * - Spacing characters in the East Asian Wide (W) or East Asian
+ * Full-width (F) category as defined in Unicode Technical
+ * Report #11 have a column width of 2.
+ *
+ * - All remaining characters (including all printable
+ * ISO 8859-1 and WGL4 characters, Unicode control characters,
+ * etc.) have a column width of 1.
+ *
+ * This implementation assumes that uint32_t characters are encoded
+ * in ISO 10646.
+ */
+
+#ifdef WCWIDTH_FUNCTION
+/* use a provided wcwidth() function */
+int WCWIDTH_FUNCTION(uint32_t ucs);
+#else
+# define WCWIDTH_FUNCTION mk_wcwidth
+
+static int mk_wcwidth(uint32_t ucs)
+{
+ /* test for 8-bit control characters */
+ if (ucs == 0)
+ return 0;
+ if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0))
+ return -1;
+
+ /* binary search in table of non-spacing characters */
+ if (bisearch(ucs, combining,
+ sizeof(combining) / sizeof(struct interval) - 1))
+ return 0;
+
+ /* if we arrive here, ucs is not a combining or C0/C1 control character */
+
+ return 1 +
+ (ucs >= 0x1100 &&
+ (ucs <= 0x115f || /* Hangul Jamo init. consonants */
+ ucs == 0x2329 || ucs == 0x232a ||
+ (ucs >= 0x2e80 && ucs <= 0xa4cf &&
+ ucs != 0x303f) || /* CJK ... Yi */
+ (ucs >= 0xac00 && ucs <= 0xd7a3) || /* Hangul Syllables */
+ (ucs >= 0xf900 && ucs <= 0xfaff) || /* CJK Compatibility Ideographs */
+ (ucs >= 0xfe10 && ucs <= 0xfe19) || /* Vertical forms */
+ (ucs >= 0xfe30 && ucs <= 0xfe6f) || /* CJK Compatibility Forms */
+ (ucs >= 0xff00 && ucs <= 0xff60) || /* Fullwidth Forms */
+ (ucs >= 0xffe0 && ucs <= 0xffe6) ||
+ (ucs >= 0x20000 && ucs <= 0x2fffd) ||
+ (ucs >= 0x30000 && ucs <= 0x3fffd)));
+}
+#endif
+
+#if 0 /* unused */
+static int mk_wcswidth(const uint32_t *pwcs, size_t n)
+{
+ int w, width = 0;
+
+ for (;*pwcs && n-- > 0; pwcs++)
+ if ((w = mk_wcwidth(*pwcs)) < 0)
+ return -1;
+ else
+ width += w;
+
+ return width;
+}
+
+
+/*
+ * The following functions are the same as mk_wcwidth() and
+ * mk_wcswidth(), except that spacing characters in the East Asian
+ * Ambiguous (A) category as defined in Unicode Technical Report #11
+ * have a column width of 2. This variant might be useful for users of
+ * CJK legacy encodings who want to migrate to UCS without changing
+ * the traditional terminal character-width behaviour. It is not
+ * otherwise recommended for general use.
+ */
+static int mk_wcwidth_cjk(uint32_t ucs)
+{
+ /* sorted list of non-overlapping intervals of East Asian Ambiguous
+ * characters, generated by "uniset +WIDTH-A -cat=Me -cat=Mn -cat=Cf c" */
+ static const struct interval ambiguous[] = {
+ { 0x00A1, 0x00A1 }, { 0x00A4, 0x00A4 }, { 0x00A7, 0x00A8 },
+ { 0x00AA, 0x00AA }, { 0x00AE, 0x00AE }, { 0x00B0, 0x00B4 },
+ { 0x00B6, 0x00BA }, { 0x00BC, 0x00BF }, { 0x00C6, 0x00C6 },
+ { 0x00D0, 0x00D0 }, { 0x00D7, 0x00D8 }, { 0x00DE, 0x00E1 },
+ { 0x00E6, 0x00E6 }, { 0x00E8, 0x00EA }, { 0x00EC, 0x00ED },
+ { 0x00F0, 0x00F0 }, { 0x00F2, 0x00F3 }, { 0x00F7, 0x00FA },
+ { 0x00FC, 0x00FC }, { 0x00FE, 0x00FE }, { 0x0101, 0x0101 },
+ { 0x0111, 0x0111 }, { 0x0113, 0x0113 }, { 0x011B, 0x011B },
+ { 0x0126, 0x0127 }, { 0x012B, 0x012B }, { 0x0131, 0x0133 },
+ { 0x0138, 0x0138 }, { 0x013F, 0x0142 }, { 0x0144, 0x0144 },
+ { 0x0148, 0x014B }, { 0x014D, 0x014D }, { 0x0152, 0x0153 },
+ { 0x0166, 0x0167 }, { 0x016B, 0x016B }, { 0x01CE, 0x01CE },
+ { 0x01D0, 0x01D0 }, { 0x01D2, 0x01D2 }, { 0x01D4, 0x01D4 },
+ { 0x01D6, 0x01D6 }, { 0x01D8, 0x01D8 }, { 0x01DA, 0x01DA },
+ { 0x01DC, 0x01DC }, { 0x0251, 0x0251 }, { 0x0261, 0x0261 },
+ { 0x02C4, 0x02C4 }, { 0x02C7, 0x02C7 }, { 0x02C9, 0x02CB },
+ { 0x02CD, 0x02CD }, { 0x02D0, 0x02D0 }, { 0x02D8, 0x02DB },
+ { 0x02DD, 0x02DD }, { 0x02DF, 0x02DF }, { 0x0391, 0x03A1 },
+ { 0x03A3, 0x03A9 }, { 0x03B1, 0x03C1 }, { 0x03C3, 0x03C9 },
+ { 0x0401, 0x0401 }, { 0x0410, 0x044F }, { 0x0451, 0x0451 },
+ { 0x2010, 0x2010 }, { 0x2013, 0x2016 }, { 0x2018, 0x2019 },
+ { 0x201C, 0x201D }, { 0x2020, 0x2022 }, { 0x2024, 0x2027 },
+ { 0x2030, 0x2030 }, { 0x2032, 0x2033 }, { 0x2035, 0x2035 },
+ { 0x203B, 0x203B }, { 0x203E, 0x203E }, { 0x2074, 0x2074 },
+ { 0x207F, 0x207F }, { 0x2081, 0x2084 }, { 0x20AC, 0x20AC },
+ { 0x2103, 0x2103 }, { 0x2105, 0x2105 }, { 0x2109, 0x2109 },
+ { 0x2113, 0x2113 }, { 0x2116, 0x2116 }, { 0x2121, 0x2122 },
+ { 0x2126, 0x2126 }, { 0x212B, 0x212B }, { 0x2153, 0x2154 },
+ { 0x215B, 0x215E }, { 0x2160, 0x216B }, { 0x2170, 0x2179 },
+ { 0x2190, 0x2199 }, { 0x21B8, 0x21B9 }, { 0x21D2, 0x21D2 },
+ { 0x21D4, 0x21D4 }, { 0x21E7, 0x21E7 }, { 0x2200, 0x2200 },
+ { 0x2202, 0x2203 }, { 0x2207, 0x2208 }, { 0x220B, 0x220B },
+ { 0x220F, 0x220F }, { 0x2211, 0x2211 }, { 0x2215, 0x2215 },
+ { 0x221A, 0x221A }, { 0x221D, 0x2220 }, { 0x2223, 0x2223 },
+ { 0x2225, 0x2225 }, { 0x2227, 0x222C }, { 0x222E, 0x222E },
+ { 0x2234, 0x2237 }, { 0x223C, 0x223D }, { 0x2248, 0x2248 },
+ { 0x224C, 0x224C }, { 0x2252, 0x2252 }, { 0x2260, 0x2261 },
+ { 0x2264, 0x2267 }, { 0x226A, 0x226B }, { 0x226E, 0x226F },
+ { 0x2282, 0x2283 }, { 0x2286, 0x2287 }, { 0x2295, 0x2295 },
+ { 0x2299, 0x2299 }, { 0x22A5, 0x22A5 }, { 0x22BF, 0x22BF },
+ { 0x2312, 0x2312 }, { 0x2460, 0x24E9 }, { 0x24EB, 0x254B },
+ { 0x2550, 0x2573 }, { 0x2580, 0x258F }, { 0x2592, 0x2595 },
+ { 0x25A0, 0x25A1 }, { 0x25A3, 0x25A9 }, { 0x25B2, 0x25B3 },
+ { 0x25B6, 0x25B7 }, { 0x25BC, 0x25BD }, { 0x25C0, 0x25C1 },
+ { 0x25C6, 0x25C8 }, { 0x25CB, 0x25CB }, { 0x25CE, 0x25D1 },
+ { 0x25E2, 0x25E5 }, { 0x25EF, 0x25EF }, { 0x2605, 0x2606 },
+ { 0x2609, 0x2609 }, { 0x260E, 0x260F }, { 0x2614, 0x2615 },
+ { 0x261C, 0x261C }, { 0x261E, 0x261E }, { 0x2640, 0x2640 },
+ { 0x2642, 0x2642 }, { 0x2660, 0x2661 }, { 0x2663, 0x2665 },
+ { 0x2667, 0x266A }, { 0x266C, 0x266D }, { 0x266F, 0x266F },
+ { 0x273D, 0x273D }, { 0x2776, 0x277F }, { 0xE000, 0xF8FF },
+ { 0xFFFD, 0xFFFD }, { 0xF0000, 0xFFFFD }, { 0x100000, 0x10FFFD }
+ };
+
+ /* binary search in table of non-spacing characters */
+ if (bisearch(ucs, ambiguous,
+ sizeof(ambiguous) / sizeof(struct interval) - 1))
+ return 2;
+
+ return mk_wcwidth(ucs);
+}
+
+static int mk_wcswidth_cjk(const uint32_t *pwcs, size_t n)
+{
+ int w, width = 0;
+
+ for (;*pwcs && n-- > 0; pwcs++)
+ if ((w = mk_wcwidth_cjk(*pwcs)) < 0)
+ return -1;
+ else
+ width += w;
+
+ return width;
+}
+#endif
+
+#ifdef IS_COMBINING_FUNCTION
+/* Use a provided is_combining() function. */
+int IS_COMBINING_FUNCTION(uint32_t codepoint);
+#else
+# define IS_COMBINING_FUNCTION vterm_is_combining
+ static int
+vterm_is_combining(uint32_t codepoint)
+{
+ return bisearch(codepoint, combining, sizeof(combining) / sizeof(struct interval) - 1);
+}
+#endif
+
+// ################################
+// ### The rest added by Paul Evans
+
+INTERNAL int vterm_unicode_width(uint32_t codepoint)
+{
+ return WCWIDTH_FUNCTION(codepoint);
+}
+
+INTERNAL int vterm_unicode_is_combining(uint32_t codepoint)
+{
+ return IS_COMBINING_FUNCTION(codepoint);
+}
diff --git a/src/libvterm/src/utf8.h b/src/libvterm/src/utf8.h
new file mode 100644
index 0000000..886a1ca
--- /dev/null
+++ b/src/libvterm/src/utf8.h
@@ -0,0 +1,47 @@
+/* The following functions copied and adapted from libtermkey
+ *
+ * http://www.leonerd.org.uk/code/libtermkey/
+ */
+unsigned int utf8_seqlen(long codepoint);
+
+#if defined(DEFINE_INLINES) || USE_INLINE
+INLINE unsigned int utf8_seqlen(long codepoint)
+{
+ if(codepoint < 0x0000080) return 1;
+ if(codepoint < 0x0000800) return 2;
+ if(codepoint < 0x0010000) return 3;
+ if(codepoint < 0x0200000) return 4;
+ if(codepoint < 0x4000000) return 5;
+ return 6;
+}
+#endif
+
+/* Does NOT NUL-terminate the buffer */
+int fill_utf8(long codepoint, char *str);
+
+#if defined(DEFINE_INLINES) || USE_INLINE
+INLINE int fill_utf8(long codepoint, char *str)
+{
+ int nbytes = utf8_seqlen(codepoint);
+
+ // This is easier done backwards
+ int b = nbytes;
+ while(b > 1) {
+ b--;
+ str[b] = 0x80 | (codepoint & 0x3f);
+ codepoint >>= 6;
+ }
+
+ switch(nbytes) {
+ case 1: str[0] = (codepoint & 0x7f); break;
+ case 2: str[0] = 0xc0 | (codepoint & 0x1f); break;
+ case 3: str[0] = 0xe0 | (codepoint & 0x0f); break;
+ case 4: str[0] = 0xf0 | (codepoint & 0x07); break;
+ case 5: str[0] = 0xf8 | (codepoint & 0x03); break;
+ case 6: str[0] = 0xfc | (codepoint & 0x01); break;
+ }
+
+ return nbytes;
+}
+#endif
+/* end copy */
diff --git a/src/libvterm/src/vterm.c b/src/libvterm/src/vterm.c
new file mode 100644
index 0000000..5e4722c
--- /dev/null
+++ b/src/libvterm/src/vterm.c
@@ -0,0 +1,425 @@
+#define DEFINE_INLINES
+
+/* vim: set sw=2 : */
+#include "vterm_internal.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+
+#include "utf8.h"
+
+/*****************
+ * API functions *
+ *****************/
+
+static void *default_malloc(size_t size, void *allocdata UNUSED)
+{
+ void *ptr = malloc(size);
+ if(ptr)
+ memset(ptr, 0, size);
+ return ptr;
+}
+
+static void default_free(void *ptr, void *allocdata UNUSED)
+{
+ free(ptr);
+}
+
+static VTermAllocatorFunctions default_allocator = {
+ &default_malloc, // malloc
+ &default_free // free
+};
+
+VTerm *vterm_new(int rows, int cols)
+{
+ return vterm_new_with_allocator(rows, cols, &default_allocator, NULL);
+}
+
+VTerm *vterm_new_with_allocator(int rows, int cols, VTermAllocatorFunctions *funcs, void *allocdata)
+{
+ /* Need to bootstrap using the allocator function directly */
+ VTerm *vt = (*funcs->malloc)(sizeof(VTerm), allocdata);
+
+ if (vt == NULL)
+ return NULL;
+ vt->allocator = funcs;
+ vt->allocdata = allocdata;
+
+ vt->rows = rows;
+ vt->cols = cols;
+
+ vt->parser.state = NORMAL;
+
+ vt->parser.callbacks = NULL;
+ vt->parser.cbdata = NULL;
+
+ vt->parser.strbuffer_len = 500; /* should be able to hold an OSC string */
+ vt->parser.strbuffer_cur = 0;
+ vt->parser.strbuffer = vterm_allocator_malloc(vt, vt->parser.strbuffer_len);
+ if (vt->parser.strbuffer == NULL)
+ {
+ vterm_allocator_free(vt, vt);
+ return NULL;
+ }
+
+ vt->outbuffer_len = 200;
+ vt->outbuffer_cur = 0;
+ vt->outbuffer = vterm_allocator_malloc(vt, vt->outbuffer_len);
+ if (vt->outbuffer == NULL)
+ {
+ vterm_allocator_free(vt, vt->parser.strbuffer);
+ vterm_allocator_free(vt, vt);
+ return NULL;
+ }
+
+ return vt;
+}
+
+void vterm_free(VTerm *vt)
+{
+ if(vt->screen)
+ vterm_screen_free(vt->screen);
+
+ if(vt->state)
+ vterm_state_free(vt->state);
+
+ vterm_allocator_free(vt, vt->parser.strbuffer);
+ vterm_allocator_free(vt, vt->outbuffer);
+
+ vterm_allocator_free(vt, vt);
+}
+
+INTERNAL void *vterm_allocator_malloc(VTerm *vt, size_t size)
+{
+ return (*vt->allocator->malloc)(size, vt->allocdata);
+}
+
+/*
+ * Free "ptr" unless it is NULL.
+ */
+INTERNAL void vterm_allocator_free(VTerm *vt, void *ptr)
+{
+ if (ptr)
+ (*vt->allocator->free)(ptr, vt->allocdata);
+}
+
+void vterm_get_size(const VTerm *vt, int *rowsp, int *colsp)
+{
+ if(rowsp)
+ *rowsp = vt->rows;
+ if(colsp)
+ *colsp = vt->cols;
+}
+
+void vterm_set_size(VTerm *vt, int rows, int cols)
+{
+ vt->rows = rows;
+ vt->cols = cols;
+
+ if(vt->parser.callbacks && vt->parser.callbacks->resize)
+ (*vt->parser.callbacks->resize)(rows, cols, vt->parser.cbdata);
+}
+
+int vterm_get_utf8(const VTerm *vt)
+{
+ return vt->mode.utf8;
+}
+
+void vterm_set_utf8(VTerm *vt, int is_utf8)
+{
+ vt->mode.utf8 = is_utf8;
+}
+
+INTERNAL void vterm_push_output_bytes(VTerm *vt, const char *bytes, size_t len)
+{
+ if(len > vt->outbuffer_len - vt->outbuffer_cur) {
+ DEBUG_LOG("vterm_push_output(): buffer overflow; truncating output\n");
+ len = vt->outbuffer_len - vt->outbuffer_cur;
+ }
+
+ memcpy(vt->outbuffer + vt->outbuffer_cur, bytes, len);
+ vt->outbuffer_cur += len;
+}
+
+static int outbuffer_is_full(VTerm *vt)
+{
+ return vt->outbuffer_cur >= vt->outbuffer_len - 1;
+}
+
+#if (defined(_XOPEN_SOURCE) && _XOPEN_SOURCE >= 500) \
+ || defined(_ISOC99_SOURCE) || defined(_BSD_SOURCE)
+# undef VSNPRINTF
+# define VSNPRINTF vsnprintf
+#else
+# ifdef VSNPRINTF
+/* Use a provided vsnprintf() function. */
+int VSNPRINTF(char *str, size_t str_m, const char *fmt, va_list ap);
+# endif
+#endif
+
+
+INTERNAL void vterm_push_output_vsprintf(VTerm *vt, const char *format, va_list args)
+{
+ int written;
+#ifndef VSNPRINTF
+ /* When vsnprintf() is not available (C90) fall back to vsprintf(). */
+ char buffer[1024]; /* 1Kbyte is enough for everybody, right? */
+#endif
+
+ if(outbuffer_is_full(vt)) {
+ DEBUG_LOG("vterm_push_output(): buffer overflow; truncating output\n");
+ return;
+ }
+
+#ifdef VSNPRINTF
+ written = VSNPRINTF(vt->outbuffer + vt->outbuffer_cur,
+ vt->outbuffer_len - vt->outbuffer_cur,
+ format, args);
+
+ if(written == (int)(vt->outbuffer_len - vt->outbuffer_cur)) {
+ /* output was truncated */
+ vt->outbuffer_cur = vt->outbuffer_len - 1;
+ }
+ else
+ vt->outbuffer_cur += written;
+#else
+ written = vsprintf(buffer, format, args);
+
+ if(written >= (int)(vt->outbuffer_len - vt->outbuffer_cur - 1)) {
+ /* output was truncated */
+ written = vt->outbuffer_len - vt->outbuffer_cur - 1;
+ }
+ if (written > 0)
+ {
+ strncpy(vt->outbuffer + vt->outbuffer_cur, buffer, written + 1);
+ vt->outbuffer_cur += written;
+ }
+#endif
+}
+
+INTERNAL void vterm_push_output_sprintf(VTerm *vt, const char *format, ...)
+{
+ va_list args;
+ va_start(args, format);
+ vterm_push_output_vsprintf(vt, format, args);
+ va_end(args);
+}
+
+INTERNAL void vterm_push_output_sprintf_ctrl(VTerm *vt, unsigned char ctrl, const char *fmt, ...)
+{
+ size_t orig_cur = vt->outbuffer_cur;
+ va_list args;
+
+ if(ctrl >= 0x80 && !vt->mode.ctrl8bit)
+ vterm_push_output_sprintf(vt, ESC_S "%c", ctrl - 0x40);
+ else
+ vterm_push_output_sprintf(vt, "%c", ctrl);
+
+ va_start(args, fmt);
+ vterm_push_output_vsprintf(vt, fmt, args);
+ va_end(args);
+
+ if(outbuffer_is_full(vt))
+ vt->outbuffer_cur = orig_cur;
+}
+
+INTERNAL void vterm_push_output_sprintf_dcs(VTerm *vt, const char *fmt, ...)
+{
+ size_t orig_cur = vt->outbuffer_cur;
+ va_list args;
+
+ if(!vt->mode.ctrl8bit)
+ vterm_push_output_sprintf(vt, ESC_S "%c", C1_DCS - 0x40);
+ else
+ vterm_push_output_sprintf(vt, "%c", C1_DCS);
+
+ va_start(args, fmt);
+ vterm_push_output_vsprintf(vt, fmt, args);
+ va_end(args);
+
+ vterm_push_output_sprintf_ctrl(vt, C1_ST, "");
+
+ if(outbuffer_is_full(vt))
+ vt->outbuffer_cur = orig_cur;
+}
+
+size_t vterm_output_get_buffer_size(const VTerm *vt)
+{
+ return vt->outbuffer_len;
+}
+
+size_t vterm_output_get_buffer_current(const VTerm *vt)
+{
+ return vt->outbuffer_cur;
+}
+
+size_t vterm_output_get_buffer_remaining(const VTerm *vt)
+{
+ return vt->outbuffer_len - vt->outbuffer_cur;
+}
+
+size_t vterm_output_read(VTerm *vt, char *buffer, size_t len)
+{
+ if(len > vt->outbuffer_cur)
+ len = vt->outbuffer_cur;
+
+ memcpy(buffer, vt->outbuffer, len);
+
+ if(len < vt->outbuffer_cur)
+ memmove(vt->outbuffer, vt->outbuffer + len, vt->outbuffer_cur - len);
+
+ vt->outbuffer_cur -= len;
+
+ return len;
+}
+
+VTermValueType vterm_get_attr_type(VTermAttr attr)
+{
+ switch(attr) {
+ case VTERM_ATTR_BOLD: return VTERM_VALUETYPE_BOOL;
+ case VTERM_ATTR_UNDERLINE: return VTERM_VALUETYPE_INT;
+ case VTERM_ATTR_ITALIC: return VTERM_VALUETYPE_BOOL;
+ case VTERM_ATTR_BLINK: return VTERM_VALUETYPE_BOOL;
+ case VTERM_ATTR_REVERSE: return VTERM_VALUETYPE_BOOL;
+ case VTERM_ATTR_STRIKE: return VTERM_VALUETYPE_BOOL;
+ case VTERM_ATTR_FONT: return VTERM_VALUETYPE_INT;
+ case VTERM_ATTR_FOREGROUND: return VTERM_VALUETYPE_COLOR;
+ case VTERM_ATTR_BACKGROUND: return VTERM_VALUETYPE_COLOR;
+
+ case VTERM_N_ATTRS: return 0;
+ }
+ return 0; /* UNREACHABLE */
+}
+
+VTermValueType vterm_get_prop_type(VTermProp prop)
+{
+ switch(prop) {
+ case VTERM_PROP_CURSORVISIBLE: return VTERM_VALUETYPE_BOOL;
+ case VTERM_PROP_CURSORBLINK: return VTERM_VALUETYPE_BOOL;
+ case VTERM_PROP_ALTSCREEN: return VTERM_VALUETYPE_BOOL;
+ case VTERM_PROP_TITLE: return VTERM_VALUETYPE_STRING;
+ case VTERM_PROP_ICONNAME: return VTERM_VALUETYPE_STRING;
+ case VTERM_PROP_REVERSE: return VTERM_VALUETYPE_BOOL;
+ case VTERM_PROP_CURSORSHAPE: return VTERM_VALUETYPE_INT;
+ case VTERM_PROP_MOUSE: return VTERM_VALUETYPE_INT;
+ case VTERM_PROP_CURSORCOLOR: return VTERM_VALUETYPE_STRING;
+
+ case VTERM_N_PROPS: return 0;
+ }
+ return 0; /* UNREACHABLE */
+}
+
+void vterm_scroll_rect(VTermRect rect,
+ int downward,
+ int rightward,
+ int (*moverect)(VTermRect src, VTermRect dest, void *user),
+ int (*eraserect)(VTermRect rect, int selective, void *user),
+ void *user)
+{
+ VTermRect src;
+ VTermRect dest;
+
+ if(abs(downward) >= rect.end_row - rect.start_row ||
+ abs(rightward) >= rect.end_col - rect.start_col) {
+ /* Scroll more than area; just erase the lot */
+ (*eraserect)(rect, 0, user);
+ return;
+ }
+
+ if(rightward >= 0) {
+ /* rect: [XXX................]
+ * src: [----------------]
+ * dest: [----------------]
+ */
+ dest.start_col = rect.start_col;
+ dest.end_col = rect.end_col - rightward;
+ src.start_col = rect.start_col + rightward;
+ src.end_col = rect.end_col;
+ }
+ else {
+ /* rect: [................XXX]
+ * src: [----------------]
+ * dest: [----------------]
+ */
+ int leftward = -rightward;
+ dest.start_col = rect.start_col + leftward;
+ dest.end_col = rect.end_col;
+ src.start_col = rect.start_col;
+ src.end_col = rect.end_col - leftward;
+ }
+
+ if(downward >= 0) {
+ dest.start_row = rect.start_row;
+ dest.end_row = rect.end_row - downward;
+ src.start_row = rect.start_row + downward;
+ src.end_row = rect.end_row;
+ }
+ else {
+ int upward = -downward;
+ dest.start_row = rect.start_row + upward;
+ dest.end_row = rect.end_row;
+ src.start_row = rect.start_row;
+ src.end_row = rect.end_row - upward;
+ }
+
+ if(moverect)
+ (*moverect)(dest, src, user);
+
+ if(downward > 0)
+ rect.start_row = rect.end_row - downward;
+ else if(downward < 0)
+ rect.end_row = rect.start_row - downward;
+
+ if(rightward > 0)
+ rect.start_col = rect.end_col - rightward;
+ else if(rightward < 0)
+ rect.end_col = rect.start_col - rightward;
+
+ (*eraserect)(rect, 0, user);
+}
+
+void vterm_copy_cells(VTermRect dest,
+ VTermRect src,
+ void (*copycell)(VTermPos dest, VTermPos src, void *user),
+ void *user)
+{
+ int downward = src.start_row - dest.start_row;
+ int rightward = src.start_col - dest.start_col;
+
+ int init_row, test_row, init_col, test_col;
+ int inc_row, inc_col;
+
+ VTermPos pos;
+
+ if(downward < 0) {
+ init_row = dest.end_row - 1;
+ test_row = dest.start_row - 1;
+ inc_row = -1;
+ }
+ else /* downward >= 0 */ {
+ init_row = dest.start_row;
+ test_row = dest.end_row;
+ inc_row = +1;
+ }
+
+ if(rightward < 0) {
+ init_col = dest.end_col - 1;
+ test_col = dest.start_col - 1;
+ inc_col = -1;
+ }
+ else /* rightward >= 0 */ {
+ init_col = dest.start_col;
+ test_col = dest.end_col;
+ inc_col = +1;
+ }
+
+ for(pos.row = init_row; pos.row != test_row; pos.row += inc_row)
+ for(pos.col = init_col; pos.col != test_col; pos.col += inc_col) {
+ VTermPos srcpos;
+ srcpos.row = pos.row + downward;
+ srcpos.col = pos.col + rightward;
+ (*copycell)(pos, srcpos, user);
+ }
+}
diff --git a/src/libvterm/src/vterm_internal.h b/src/libvterm/src/vterm_internal.h
new file mode 100644
index 0000000..2ef358c
--- /dev/null
+++ b/src/libvterm/src/vterm_internal.h
@@ -0,0 +1,263 @@
+#ifndef __VTERM_INTERNAL_H__
+#define __VTERM_INTERNAL_H__
+
+#include "vterm.h"
+
+#include <stdarg.h>
+
+#if defined(__GNUC__) && !defined(__MINGW32__)
+# define INTERNAL __attribute__((visibility("internal")))
+# define UNUSED __attribute__((unused))
+#else
+# define INTERNAL
+# define UNUSED
+#endif
+
+#ifdef DEBUG
+# define DEBUG_LOG(s) fprintf(stderr, s)
+# define DEBUG_LOG1(s, a) fprintf(stderr, s, a)
+# define DEBUG_LOG2(s, a, b) fprintf(stderr, s, a, b)
+# define DEBUG_LOG3(s, a, b, c) fprintf(stderr, s, a, b, c)
+#else
+# define DEBUG_LOG(s)
+# define DEBUG_LOG1(s, a)
+# define DEBUG_LOG2(s, a, b)
+# define DEBUG_LOG3(s, a, b, c)
+#endif
+
+#define ESC_S "\x1b"
+
+#define INTERMED_MAX 16
+
+#define CSI_ARGS_MAX 16
+#define CSI_LEADER_MAX 16
+
+typedef struct VTermEncoding VTermEncoding;
+
+typedef struct {
+ VTermEncoding *enc;
+
+ // This size should be increased if required by other stateful encodings
+ char data[4*sizeof(uint32_t)];
+} VTermEncodingInstance;
+
+struct VTermPen
+{
+ VTermColor fg;
+ VTermColor bg;
+ unsigned int bold:1;
+ unsigned int underline:2;
+ unsigned int italic:1;
+ unsigned int blink:1;
+ unsigned int reverse:1;
+ unsigned int strike:1;
+ unsigned int font:4; /* To store 0-9 */
+};
+
+int vterm_color_equal(VTermColor a, VTermColor b);
+
+#if defined(DEFINE_INLINES) || USE_INLINE
+INLINE int vterm_color_equal(VTermColor a, VTermColor b)
+{
+ return a.red == b.red && a.green == b.green && a.blue == b.blue;
+}
+#endif
+
+struct VTermState
+{
+ VTerm *vt;
+
+ const VTermStateCallbacks *callbacks;
+ void *cbdata;
+
+ const VTermParserCallbacks *fallbacks;
+ void *fbdata;
+
+ int rows;
+ int cols;
+
+ /* Current cursor position */
+ VTermPos pos;
+
+ int at_phantom; /* True if we're on the "81st" phantom column to defer a wraparound */
+
+ int scrollregion_top;
+ int scrollregion_bottom; /* -1 means unbounded */
+#define SCROLLREGION_BOTTOM(state) ((state)->scrollregion_bottom > -1 ? (state)->scrollregion_bottom : (state)->rows)
+ int scrollregion_left;
+#define SCROLLREGION_LEFT(state) ((state)->mode.leftrightmargin ? (state)->scrollregion_left : 0)
+ int scrollregion_right; /* -1 means unbounded */
+#define SCROLLREGION_RIGHT(state) ((state)->mode.leftrightmargin && (state)->scrollregion_right > -1 ? (state)->scrollregion_right : (state)->cols)
+
+ /* Bitvector of tab stops */
+ unsigned char *tabstops;
+
+ VTermLineInfo *lineinfo;
+#define ROWWIDTH(state,row) ((state)->lineinfo[(row)].doublewidth ? ((state)->cols / 2) : (state)->cols)
+#define THISROWWIDTH(state) ROWWIDTH(state, (state)->pos.row)
+
+ /* Mouse state */
+ int mouse_col, mouse_row;
+ int mouse_buttons;
+ int mouse_flags;
+
+ enum { MOUSE_X10, MOUSE_UTF8, MOUSE_SGR, MOUSE_RXVT } mouse_protocol;
+
+ /* Last glyph output, for Unicode recombining purposes */
+ uint32_t *combine_chars;
+ size_t combine_chars_size; // Number of ELEMENTS in the above
+ int combine_width; // The width of the glyph above
+ VTermPos combine_pos; // Position before movement
+
+ struct {
+ unsigned int keypad:1;
+ unsigned int cursor:1;
+ unsigned int autowrap:1;
+ unsigned int insert:1;
+ unsigned int newline:1;
+ unsigned int cursor_visible:1;
+ unsigned int cursor_blink:1;
+ unsigned int cursor_shape:2;
+ unsigned int alt_screen:1;
+ unsigned int origin:1;
+ unsigned int screen:1;
+ unsigned int leftrightmargin:1;
+ unsigned int bracketpaste:1;
+ unsigned int report_focus:1;
+ } mode;
+
+ VTermEncodingInstance encoding[4], encoding_utf8;
+ int gl_set, gr_set, gsingle_set;
+
+ struct VTermPen pen;
+
+ VTermColor default_fg;
+ VTermColor default_bg;
+ VTermColor colors[16]; // Store the 8 ANSI and the 8 ANSI high-brights only
+
+ int fg_index;
+ int bg_index;
+ int bold_is_highbright;
+
+ unsigned int protected_cell : 1;
+
+ /* Saved state under DEC mode 1048/1049 */
+ struct {
+ VTermPos pos;
+ struct VTermPen pen;
+
+ struct {
+ unsigned int cursor_visible:1;
+ unsigned int cursor_blink:1;
+ unsigned int cursor_shape:2;
+ } mode;
+ } saved;
+};
+
+typedef enum {
+ VTERM_PARSER_OSC,
+ VTERM_PARSER_DCS,
+
+ VTERM_N_PARSER_TYPES
+} VTermParserStringType;
+
+struct VTerm
+{
+ VTermAllocatorFunctions *allocator;
+ void *allocdata;
+
+ int rows;
+ int cols;
+
+ struct {
+ unsigned int utf8:1;
+ unsigned int ctrl8bit:1;
+ } mode;
+
+ struct {
+ enum VTermParserState {
+ NORMAL,
+ CSI_LEADER,
+ CSI_ARGS,
+ CSI_INTERMED,
+ ESC,
+ /* below here are the "string states" */
+ STRING,
+ ESC_IN_STRING,
+ } state;
+
+ int intermedlen;
+ char intermed[INTERMED_MAX];
+
+ int csi_leaderlen;
+ char csi_leader[CSI_LEADER_MAX];
+
+ int csi_argi;
+ long csi_args[CSI_ARGS_MAX];
+
+ const VTermParserCallbacks *callbacks;
+ void *cbdata;
+
+ VTermParserStringType stringtype;
+ char *strbuffer;
+ size_t strbuffer_len;
+ size_t strbuffer_cur;
+ } parser;
+
+ /* len == malloc()ed size; cur == number of valid bytes */
+
+ char *outbuffer;
+ size_t outbuffer_len;
+ size_t outbuffer_cur;
+
+ VTermState *state;
+ VTermScreen *screen;
+};
+
+struct VTermEncoding {
+ void (*init) (VTermEncoding *enc, void *data);
+ void (*decode)(VTermEncoding *enc, void *data,
+ uint32_t cp[], int *cpi, int cplen,
+ const char bytes[], size_t *pos, size_t len);
+};
+
+typedef enum {
+ ENC_UTF8,
+ ENC_SINGLE_94
+} VTermEncodingType;
+
+void *vterm_allocator_malloc(VTerm *vt, size_t size);
+void vterm_allocator_free(VTerm *vt, void *ptr);
+
+void vterm_push_output_bytes(VTerm *vt, const char *bytes, size_t len);
+void vterm_push_output_vsprintf(VTerm *vt, const char *format, va_list args);
+void vterm_push_output_sprintf(VTerm *vt, const char *format, ...);
+void vterm_push_output_sprintf_ctrl(VTerm *vt, unsigned char ctrl, const char *fmt, ...);
+void vterm_push_output_sprintf_dcs(VTerm *vt, const char *fmt, ...);
+
+void vterm_state_free(VTermState *state);
+
+void vterm_state_newpen(VTermState *state);
+void vterm_state_resetpen(VTermState *state);
+void vterm_state_setpen(VTermState *state, const long args[], int argcount);
+int vterm_state_getpen(VTermState *state, long args[], int argcount);
+void vterm_state_savepen(VTermState *state, int save);
+
+enum {
+ C1_SS3 = 0x8f,
+ C1_DCS = 0x90,
+ C1_CSI = 0x9b,
+ C1_ST = 0x9c,
+ C1_OSC = 0x9d,
+};
+
+void vterm_state_push_output_sprintf_CSI(VTermState *vts, const char *format, ...);
+
+void vterm_screen_free(VTermScreen *screen);
+
+VTermEncoding *vterm_lookup_encoding(VTermEncodingType type, char designation);
+
+int vterm_unicode_width(uint32_t codepoint);
+int vterm_unicode_is_combining(uint32_t codepoint);
+
+#endif
diff --git a/src/libvterm/t/02parser.test b/src/libvterm/t/02parser.test
new file mode 100644
index 0000000..66d487d
--- /dev/null
+++ b/src/libvterm/t/02parser.test
@@ -0,0 +1,200 @@
+INIT
+UTF8 0
+WANTPARSER
+
+!Basic text
+PUSH "hello"
+ text 0x68, 0x65, 0x6c, 0x6c, 0x6f
+
+!C0
+PUSH "\x03"
+ control 3
+
+PUSH "\x1f"
+ control 0x1f
+
+!C1 8bit
+PUSH "\x83"
+ control 0x83
+
+PUSH "\x9f"
+ control 0x9f
+
+!C1 7bit
+PUSH "\e\x43"
+ control 0x83
+
+PUSH "\e\x5f"
+ control 0x9f
+
+!High bytes
+PUSH "\xa0\xcc\xfe"
+ text 0xa0, 0xcc, 0xfe
+
+!Mixed
+PUSH "1\n2"
+ text 0x31
+ control 10
+ text 0x32
+
+!Escape
+PUSH "\e="
+ escape "="
+
+!Escape 2-byte
+PUSH "\e(X"
+ escape "(X"
+
+!Split write Escape
+PUSH "\e("
+PUSH "Y"
+ escape "(Y"
+
+!Escape cancels Escape, starts another
+PUSH "\e(\e)Z"
+ escape ")Z"
+
+!CAN cancels Escape, returns to normal mode
+PUSH "\e(\x{18}AB"
+ text 0x41, 0x42
+
+!C0 in Escape interrupts and continues
+PUSH "\e(\nX"
+ control 10
+ escape "(X"
+
+!CSI 0 args
+PUSH "\e[a"
+ csi 0x61 *
+
+!CSI 1 arg
+PUSH "\e[9b"
+ csi 0x62 9
+
+!CSI 2 args
+PUSH "\e[3;4c"
+ csi 0x63 3,4
+
+!CSI 1 arg 1 sub
+PUSH "\e[1:2c"
+ csi 0x63 1+,2
+
+!CSI many digits
+PUSH "\e[678d"
+ csi 0x64 678
+
+!CSI leading zero
+PUSH "\e[007e"
+ csi 0x65 7
+
+!CSI qmark
+PUSH "\e[?2;7f"
+ csi 0x66 L=3f 2,7
+
+!CSI greater
+PUSH "\e[>c"
+ csi 0x63 L=3e *
+
+!CSI SP
+PUSH "\e[12 q"
+ csi 0x71 12 I=20
+
+!Mixed CSI
+PUSH "A\e[8mB"
+ text 0x41
+ csi 0x6d 8
+ text 0x42
+
+!Split write
+PUSH "\e"
+PUSH "[a"
+ csi 0x61 *
+PUSH "foo\e["
+ text 0x66, 0x6f, 0x6f
+PUSH "4b"
+ csi 0x62 4
+PUSH "\e[12;"
+PUSH "3c"
+ csi 0x63 12,3
+
+!Escape cancels CSI, starts Escape
+PUSH "\e[123\e9"
+ escape "9"
+
+!CAN cancels CSI, returns to normal mode
+PUSH "\e[12\x{18}AB"
+ text 0x41, 0x42
+
+!C0 in Escape interrupts and continues
+PUSH "\e[12\n;3X"
+ control 10
+ csi 0x58 12,3
+
+!OSC BEL
+PUSH "\e]1;Hello\x07"
+ osc "1;Hello"
+
+!OSC ST (7bit)
+PUSH "\e]1;Hello\e\\"
+ osc "1;Hello"
+
+!OSC ST (8bit)
+PUSH "\x{9d}1;Hello\x9c"
+ osc "1;Hello"
+
+!Escape cancels OSC, starts Escape
+PUSH "\e]Something\e9"
+ escape "9"
+
+!CAN cancels OSC, returns to normal mode
+PUSH "\e]12\x{18}AB"
+ text 0x41, 0x42
+
+!C0 in OSC interrupts and continues
+PUSH "\e]2;\nBye\x07"
+ control 10
+ osc "2;Bye"
+
+!DCS BEL
+PUSH "\ePHello\x07"
+ dcs "Hello"
+
+!DCS ST (7bit)
+PUSH "\ePHello\e\\"
+ dcs "Hello"
+
+!DCS ST (8bit)
+PUSH "\x{90}Hello\x9c"
+ dcs "Hello"
+
+!Escape cancels DCS, starts Escape
+PUSH "\ePSomething\e9"
+ escape "9"
+
+!CAN cancels DCS, returns to normal mode
+PUSH "\eP12\x{18}AB"
+ text 0x41, 0x42
+
+!C0 in OSC interrupts and continues
+PUSH "\ePBy\ne\x07"
+ control 10
+ dcs "Bye"
+
+!NUL ignored
+PUSH "\x{00}"
+
+!NUL ignored within CSI
+PUSH "\e[12\x{00}3m"
+ csi 0x6d 123
+
+!DEL ignored
+PUSH "\x{7f}"
+
+!DEL ignored within CSI
+PUSH "\e[12\x{7f}3m"
+ csi 0x6d 123
+
+!DEL inside text"
+PUSH "AB\x{7f}C"
+ text 0x41,0x42
+ text 0x43
diff --git a/src/libvterm/t/03encoding_utf8.test b/src/libvterm/t/03encoding_utf8.test
new file mode 100644
index 0000000..7ee16ac
--- /dev/null
+++ b/src/libvterm/t/03encoding_utf8.test
@@ -0,0 +1,122 @@
+INIT
+WANTENCODING
+
+!Low
+ENCIN "123"
+ encout 0x31,0x32,0x33
+
+# We want to prove the UTF-8 parser correctly handles all the sequences.
+# Easy way to do this is to check it does low/high boundary cases, as that
+# leaves only two for each sequence length
+#
+# These ranges are therefore:
+#
+# Two bytes:
+# U+0080 = 000 10000000 => 00010 000000
+# => 11000010 10000000 = C2 80
+# U+07FF = 111 11111111 => 11111 111111
+# => 11011111 10111111 = DF BF
+#
+# Three bytes:
+# U+0800 = 00001000 00000000 => 0000 100000 000000
+# => 11100000 10100000 10000000 = E0 A0 80
+# U+FFFD = 11111111 11111101 => 1111 111111 111101
+# => 11101111 10111111 10111101 = EF BF BD
+# (We avoid U+FFFE and U+FFFF as they're invalid codepoints)
+#
+# Four bytes:
+# U+10000 = 00001 00000000 00000000 => 000 010000 000000 000000
+# => 11110000 10010000 10000000 10000000 = F0 90 80 80
+# U+1FFFFF = 11111 11111111 11111111 => 111 111111 111111 111111
+# => 11110111 10111111 10111111 10111111 = F7 BF BF BF
+
+!2 byte
+ENCIN "\xC2\x80\xDF\xBF"
+ encout 0x0080, 0x07FF
+
+!3 byte
+ENCIN "\xE0\xA0\x80\xEF\xBF\xBD"
+ encout 0x0800,0xFFFD
+
+!4 byte
+ENCIN "\xF0\x90\x80\x80\xF7\xBF\xBF\xBF"
+ encout 0x10000,0x1fffff
+
+# Next up, we check some invalid sequences
+# + Early termination (back to low bytes too soon)
+# + Early restart (another sequence introduction before the previous one was finished)
+
+!Early termination
+ENCIN "\xC2!"
+ encout 0xfffd,0x21
+
+ENCIN "\xE0!\xE0\xA0!"
+ encout 0xfffd,0x21,0xfffd,0x21
+
+ENCIN "\xF0!\xF0\x90!\xF0\x90\x80!"
+ encout 0xfffd,0x21,0xfffd,0x21,0xfffd,0x21
+
+!Early restart
+ENCIN "\xC2\xC2\x90"
+ encout 0xfffd,0x0090
+
+ENCIN "\xE0\xC2\x90\xE0\xA0\xC2\x90"
+ encout 0xfffd,0x0090,0xfffd,0x0090
+
+ENCIN "\xF0\xC2\x90\xF0\x90\xC2\x90\xF0\x90\x80\xC2\x90"
+ encout 0xfffd,0x0090,0xfffd,0x0090,0xfffd,0x0090
+
+# Test the overlong sequences by giving an overlong encoding of U+0000 and
+# an encoding of the highest codepoint still too short
+#
+# Two bytes:
+# U+0000 = C0 80
+# U+007F = 000 01111111 => 00001 111111 =>
+# => 11000001 10111111 => C1 BF
+#
+# Three bytes:
+# U+0000 = E0 80 80
+# U+07FF = 00000111 11111111 => 0000 011111 111111
+# => 11100000 10011111 10111111 = E0 9F BF
+#
+# Four bytes:
+# U+0000 = F0 80 80 80
+# U+FFFF = 11111111 11111111 => 000 001111 111111 111111
+# => 11110000 10001111 10111111 10111111 = F0 8F BF BF
+
+!Overlong
+ENCIN "\xC0\x80\xC1\xBF"
+ encout 0xfffd,0xfffd
+
+ENCIN "\xE0\x80\x80\xE0\x9F\xBF"
+ encout 0xfffd,0xfffd
+
+ENCIN "\xF0\x80\x80\x80\xF0\x8F\xBF\xBF"
+ encout 0xfffd,0xfffd
+
+# UTF-16 surrogates U+D800 and U+DFFF
+!UTF-16 Surrogates
+ENCIN "\xED\xA0\x80\xED\xBF\xBF"
+ encout 0xfffd,0xfffd
+
+!Split write
+ENCIN "\xC2"
+ENCIN "\xA0"
+ encout 0x000A0
+
+ENCIN "\xE0"
+ENCIN "\xA0\x80"
+ encout 0x00800
+ENCIN "\xE0\xA0"
+ENCIN "\x80"
+ encout 0x00800
+
+ENCIN "\xF0"
+ENCIN "\x90\x80\x80"
+ encout 0x10000
+ENCIN "\xF0\x90"
+ENCIN "\x80\x80"
+ encout 0x10000
+ENCIN "\xF0\x90\x80"
+ENCIN "\x80"
+ encout 0x10000
diff --git a/src/libvterm/t/10state_putglyph.test b/src/libvterm/t/10state_putglyph.test
new file mode 100644
index 0000000..6d5d56a
--- /dev/null
+++ b/src/libvterm/t/10state_putglyph.test
@@ -0,0 +1,61 @@
+INIT
+UTF8 1
+WANTSTATE g
+
+!Low
+RESET
+PUSH "ABC"
+ putglyph 0x41 1 0,0
+ putglyph 0x42 1 0,1
+ putglyph 0x43 1 0,2
+
+!UTF-8 1 char
+# U+00C1 = 0xC3 0x81 name: LATIN CAPITAL LETTER A WITH ACUTE
+# U+00E9 = 0xC3 0xA9 name: LATIN SMALL LETTER E WITH ACUTE
+RESET
+PUSH "\xC3\x81\xC3\xA9"
+ putglyph 0xc1 1 0,0
+ putglyph 0xe9 1 0,1
+
+!UTF-8 split writes
+RESET
+PUSH "\xC3"
+PUSH "\x81"
+ putglyph 0xc1 1 0,0
+
+!UTF-8 wide char
+# U+FF10 = 0xEF 0xBC 0x90 name: FULLWIDTH DIGIT ZERO
+RESET
+PUSH "\xEF\xBC\x90 "
+ putglyph 0xff10 2 0,0
+ putglyph 0x20 1 0,2
+
+!UTF-8 combining chars
+# U+0301 = 0xCC 0x81 name: COMBINING ACUTE
+RESET
+PUSH "e\xCC\x81Z"
+ putglyph 0x65,0x301 1 0,0
+ putglyph 0x5a 1 0,1
+
+!Combining across buffers
+RESET
+PUSH "e"
+ putglyph 0x65 1 0,0
+PUSH "\xCC\x81Z"
+ putglyph 0x65,0x301 1 0,0
+ putglyph 0x5a 1 0,1
+
+RESET
+PUSH "e"
+ putglyph 0x65 1 0,0
+PUSH "\xCC\x81"
+ putglyph 0x65,0x301 1 0,0
+PUSH "\xCC\x82"
+ putglyph 0x65,0x301,0x302 1 0,0
+
+!DECSCA protected
+RESET
+PUSH "A\e[1\"qB\e[2\"qC"
+ putglyph 0x41 1 0,0
+ putglyph 0x42 1 0,1 prot
+ putglyph 0x43 1 0,2
diff --git a/src/libvterm/t/11state_movecursor.test b/src/libvterm/t/11state_movecursor.test
new file mode 100644
index 0000000..c1d72b2
--- /dev/null
+++ b/src/libvterm/t/11state_movecursor.test
@@ -0,0 +1,224 @@
+INIT
+UTF8 1
+WANTSTATE
+
+!Implicit
+PUSH "ABC"
+ ?cursor = 0,3
+!Backspace
+PUSH "\b"
+ ?cursor = 0,2
+!Horizontal Tab
+PUSH "\t"
+ ?cursor = 0,8
+!Carriage Return
+PUSH "\r"
+ ?cursor = 0,0
+!Linefeed
+PUSH "\n"
+ ?cursor = 1,0
+
+!Backspace bounded by lefthand edge
+PUSH "\e[4;2H"
+ ?cursor = 3,1
+PUSH "\b"
+ ?cursor = 3,0
+PUSH "\b"
+ ?cursor = 3,0
+
+!Backspace cancels phantom
+PUSH "\e[4;80H"
+ ?cursor = 3,79
+PUSH "X"
+ ?cursor = 3,79
+PUSH "\b"
+ ?cursor = 3,78
+
+!HT bounded by righthand edge
+PUSH "\e[1;78H"
+ ?cursor = 0,77
+PUSH "\t"
+ ?cursor = 0,79
+PUSH "\t"
+ ?cursor = 0,79
+
+RESET
+
+!Index
+PUSH "ABC\eD"
+ ?cursor = 1,3
+!Reverse Index
+PUSH "\eM"
+ ?cursor = 0,3
+!Newline
+PUSH "\eE"
+ ?cursor = 1,0
+
+RESET
+
+!Cursor Forward
+PUSH "\e[B"
+ ?cursor = 1,0
+PUSH "\e[3B"
+ ?cursor = 4,0
+PUSH "\e[0B"
+ ?cursor = 5,0
+
+!Cursor Down
+PUSH "\e[C"
+ ?cursor = 5,1
+PUSH "\e[3C"
+ ?cursor = 5,4
+PUSH "\e[0C"
+ ?cursor = 5,5
+
+!Cursor Up
+PUSH "\e[A"
+ ?cursor = 4,5
+PUSH "\e[3A"
+ ?cursor = 1,5
+PUSH "\e[0A"
+ ?cursor = 0,5
+
+!Cursor Backward
+PUSH "\e[D"
+ ?cursor = 0,4
+PUSH "\e[3D"
+ ?cursor = 0,1
+PUSH "\e[0D"
+ ?cursor = 0,0
+
+!Cursor Next Line
+PUSH " "
+ ?cursor = 0,3
+PUSH "\e[E"
+ ?cursor = 1,0
+PUSH " "
+ ?cursor = 1,3
+PUSH "\e[2E"
+ ?cursor = 3,0
+PUSH "\e[0E"
+ ?cursor = 4,0
+
+!Cursor Previous Line
+PUSH " "
+ ?cursor = 4,3
+PUSH "\e[F"
+ ?cursor = 3,0
+PUSH " "
+ ?cursor = 3,3
+PUSH "\e[2F"
+ ?cursor = 1,0
+PUSH "\e[0F"
+ ?cursor = 0,0
+
+!Cursor Horizonal Absolute
+PUSH "\n"
+ ?cursor = 1,0
+PUSH "\e[20G"
+ ?cursor = 1,19
+PUSH "\e[G"
+ ?cursor = 1,0
+
+!Cursor Position
+PUSH "\e[10;5H"
+ ?cursor = 9,4
+PUSH "\e[8H"
+ ?cursor = 7,0
+PUSH "\e[H"
+ ?cursor = 0,0
+
+!Cursor Position cancels phantom
+PUSH "\e[10;78H"
+ ?cursor = 9,77
+PUSH "ABC"
+ ?cursor = 9,79
+PUSH "\e[10;80H"
+PUSH "C"
+ ?cursor = 9,79
+PUSH "X"
+ ?cursor = 10,1
+
+RESET
+
+!Bounds Checking
+PUSH "\e[A"
+ ?cursor = 0,0
+PUSH "\e[D"
+ ?cursor = 0,0
+PUSH "\e[25;80H"
+ ?cursor = 24,79
+PUSH "\e[B"
+ ?cursor = 24,79
+PUSH "\e[C"
+ ?cursor = 24,79
+PUSH "\e[E"
+ ?cursor = 24,0
+PUSH "\e[H"
+ ?cursor = 0,0
+PUSH "\e[F"
+ ?cursor = 0,0
+PUSH "\e[999G"
+ ?cursor = 0,79
+PUSH "\e[99;99H"
+ ?cursor = 24,79
+
+RESET
+
+!Horizontal Position Absolute
+PUSH "\e[5`"
+ ?cursor = 0,4
+
+!Horizontal Position Relative
+PUSH "\e[3a"
+ ?cursor = 0,7
+
+!Horizontal Position Backward
+PUSH "\e[3j"
+ ?cursor = 0,4
+
+!Horizontal and Vertical Position
+PUSH "\e[3;3f"
+ ?cursor = 2,2
+
+!Vertical Position Absolute
+PUSH "\e[5d"
+ ?cursor = 4,2
+
+!Vertical Position Relative
+PUSH "\e[2e"
+ ?cursor = 6,2
+
+!Vertical Position Backward
+PUSH "\e[2k"
+ ?cursor = 4,2
+
+RESET
+
+!Horizontal Tab
+PUSH "\t"
+ ?cursor = 0,8
+PUSH " "
+ ?cursor = 0,11
+PUSH "\t"
+ ?cursor = 0,16
+PUSH " "
+ ?cursor = 0,23
+PUSH "\t"
+ ?cursor = 0,24
+PUSH " "
+ ?cursor = 0,32
+PUSH "\t"
+ ?cursor = 0,40
+
+!Cursor Horizontal Tab
+PUSH "\e[I"
+ ?cursor = 0,48
+PUSH "\e[2I"
+ ?cursor = 0,64
+
+!Cursor Backward Tab
+PUSH "\e[Z"
+ ?cursor = 0,56
+PUSH "\e[2Z"
+ ?cursor = 0,40
diff --git a/src/libvterm/t/12state_scroll.test b/src/libvterm/t/12state_scroll.test
new file mode 100644
index 0000000..ca305d4
--- /dev/null
+++ b/src/libvterm/t/12state_scroll.test
@@ -0,0 +1,150 @@
+INIT
+UTF8 1
+WANTSTATE s
+
+!Linefeed
+PUSH "\n"x24
+ ?cursor = 24,0
+PUSH "\n"
+ scrollrect 0..25,0..80 => +1,+0
+ ?cursor = 24,0
+
+RESET
+
+!Index
+PUSH "\e[25H"
+PUSH "\eD"
+ scrollrect 0..25,0..80 => +1,+0
+
+RESET
+
+!Reverse Index
+PUSH "\eM"
+ scrollrect 0..25,0..80 => -1,+0
+
+RESET
+
+!Linefeed in DECSTBM
+PUSH "\e[1;10r"
+ ?cursor = 0,0
+PUSH "\n"x9
+ ?cursor = 9,0
+PUSH "\n"
+ scrollrect 0..10,0..80 => +1,+0
+ ?cursor = 9,0
+
+!Linefeed outside DECSTBM
+PUSH "\e[20H"
+ ?cursor = 19,0
+PUSH "\n"
+ ?cursor = 20,0
+
+!Index in DECSTBM
+PUSH "\e[10H"
+PUSH "\e[9;10r"
+PUSH "\eM"
+ ?cursor = 8,0
+PUSH "\eM"
+ scrollrect 8..10,0..80 => -1,+0
+
+!Reverse Index in DECSTBM
+PUSH "\e[25H"
+ ?cursor = 24,0
+PUSH "\n"
+ # no scrollrect
+ ?cursor = 24,0
+
+!Linefeed in DECSTBM+DECSLRM
+PUSH "\e[?69h"
+PUSH "\e[3;10r\e[10;40s"
+PUSH "\e[10;10H\n"
+ scrollrect 2..10,9..40 => +1,+0
+
+!IND/RI in DECSTBM+DECSLRM
+PUSH "\eD"
+ scrollrect 2..10,9..40 => +1,+0
+PUSH "\e[3;10H\eM"
+ scrollrect 2..10,9..40 => -1,+0
+
+!DECRQSS on DECSTBM
+PUSH "\eP\$qr\e\\"
+ output "\eP1\$r3;10r\e\\"
+
+!DECRQSS on DECSLRM
+PUSH "\eP\$qs\e\\"
+ output "\eP1\$r10;40s\e\\"
+
+!Setting invalid DECSLRM with !DECVSSM is still rejected
+PUSH "\e[?69l\e[;0s\e[?69h"
+
+RESET
+
+!Scroll Down
+PUSH "\e[S"
+ scrollrect 0..25,0..80 => +1,+0
+ ?cursor = 0,0
+PUSH "\e[2S"
+ scrollrect 0..25,0..80 => +2,+0
+ ?cursor = 0,0
+PUSH "\e[100S"
+ scrollrect 0..25,0..80 => +25,+0
+
+!Scroll Up
+PUSH "\e[T"
+ scrollrect 0..25,0..80 => -1,+0
+ ?cursor = 0,0
+PUSH "\e[2T"
+ scrollrect 0..25,0..80 => -2,+0
+ ?cursor = 0,0
+PUSH "\e[100T"
+ scrollrect 0..25,0..80 => -25,+0
+
+!SD/SU in DECSTBM
+PUSH "\e[5;20r"
+PUSH "\e[S"
+ scrollrect 4..20,0..80 => +1,+0
+PUSH "\e[T"
+ scrollrect 4..20,0..80 => -1,+0
+
+RESET
+
+!SD/SU in DECSTBM+DECSLRM
+PUSH "\e[?69h"
+PUSH "\e[3;10r\e[10;40s"
+ ?cursor = 0,0
+PUSH "\e[3;10H"
+ ?cursor = 2,9
+PUSH "\e[S"
+ scrollrect 2..10,9..40 => +1,+0
+PUSH "\e[?69l"
+PUSH "\e[S"
+ scrollrect 2..10,0..80 => +1,+0
+
+!Invalid boundaries
+RESET
+
+PUSH "\e[100;105r\eD"
+PUSH "\e[5;2r\eD"
+
+RESET
+WANTSTATE -s+me
+
+!Scroll Down move+erase emulation
+PUSH "\e[S"
+ moverect 1..25,0..80 -> 0..24,0..80
+ erase 24..25,0..80
+ ?cursor = 0,0
+PUSH "\e[2S"
+ moverect 2..25,0..80 -> 0..23,0..80
+ erase 23..25,0..80
+ ?cursor = 0,0
+
+!Scroll Up move+erase emulation
+PUSH "\e[T"
+ moverect 0..24,0..80 -> 1..25,0..80
+ erase 0..1,0..80
+ ?cursor = 0,0
+PUSH "\e[2T"
+ moverect 0..23,0..80 -> 2..25,0..80
+ erase 0..2,0..80
+ ?cursor = 0,0
diff --git a/src/libvterm/t/13state_edit.test b/src/libvterm/t/13state_edit.test
new file mode 100644
index 0000000..b435655
--- /dev/null
+++ b/src/libvterm/t/13state_edit.test
@@ -0,0 +1,300 @@
+INIT
+UTF8 1
+WANTSTATE se
+
+!ICH
+RESET
+ erase 0..25,0..80
+ ?cursor = 0,0
+PUSH "ACD"
+PUSH "\e[2D"
+ ?cursor = 0,1
+PUSH "\e[@"
+ scrollrect 0..1,1..80 => +0,-1
+ ?cursor = 0,1
+PUSH "B"
+ ?cursor = 0,2
+PUSH "\e[3@"
+ scrollrect 0..1,2..80 => +0,-3
+
+!ICH with DECSLRM
+PUSH "\e[?69h"
+PUSH "\e[;50s"
+PUSH "\e[20G\e[@"
+ scrollrect 0..1,19..50 => +0,-1
+
+!ICH outside DECSLRM
+PUSH "\e[70G\e[@"
+ # nothing happens
+
+!DCH
+RESET
+ erase 0..25,0..80
+ ?cursor = 0,0
+PUSH "ABBC"
+PUSH "\e[3D"
+ ?cursor = 0,1
+PUSH "\e[P"
+ scrollrect 0..1,1..80 => +0,+1
+ ?cursor = 0,1
+PUSH "\e[3P"
+ scrollrect 0..1,1..80 => +0,+3
+ ?cursor = 0,1
+
+!DCH with DECSLRM
+PUSH "\e[?69h"
+PUSH "\e[;50s"
+PUSH "\e[20G\e[P"
+ scrollrect 0..1,19..50 => +0,+1
+
+!DCH outside DECSLRM
+PUSH "\e[70G\e[P"
+ # nothing happens
+
+!ECH
+RESET
+ erase 0..25,0..80
+ ?cursor = 0,0
+PUSH "ABC"
+PUSH "\e[2D"
+ ?cursor = 0,1
+PUSH "\e[X"
+ erase 0..1,1..2
+ ?cursor = 0,1
+PUSH "\e[3X"
+ erase 0..1,1..4
+ ?cursor = 0,1
+# ECH more columns than there are should be bounded
+PUSH "\e[100X"
+ erase 0..1,1..80
+
+!IL
+RESET
+ erase 0..25,0..80
+ ?cursor = 0,0
+PUSH "A\r\nC"
+ ?cursor = 1,1
+PUSH "\e[L"
+ scrollrect 1..25,0..80 => -1,+0
+ # TODO: ECMA-48 says we should move to line home, but neither xterm nor
+ # xfce4-terminal do this
+ ?cursor = 1,1
+PUSH "\rB"
+ ?cursor = 1,1
+PUSH "\e[3L"
+ scrollrect 1..25,0..80 => -3,+0
+
+!IL with DECSTBM
+PUSH "\e[5;15r"
+PUSH "\e[5H\e[L"
+ scrollrect 4..15,0..80 => -1,+0
+
+!IL outside DECSTBM
+PUSH "\e[20H\e[L"
+ # nothing happens
+
+!IL with DECSTBM+DECSLRM
+PUSH "\e[?69h"
+PUSH "\e[10;50s"
+PUSH "\e[5;10H\e[L"
+ scrollrect 4..15,9..50 => -1,+0
+
+!DL
+RESET
+ erase 0..25,0..80
+ ?cursor = 0,0
+PUSH "A\r\nB\r\nB\r\nC"
+ ?cursor = 3,1
+PUSH "\e[2H"
+ ?cursor = 1,0
+PUSH "\e[M"
+ scrollrect 1..25,0..80 => +1,+0
+ ?cursor = 1,0
+PUSH "\e[3M"
+ scrollrect 1..25,0..80 => +3,+0
+ ?cursor = 1,0
+
+!DL with DECSTBM
+PUSH "\e[5;15r"
+PUSH "\e[5H\e[M"
+ scrollrect 4..15,0..80 => +1,+0
+
+!DL outside DECSTBM
+PUSH "\e[20H\e[M"
+ # nothing happens
+
+!DL with DECSTBM+DECSLRM
+PUSH "\e[?69h"
+PUSH "\e[10;50s"
+PUSH "\e[5;10H\e[M"
+ scrollrect 4..15,9..50 => +1,+0
+
+!DECIC
+RESET
+ erase 0..25,0..80
+PUSH "\e[20G\e[5'}"
+ scrollrect 0..25,19..80 => +0,-5
+
+!DECIC with DECSTBM+DECSLRM
+PUSH "\e[?69h"
+PUSH "\e[4;20r\e[20;60s"
+PUSH "\e[4;20H\e[3'}"
+ scrollrect 3..20,19..60 => +0,-3
+
+!DECIC outside DECSLRM
+PUSH "\e[70G\e['}"
+ # nothing happens
+
+!DECDC
+RESET
+ erase 0..25,0..80
+PUSH "\e[20G\e[5'~"
+ scrollrect 0..25,19..80 => +0,+5
+
+!DECDC with DECSTBM+DECSLRM
+PUSH "\e[?69h"
+PUSH "\e[4;20r\e[20;60s"
+PUSH "\e[4;20H\e[3'~"
+ scrollrect 3..20,19..60 => +0,+3
+
+!DECDC outside DECSLRM
+PUSH "\e[70G\e['~"
+ # nothing happens
+
+!EL 0
+RESET
+ erase 0..25,0..80
+ ?cursor = 0,0
+PUSH "ABCDE"
+PUSH "\e[3D"
+ ?cursor = 0,2
+PUSH "\e[0K"
+ erase 0..1,2..80
+ ?cursor = 0,2
+
+!EL 1
+RESET
+ erase 0..25,0..80
+ ?cursor = 0,0
+PUSH "ABCDE"
+PUSH "\e[3D"
+ ?cursor = 0,2
+PUSH "\e[1K"
+ erase 0..1,0..3
+ ?cursor = 0,2
+
+!EL 2
+RESET
+ erase 0..25,0..80
+ ?cursor = 0,0
+PUSH "ABCDE"
+PUSH "\e[3D"
+ ?cursor = 0,2
+PUSH "\e[2K"
+ erase 0..1,0..80
+ ?cursor = 0,2
+
+!SEL
+RESET
+ erase 0..25,0..80
+ ?cursor = 0,0
+PUSH "\e[11G"
+ ?cursor = 0,10
+PUSH "\e[?0K"
+ erase 0..1,10..80 selective
+ ?cursor = 0,10
+PUSH "\e[?1K"
+ erase 0..1,0..11 selective
+ ?cursor = 0,10
+PUSH "\e[?2K"
+ erase 0..1,0..80 selective
+ ?cursor = 0,10
+
+!ED 0
+RESET
+ erase 0..25,0..80
+ ?cursor = 0,0
+PUSH "\e[2;2H"
+ ?cursor = 1,1
+PUSH "\e[0J"
+ erase 1..2,1..80
+ erase 2..25,0..80
+ ?cursor = 1,1
+
+!ED 1
+RESET
+ erase 0..25,0..80
+ ?cursor = 0,0
+PUSH "\e[2;2H"
+ ?cursor = 1,1
+PUSH "\e[1J"
+ erase 0..1,0..80
+ erase 1..2,0..2
+ ?cursor = 1,1
+
+!ED 2
+RESET
+ erase 0..25,0..80
+ ?cursor = 0,0
+PUSH "\e[2;2H"
+ ?cursor = 1,1
+PUSH "\e[2J"
+ erase 0..25,0..80
+ ?cursor = 1,1
+
+!SED
+RESET
+ erase 0..25,0..80
+PUSH "\e[5;5H"
+ ?cursor = 4,4
+PUSH "\e[?0J"
+ erase 4..5,4..80 selective
+ erase 5..25,0..80 selective
+ ?cursor = 4,4
+PUSH "\e[?1J"
+ erase 0..4,0..80 selective
+ erase 4..5,0..5 selective
+ ?cursor = 4,4
+PUSH "\e[?2J"
+ erase 0..25,0..80 selective
+ ?cursor = 4,4
+
+!DECRQSS on DECSCA
+PUSH "\e[2\"q"
+PUSH "\eP\$q\"q\e\\"
+ output "\eP1\$r2\"q\e\\"
+
+WANTSTATE -s+m
+
+!ICH move+erase emuation
+RESET
+ erase 0..25,0..80
+ ?cursor = 0,0
+PUSH "ACD"
+PUSH "\e[2D"
+ ?cursor = 0,1
+PUSH "\e[@"
+ moverect 0..1,1..79 -> 0..1,2..80
+ erase 0..1,1..2
+ ?cursor = 0,1
+PUSH "B"
+ ?cursor = 0,2
+PUSH "\e[3@"
+ moverect 0..1,2..77 -> 0..1,5..80
+ erase 0..1,2..5
+
+!DCH move+erase emulation
+RESET
+ erase 0..25,0..80
+ ?cursor = 0,0
+PUSH "ABBC"
+PUSH "\e[3D"
+ ?cursor = 0,1
+PUSH "\e[P"
+ moverect 0..1,2..80 -> 0..1,1..79
+ erase 0..1,79..80
+ ?cursor = 0,1
+PUSH "\e[3P"
+ moverect 0..1,4..80 -> 0..1,1..77
+ erase 0..1,77..80
+ ?cursor = 0,1
diff --git a/src/libvterm/t/14state_encoding.test b/src/libvterm/t/14state_encoding.test
new file mode 100644
index 0000000..b1f5d69
--- /dev/null
+++ b/src/libvterm/t/14state_encoding.test
@@ -0,0 +1,105 @@
+INIT
+WANTSTATE g
+
+!Default
+RESET
+PUSH "#"
+ putglyph 0x23 1 0,0
+
+!Designate G0=UK
+RESET
+PUSH "\e(A"
+PUSH "#"
+ putglyph 0x00a3 1 0,0
+
+!Designate G0=DEC drawing
+RESET
+PUSH "\e(0"
+PUSH "a"
+ putglyph 0x2592 1 0,0
+
+!Designate G1 + LS1
+RESET
+PUSH "\e)0"
+PUSH "a"
+ putglyph 0x61 1 0,0
+PUSH "\x0e"
+PUSH "a"
+ putglyph 0x2592 1 0,1
+!LS0
+PUSH "\x0f"
+PUSH "a"
+ putglyph 0x61 1 0,2
+
+!Designate G2 + LS2
+PUSH "\e*0"
+PUSH "a"
+ putglyph 0x61 1 0,3
+PUSH "\en"
+PUSH "a"
+ putglyph 0x2592 1 0,4
+PUSH "\x0f"
+PUSH "a"
+ putglyph 0x61 1 0,5
+
+!Designate G3 + LS3
+PUSH "\e+0"
+PUSH "a"
+ putglyph 0x61 1 0,6
+PUSH "\eo"
+PUSH "a"
+ putglyph 0x2592 1 0,7
+PUSH "\x0f"
+PUSH "a"
+ putglyph 0x61 1 0,8
+
+!SS2
+PUSH "a\x{8e}aa"
+ putglyph 0x61 1 0,9
+ putglyph 0x2592 1 0,10
+ putglyph 0x61 1 0,11
+
+!SS3
+PUSH "a\x{8f}aa"
+ putglyph 0x61 1 0,12
+ putglyph 0x2592 1 0,13
+ putglyph 0x61 1 0,14
+
+!LS1R
+RESET
+PUSH "\e~"
+PUSH "\xe1"
+ putglyph 0x61 1 0,0
+PUSH "\e)0"
+PUSH "\xe1"
+ putglyph 0x2592 1 0,1
+
+!LS2R
+RESET
+PUSH "\e}"
+PUSH "\xe1"
+ putglyph 0x61 1 0,0
+PUSH "\e*0"
+PUSH "\xe1"
+ putglyph 0x2592 1 0,1
+
+!LS3R
+RESET
+PUSH "\e|"
+PUSH "\xe1"
+ putglyph 0x61 1 0,0
+PUSH "\e+0"
+PUSH "\xe1"
+ putglyph 0x2592 1 0,1
+
+UTF8 1
+
+!Mixed US-ASCII and UTF-8
+# U+0108 == 0xc4 0x88
+RESET
+PUSH "\e(B"
+PUSH "AB\xc4\x88D"
+ putglyph 0x0041 1 0,0
+ putglyph 0x0042 1 0,1
+ putglyph 0x0108 1 0,2
+ putglyph 0x0044 1 0,3
diff --git a/src/libvterm/t/15state_mode.test b/src/libvterm/t/15state_mode.test
new file mode 100644
index 0000000..b7917ad
--- /dev/null
+++ b/src/libvterm/t/15state_mode.test
@@ -0,0 +1,86 @@
+INIT
+UTF8 1
+WANTSTATE gme
+
+!Insert/Replace Mode
+RESET
+ erase 0..25,0..80
+ ?cursor = 0,0
+PUSH "AC\e[DB"
+ putglyph 0x41 1 0,0
+ putglyph 0x43 1 0,1
+ putglyph 0x42 1 0,1
+PUSH "\e[4h"
+PUSH "\e[G"
+PUSH "AC\e[DB"
+ moverect 0..1,0..79 -> 0..1,1..80
+ erase 0..1,0..1
+ putglyph 0x41 1 0,0
+ moverect 0..1,1..79 -> 0..1,2..80
+ erase 0..1,1..2
+ putglyph 0x43 1 0,1
+ moverect 0..1,1..79 -> 0..1,2..80
+ erase 0..1,1..2
+ putglyph 0x42 1 0,1
+
+!Insert mode only happens once for UTF-8 combining
+PUSH "e"
+ moverect 0..1,2..79 -> 0..1,3..80
+ erase 0..1,2..3
+ putglyph 0x65 1 0,2
+PUSH "\xCC\x81"
+ putglyph 0x65,0x301 1 0,2
+
+!Newline/Linefeed mode
+RESET
+ erase 0..25,0..80
+ ?cursor = 0,0
+PUSH "\e[5G\n"
+ ?cursor = 1,4
+PUSH "\e[20h"
+PUSH "\e[5G\n"
+ ?cursor = 2,0
+
+!DEC origin mode
+RESET
+ erase 0..25,0..80
+ ?cursor = 0,0
+PUSH "\e[5;15r"
+PUSH "\e[H"
+ ?cursor = 0,0
+PUSH "\e[3;3H"
+ ?cursor = 2,2
+PUSH "\e[?6h"
+PUSH "\e[H"
+ ?cursor = 4,0
+PUSH "\e[3;3H"
+ ?cursor = 6,2
+
+!DECRQM on DECOM
+PUSH "\e[?6h"
+PUSH "\e[?6\$p"
+ output "\e[?6;1\$y"
+PUSH "\e[?6l"
+PUSH "\e[?6\$p"
+ output "\e[?6;2\$y"
+
+!Origin mode with DECSLRM
+PUSH "\e[?6h"
+PUSH "\e[?69h"
+PUSH "\e[20;60s"
+PUSH "\e[H"
+ ?cursor = 4,19
+
+PUSH "\e[?69l"
+
+!Origin mode bounds cursor to scrolling region
+PUSH "\e[H"
+PUSH "\e[10A"
+ ?cursor = 4,0
+PUSH "\e[20B"
+ ?cursor = 14,0
+
+!Origin mode without scroll region
+PUSH "\e[?6l"
+PUSH "\e[r\e[?6h"
+ ?cursor = 0,0
diff --git a/src/libvterm/t/16state_resize.test b/src/libvterm/t/16state_resize.test
new file mode 100644
index 0000000..42c77c7
--- /dev/null
+++ b/src/libvterm/t/16state_resize.test
@@ -0,0 +1,48 @@
+INIT
+WANTSTATE g
+
+!Placement
+RESET
+PUSH "AB\e[79GCDE"
+ putglyph 0x41 1 0,0
+ putglyph 0x42 1 0,1
+ putglyph 0x43 1 0,78
+ putglyph 0x44 1 0,79
+ putglyph 0x45 1 1,0
+
+!Resize
+RESET
+RESIZE 27,85
+PUSH "AB\e[79GCDE"
+ putglyph 0x41 1 0,0
+ putglyph 0x42 1 0,1
+ putglyph 0x43 1 0,78
+ putglyph 0x44 1 0,79
+ putglyph 0x45 1 0,80
+ ?cursor = 0,81
+
+!Resize without reset
+RESIZE 28,90
+ ?cursor = 0,81
+PUSH "FGHI"
+ putglyph 0x46 1 0,81
+ putglyph 0x47 1 0,82
+ putglyph 0x48 1 0,83
+ putglyph 0x49 1 0,84
+ ?cursor = 0,85
+
+!Resize shrink moves cursor
+RESIZE 25,80
+ ?cursor = 0,79
+
+!Resize grow doesn't cancel phantom
+RESET
+PUSH "\e[79GAB"
+ putglyph 0x41 1 0,78
+ putglyph 0x42 1 0,79
+ ?cursor = 0,79
+RESIZE 30,100
+ ?cursor = 0,80
+PUSH "C"
+ putglyph 0x43 1 0,80
+ ?cursor = 0,81
diff --git a/src/libvterm/t/17state_mouse.test b/src/libvterm/t/17state_mouse.test
new file mode 100644
index 0000000..c39f56b
--- /dev/null
+++ b/src/libvterm/t/17state_mouse.test
@@ -0,0 +1,172 @@
+INIT
+WANTSTATE p
+
+!DECRQM on with mouse off
+PUSH "\e[?1000\$p"
+ output "\e[?1000;2\$y"
+PUSH "\e[?1002\$p"
+ output "\e[?1002;2\$y"
+PUSH "\e[?1003\$p"
+ output "\e[?1003;2\$y"
+
+!Mouse in simple button report mode
+RESET
+ settermprop 1 true
+ settermprop 2 true
+ settermprop 7 1
+PUSH "\e[?1000h"
+ settermprop 8 1
+
+!Press 1
+MOUSEMOVE 0,0 0
+MOUSEBTN d 1 0
+ output "\e[M\x20\x21\x21"
+
+!Release 1
+MOUSEBTN u 1 0
+ output "\e[M\x23\x21\x21"
+
+!Ctrl-Press 1
+MOUSEBTN d 1 C
+ output "\e[M\x30\x21\x21"
+MOUSEBTN u 1 C
+ output "\e[M\x33\x21\x21"
+
+!Button 2
+MOUSEBTN d 2 0
+ output "\e[M\x21\x21\x21"
+MOUSEBTN u 2 0
+ output "\e[M\x23\x21\x21"
+
+!Position
+MOUSEMOVE 10,20 0
+MOUSEBTN d 1 0
+ output "\e[M\x20\x35\x2b"
+
+MOUSEBTN u 1 0
+ output "\e[M\x23\x35\x2b"
+MOUSEMOVE 10,21 0
+ # no output
+
+!Wheel events
+MOUSEBTN d 4 0
+ output "\e[M\x60\x36\x2b"
+MOUSEBTN d 4 0
+ output "\e[M\x60\x36\x2b"
+MOUSEBTN d 5 0
+ output "\e[M\x61\x36\x2b"
+
+!DECRQM on mouse button mode
+PUSH "\e[?1000\$p"
+ output "\e[?1000;1\$y"
+PUSH "\e[?1002\$p"
+ output "\e[?1002;2\$y"
+PUSH "\e[?1003\$p"
+ output "\e[?1003;2\$y"
+
+!Drag events
+RESET
+ settermprop 1 true
+ settermprop 2 true
+ settermprop 7 1
+PUSH "\e[?1002h"
+ settermprop 8 2
+
+MOUSEMOVE 5,5 0
+MOUSEBTN d 1 0
+ output "\e[M\x20\x26\x26"
+MOUSEMOVE 5,6 0
+ output "\e[M\x40\x27\x26"
+MOUSEMOVE 6,6 0
+ output "\e[M\x40\x27\x27"
+MOUSEMOVE 6,6 0
+ # no output
+MOUSEBTN u 1 0
+ output "\e[M\x23\x27\x27"
+MOUSEMOVE 6,7
+ # no output
+
+!DECRQM on mouse drag mode
+PUSH "\e[?1000\$p"
+ output "\e[?1000;2\$y"
+PUSH "\e[?1002\$p"
+ output "\e[?1002;1\$y"
+PUSH "\e[?1003\$p"
+ output "\e[?1003;2\$y"
+
+!Non-drag motion events
+PUSH "\e[?1003h"
+ settermprop 8 3
+
+MOUSEMOVE 6,8 0
+ output "\e[M\x43\x29\x27"
+
+!DECRQM on mouse motion mode
+PUSH "\e[?1000\$p"
+ output "\e[?1000;2\$y"
+PUSH "\e[?1002\$p"
+ output "\e[?1002;2\$y"
+PUSH "\e[?1003\$p"
+ output "\e[?1003;1\$y"
+
+!Bounds checking
+MOUSEMOVE 300,300 0
+ output "\e[M\x43\xff\xff"
+MOUSEBTN d 1 0
+ output "\e[M\x20\xff\xff"
+MOUSEBTN u 1 0
+ output "\e[M\x23\xff\xff"
+
+!DECRQM on standard encoding mode
+PUSH "\e[?1005\$p"
+ output "\e[?1005;2\$y"
+PUSH "\e[?1006\$p"
+ output "\e[?1006;2\$y"
+PUSH "\e[?1015\$p"
+ output "\e[?1015;2\$y"
+
+!UTF-8 extended encoding mode
+# 300 + 32 + 1 = 333 = U+014d = \xc5\x8d
+PUSH "\e[?1005h"
+MOUSEBTN d 1 0
+ output "\e[M\x20\xc5\x8d\xc5\x8d"
+MOUSEBTN u 1 0
+ output "\e[M\x23\xc5\x8d\xc5\x8d"
+
+!DECRQM on UTF-8 extended encoding mode
+PUSH "\e[?1005\$p"
+ output "\e[?1005;1\$y"
+PUSH "\e[?1006\$p"
+ output "\e[?1006;2\$y"
+PUSH "\e[?1015\$p"
+ output "\e[?1015;2\$y"
+
+!SGR extended encoding mode
+PUSH "\e[?1006h"
+MOUSEBTN d 1 0
+ output "\e[<0;301;301M"
+MOUSEBTN u 1 0
+ output "\e[<0;301;301m"
+
+!DECRQM on SGR extended encoding mode
+PUSH "\e[?1005\$p"
+ output "\e[?1005;2\$y"
+PUSH "\e[?1006\$p"
+ output "\e[?1006;1\$y"
+PUSH "\e[?1015\$p"
+ output "\e[?1015;2\$y"
+
+!rxvt extended encoding mode
+PUSH "\e[?1015h"
+MOUSEBTN d 1 0
+ output "\e[0;301;301M"
+MOUSEBTN u 1 0
+ output "\e[3;301;301M"
+
+!DECRQM on rxvt extended encoding mode
+PUSH "\e[?1005\$p"
+ output "\e[?1005;2\$y"
+PUSH "\e[?1006\$p"
+ output "\e[?1006;2\$y"
+PUSH "\e[?1015\$p"
+ output "\e[?1015;1\$y"
diff --git a/src/libvterm/t/18state_termprops.test b/src/libvterm/t/18state_termprops.test
new file mode 100644
index 0000000..9e6928a
--- /dev/null
+++ b/src/libvterm/t/18state_termprops.test
@@ -0,0 +1,36 @@
+INIT
+WANTSTATE p
+
+RESET
+ settermprop 1 true
+ settermprop 2 true
+ settermprop 7 1
+
+!Cursor visibility
+PUSH "\e[?25h"
+ settermprop 1 true
+PUSH "\e[?25\$p"
+ output "\e[?25;1\$y"
+PUSH "\e[?25l"
+ settermprop 1 false
+PUSH "\e[?25\$p"
+ output "\e[?25;2\$y"
+
+!Cursor blink
+PUSH "\e[?12h"
+ settermprop 2 true
+PUSH "\e[?12\$p"
+ output "\e[?12;1\$y"
+PUSH "\e[?12l"
+ settermprop 2 false
+PUSH "\e[?12\$p"
+ output "\e[?12;2\$y"
+
+!Cursor shape
+PUSH "\e[3 q"
+ settermprop 2 true
+ settermprop 7 2
+
+!Title
+PUSH "\e]2;Here is my title\a"
+ settermprop 4 "Here is my title"
diff --git a/src/libvterm/t/20state_wrapping.test b/src/libvterm/t/20state_wrapping.test
new file mode 100644
index 0000000..606fa06
--- /dev/null
+++ b/src/libvterm/t/20state_wrapping.test
@@ -0,0 +1,69 @@
+INIT
+UTF8 1
+WANTSTATE gm
+
+!79th Column
+PUSH "\e[75G"
+PUSH "A"x5
+ putglyph 0x41 1 0,74
+ putglyph 0x41 1 0,75
+ putglyph 0x41 1 0,76
+ putglyph 0x41 1 0,77
+ putglyph 0x41 1 0,78
+ ?cursor = 0,79
+
+!80th Column Phantom
+PUSH "A"
+ putglyph 0x41 1 0,79
+ ?cursor = 0,79
+
+!Line Wraparound
+PUSH "B"
+ putglyph 0x42 1 1,0
+ ?cursor = 1,1
+
+!Line Wraparound during combined write
+PUSH "\e[78G"
+PUSH "BBBCC"
+ putglyph 0x42 1 1,77
+ putglyph 0x42 1 1,78
+ putglyph 0x42 1 1,79
+ putglyph 0x43 1 2,0
+ putglyph 0x43 1 2,1
+ ?cursor = 2,2
+
+!DEC Auto Wrap Mode
+RESET
+PUSH "\e[?7l"
+PUSH "\e[75G"
+PUSH "D"x6
+ putglyph 0x44 1 0,74
+ putglyph 0x44 1 0,75
+ putglyph 0x44 1 0,76
+ putglyph 0x44 1 0,77
+ putglyph 0x44 1 0,78
+ putglyph 0x44 1 0,79
+ ?cursor = 0,79
+PUSH "D"
+ putglyph 0x44 1 0,79
+ ?cursor = 0,79
+PUSH "\e[?7h"
+
+!80th column causes linefeed on wraparound
+PUSH "\e[25;78HABC"
+ putglyph 0x41 1 24,77
+ putglyph 0x42 1 24,78
+ putglyph 0x43 1 24,79
+ ?cursor = 24,79
+PUSH "D"
+ moverect 1..25,0..80 -> 0..24,0..80
+ putglyph 0x44 1 24,0
+
+!80th column phantom linefeed phantom cancelled by explicit cursor move
+PUSH "\e[25;78HABC"
+ putglyph 0x41 1 24,77
+ putglyph 0x42 1 24,78
+ putglyph 0x43 1 24,79
+ ?cursor = 24,79
+PUSH "\e[25;1HD"
+ putglyph 0x44 1 24,0
diff --git a/src/libvterm/t/21state_tabstops.test b/src/libvterm/t/21state_tabstops.test
new file mode 100644
index 0000000..df4a589
--- /dev/null
+++ b/src/libvterm/t/21state_tabstops.test
@@ -0,0 +1,60 @@
+INIT
+WANTSTATE g
+
+!Initial
+RESET
+PUSH "\tX"
+ putglyph 0x58 1 0,8
+PUSH "\tX"
+ putglyph 0x58 1 0,16
+ ?cursor = 0,17
+
+!HTS
+PUSH "\e[5G\eH"
+PUSH "\e[G\tX"
+ putglyph 0x58 1 0,4
+ ?cursor = 0,5
+
+!TBC 0
+PUSH "\e[9G\e[g"
+PUSH "\e[G\tX\tX"
+ putglyph 0x58 1 0,4
+ putglyph 0x58 1 0,16
+ ?cursor = 0,17
+
+!TBC 3
+PUSH "\e[3g\e[50G\eH\e[G"
+ ?cursor = 0,0
+PUSH "\tX"
+ putglyph 0x58 1 0,49
+ ?cursor = 0,50
+
+!Tabstops after resize
+RESET
+RESIZE 30,100
+# Should be 100/8 = 12 tabstops
+PUSH "\tX"
+ putglyph 0x58 1 0,8
+PUSH "\tX"
+ putglyph 0x58 1 0,16
+PUSH "\tX"
+ putglyph 0x58 1 0,24
+PUSH "\tX"
+ putglyph 0x58 1 0,32
+PUSH "\tX"
+ putglyph 0x58 1 0,40
+PUSH "\tX"
+ putglyph 0x58 1 0,48
+PUSH "\tX"
+ putglyph 0x58 1 0,56
+PUSH "\tX"
+ putglyph 0x58 1 0,64
+PUSH "\tX"
+ putglyph 0x58 1 0,72
+PUSH "\tX"
+ putglyph 0x58 1 0,80
+PUSH "\tX"
+ putglyph 0x58 1 0,88
+PUSH "\tX"
+ putglyph 0x58 1 0,96
+ ?cursor = 0,97
diff --git a/src/libvterm/t/22state_save.test b/src/libvterm/t/22state_save.test
new file mode 100644
index 0000000..81e9226
--- /dev/null
+++ b/src/libvterm/t/22state_save.test
@@ -0,0 +1,64 @@
+INIT
+WANTSTATE p
+
+RESET
+ settermprop 1 true
+ settermprop 2 true
+ settermprop 7 1
+
+!Set up state
+PUSH "\e[2;2H"
+ ?cursor = 1,1
+PUSH "\e[1m"
+ ?pen bold = on
+
+!Save
+PUSH "\e[?1048h"
+
+!Change state
+PUSH "\e[5;5H"
+ ?cursor = 4,4
+PUSH "\e[4 q"
+ settermprop 2 false
+ settermprop 7 2
+PUSH "\e[22;4m"
+ ?pen bold = off
+ ?pen underline = 1
+
+!Restore
+PUSH "\e[?1048l"
+ settermprop 1 true
+ settermprop 2 true
+ settermprop 7 1
+ ?cursor = 1,1
+ ?pen bold = on
+ ?pen underline = 0
+
+!Save/restore using DECSC/DECRC
+PUSH "\e[2;2H\e7"
+ ?cursor = 1,1
+
+PUSH "\e[5;5H"
+ ?cursor = 4,4
+PUSH "\e8"
+ settermprop 1 true
+ settermprop 2 true
+ settermprop 7 1
+ ?cursor = 1,1
+
+!Save twice, restore twice happens on both edge transitions
+PUSH "\e[2;10H\e[?1048h\e[6;10H\e[?1048h"
+PUSH "\e[H"
+ ?cursor = 0,0
+PUSH "\e[?1048l"
+ settermprop 1 true
+ settermprop 2 true
+ settermprop 7 1
+ ?cursor = 5,9
+PUSH "\e[H"
+ ?cursor = 0,0
+PUSH "\e[?1048l"
+ settermprop 1 true
+ settermprop 2 true
+ settermprop 7 1
+ ?cursor = 5,9
diff --git a/src/libvterm/t/25state_input.test b/src/libvterm/t/25state_input.test
new file mode 100644
index 0000000..a5119fb
--- /dev/null
+++ b/src/libvterm/t/25state_input.test
@@ -0,0 +1,143 @@
+INIT
+WANTSTATE
+
+!Unmodified ASCII
+INCHAR 0 41
+ output "A"
+INCHAR 0 61
+ output "a"
+
+!Ctrl modifier on ASCII letters
+INCHAR C 41
+ output "\e[65;5u"
+INCHAR C 61
+ output "\x01"
+
+!Alt modifier on ASCII letters
+INCHAR A 41
+ output "\eA"
+INCHAR A 61
+ output "\ea"
+
+!Ctrl-Alt modifier on ASCII letters
+INCHAR CA 41
+ output "\e[65;7u"
+INCHAR CA 61
+ output "\e\x01"
+
+!Special handling of Ctrl-I
+INCHAR 0 49
+ output "I"
+INCHAR 0 69
+ output "i"
+INCHAR C 49
+ output "\e[73;5u"
+INCHAR C 69
+ output "\e[105;5u"
+INCHAR A 49
+ output "\eI"
+INCHAR A 69
+ output "\ei"
+INCHAR CA 49
+ output "\e[73;7u"
+INCHAR CA 69
+ output "\e[105;7u"
+
+!Special handling of Space
+INCHAR 0 20
+ output " "
+INCHAR S 20
+ output "\e[32;2u"
+INCHAR C 20
+ output "\0"
+INCHAR SC 20
+ output "\e[32;6u"
+INCHAR A 20
+ output "\e "
+INCHAR SA 20
+ output "\e[32;4u"
+INCHAR CA 20
+ output "\e\0"
+INCHAR SCA 20
+ output "\e[32;8u"
+
+!Cursor keys in reset (cursor) mode
+INKEY 0 Up
+ output "\e[A"
+INKEY S Up
+ output "\e[1;2A"
+INKEY C Up
+ output "\e[1;5A"
+INKEY SC Up
+ output "\e[1;6A"
+INKEY A Up
+ output "\e[1;3A"
+INKEY SA Up
+ output "\e[1;4A"
+INKEY CA Up
+ output "\e[1;7A"
+INKEY SCA Up
+ output "\e[1;8A"
+
+!Cursor keys in application mode
+PUSH "\e[?1h"
+# Plain "Up" should be SS3 A now
+INKEY 0 Up
+ output "\eOA"
+# Modified keys should still use CSI
+INKEY S Up
+ output "\e[1;2A"
+INKEY C Up
+ output "\e[1;5A"
+
+!Shift-Tab should be different
+INKEY 0 Tab
+ output "\x09"
+INKEY S Tab
+ output "\e[Z"
+INKEY C Tab
+ output "\e[9;5u"
+INKEY A Tab
+ output "\e\x09"
+INKEY CA Tab
+ output "\e[9;7u"
+
+!Enter in linefeed mode
+INKEY 0 Enter
+ output "\x0d"
+
+!Enter in newline mode
+PUSH "\e[20h"
+INKEY 0 Enter
+ output "\x0d\x0a"
+
+!Keypad in DECKPNM
+INKEY 0 KP0
+ output "0"
+
+!Keypad in DECKPAM
+PUSH "\e="
+INKEY 0 KP0
+ output "\eOp"
+
+!Bracketed paste mode off
+PASTE START
+PASTE END
+
+!Bracketed paste mode on
+PUSH "\e[?2004h"
+PASTE START
+ output "\e[200~"
+PASTE END
+ output "\e[201~"
+
+!Focus reporting disabled
+FOCUS IN
+FOCUS OUT
+
+!Focus reporting enabled
+PUSH "\e[?1004h"
+FOCUS IN
+ output "\e[I"
+FOCUS OUT
+ output "\e[O"
diff --git a/src/libvterm/t/26state_query.test b/src/libvterm/t/26state_query.test
new file mode 100644
index 0000000..3ace2d5
--- /dev/null
+++ b/src/libvterm/t/26state_query.test
@@ -0,0 +1,62 @@
+INIT
+WANTSTATE
+
+!DA
+RESET
+PUSH "\e[c"
+ output "\e[?1;2c"
+
+!DSR
+RESET
+PUSH "\e[5n"
+ output "\e[0n"
+
+!CPR
+PUSH "\e[6n"
+ output "\e[1;1R"
+PUSH "\e[10;10H\e[6n"
+ output "\e[10;10R"
+
+!DECCPR
+PUSH "\e[?6n"
+ output "\e[?10;10R"
+
+!DECRQSS on DECSCUSR
+PUSH "\e[3 q"
+PUSH "\eP\$q q\e\\"
+ output "\eP1\$r3 q\e\\"
+
+!DECRQSS on SGR
+PUSH "\e[1;5;7m"
+PUSH "\eP\$qm\e\\"
+ output "\eP1\$r1;5;7m\e\\"
+
+!DECRQSS on SGR ANSI colours
+PUSH "\e[0;31;42m"
+PUSH "\eP\$qm\e\\"
+ output "\eP1\$r31;42m\e\\"
+
+!DECRQSS on SGR ANSI hi-bright colours
+PUSH "\e[0;93;104m"
+PUSH "\eP\$qm\e\\"
+ output "\eP1\$r93;104m\e\\"
+
+!DECRQSS on SGR 256-palette colours
+PUSH "\e[0;38:5:56;48:5:78m"
+PUSH "\eP\$qm\e\\"
+ output "\eP1\$r38:5:56;48:5:78m\e\\"
+
+!DECRQSS on SGR RGB8 colours
+PUSH "\e[0;38:2:24:68:112;48:2:13:57:101m"
+PUSH "\eP\$qm\e\\"
+ output "\eP1\$r38:2:24:68:112;48:2:13:57:101m\e\\"
+
+!S8C1T on DSR
+PUSH "\e G"
+PUSH "\e[5n"
+ output "\x{9b}0n"
+PUSH "\e F"
+
+!Truncation on attempted buffer overflow
+PUSH "\e[6n" x 30
+ output "\e[10;10R" x 24
diff --git a/src/libvterm/t/27state_reset.test b/src/libvterm/t/27state_reset.test
new file mode 100644
index 0000000..254f994
--- /dev/null
+++ b/src/libvterm/t/27state_reset.test
@@ -0,0 +1,32 @@
+INIT
+WANTSTATE
+
+RESET
+
+!RIS homes cursor
+PUSH "\e[5;5H"
+ ?cursor = 4,4
+WANTSTATE +m
+PUSH "\ec"
+ ?cursor = 0,0
+WANTSTATE -m
+
+!RIS cancels scrolling region
+PUSH "\e[5;10r"
+WANTSTATE +s
+PUSH "\ec\e[25H\n"
+ scrollrect 0..25,0..80 => +1,+0
+WANTSTATE -s
+
+!RIS erases screen
+PUSH "ABCDE"
+WANTSTATE +e
+PUSH "\ec"
+ erase 0..25,0..80
+WANTSTATE -e
+
+!RIS clears tabstops
+PUSH "\e[5G\eH\e[G\t"
+ ?cursor = 0,4
+PUSH "\ec\t"
+ ?cursor = 0,8
diff --git a/src/libvterm/t/28state_dbl_wh.test b/src/libvterm/t/28state_dbl_wh.test
new file mode 100644
index 0000000..596194d
--- /dev/null
+++ b/src/libvterm/t/28state_dbl_wh.test
@@ -0,0 +1,61 @@
+INIT
+WANTSTATE g
+
+!Single Width, Single Height
+RESET
+PUSH "\e#5"
+PUSH "Hello"
+ putglyph 0x48 1 0,0
+ putglyph 0x65 1 0,1
+ putglyph 0x6c 1 0,2
+ putglyph 0x6c 1 0,3
+ putglyph 0x6f 1 0,4
+
+!Double Width, Single Height
+RESET
+PUSH "\e#6"
+PUSH "Hello"
+ putglyph 0x48 1 0,0 dwl
+ putglyph 0x65 1 0,1 dwl
+ putglyph 0x6c 1 0,2 dwl
+ putglyph 0x6c 1 0,3 dwl
+ putglyph 0x6f 1 0,4 dwl
+ ?cursor = 0,5
+PUSH "\e[40GAB"
+ putglyph 0x41 1 0,39 dwl
+ putglyph 0x42 1 1,0
+ ?cursor = 1,1
+
+!Double Height
+RESET
+PUSH "\e#3"
+PUSH "Hello"
+ putglyph 0x48 1 0,0 dwl dhl-top
+ putglyph 0x65 1 0,1 dwl dhl-top
+ putglyph 0x6c 1 0,2 dwl dhl-top
+ putglyph 0x6c 1 0,3 dwl dhl-top
+ putglyph 0x6f 1 0,4 dwl dhl-top
+ ?cursor = 0,5
+PUSH "\r\n\e#4"
+PUSH "Hello"
+ putglyph 0x48 1 1,0 dwl dhl-bottom
+ putglyph 0x65 1 1,1 dwl dhl-bottom
+ putglyph 0x6c 1 1,2 dwl dhl-bottom
+ putglyph 0x6c 1 1,3 dwl dhl-bottom
+ putglyph 0x6f 1 1,4 dwl dhl-bottom
+ ?cursor = 1,5
+
+!Double Width scrolling
+RESET
+PUSH "\e[20H\e#6ABC"
+ putglyph 0x41 1 19,0 dwl
+ putglyph 0x42 1 19,1 dwl
+ putglyph 0x43 1 19,2 dwl
+PUSH "\e[25H\n"
+PUSH "\e[19;4HDE"
+ putglyph 0x44 1 18,3 dwl
+ putglyph 0x45 1 18,4 dwl
+PUSH "\e[H\eM"
+PUSH "\e[20;6HFG"
+ putglyph 0x46 1 19,5 dwl
+ putglyph 0x47 1 19,6 dwl
diff --git a/src/libvterm/t/29state_fallback.test b/src/libvterm/t/29state_fallback.test
new file mode 100644
index 0000000..adf1c23
--- /dev/null
+++ b/src/libvterm/t/29state_fallback.test
@@ -0,0 +1,19 @@
+INIT
+WANTSTATE f
+RESET
+
+!Unrecognised control
+PUSH "\x03"
+ control 03
+
+!Unrecognised CSI
+PUSH "\e[?15;2z"
+ csi 0x7a L=3f 15,2
+
+!Unrecognised OSC
+PUSH "\e]27;Something\e\\"
+ osc "27;Something"
+
+!Unrecognised DCS
+PUSH "\ePz123\e\\"
+ dcs "z123"
diff --git a/src/libvterm/t/30pen.test b/src/libvterm/t/30pen.test
new file mode 100644
index 0000000..7a671e7
--- /dev/null
+++ b/src/libvterm/t/30pen.test
@@ -0,0 +1,106 @@
+INIT
+UTF8 1
+WANTSTATE
+
+!Reset
+PUSH "\e[m"
+ ?pen bold = off
+ ?pen underline = 0
+ ?pen italic = off
+ ?pen blink = off
+ ?pen reverse = off
+ ?pen font = 0
+ ?pen foreground = rgb(240,240,240)
+ ?pen background = rgb(0,0,0)
+
+!Bold
+PUSH "\e[1m"
+ ?pen bold = on
+PUSH "\e[22m"
+ ?pen bold = off
+PUSH "\e[1m\e[m"
+ ?pen bold = off
+
+!Underline
+PUSH "\e[4m"
+ ?pen underline = 1
+PUSH "\e[21m"
+ ?pen underline = 2
+PUSH "\e[24m"
+ ?pen underline = 0
+PUSH "\e[4m\e[m"
+ ?pen underline = 0
+
+!Italic
+PUSH "\e[3m"
+ ?pen italic = on
+PUSH "\e[23m"
+ ?pen italic = off
+PUSH "\e[3m\e[m"
+ ?pen italic = off
+
+!Blink
+PUSH "\e[5m"
+ ?pen blink = on
+PUSH "\e[25m"
+ ?pen blink = off
+PUSH "\e[5m\e[m"
+ ?pen blink = off
+
+!Reverse
+PUSH "\e[7m"
+ ?pen reverse = on
+PUSH "\e[27m"
+ ?pen reverse = off
+PUSH "\e[7m\e[m"
+ ?pen reverse = off
+
+!Font Selection
+PUSH "\e[11m"
+ ?pen font = 1
+PUSH "\e[19m"
+ ?pen font = 9
+PUSH "\e[10m"
+ ?pen font = 0
+PUSH "\e[11m\e[m"
+ ?pen font = 0
+
+!Foreground
+PUSH "\e[31m"
+ ?pen foreground = rgb(224,0,0)
+PUSH "\e[32m"
+ ?pen foreground = rgb(0,224,0)
+PUSH "\e[34m"
+ ?pen foreground = rgb(0,0,224)
+PUSH "\e[91m"
+ ?pen foreground = rgb(255,64,64)
+PUSH "\e[38:2:10:20:30m"
+ ?pen foreground = rgb(10,20,30)
+PUSH "\e[38:5:1m"
+ ?pen foreground = rgb(224,0,0)
+PUSH "\e[39m"
+ ?pen foreground = rgb(240,240,240)
+
+!Background
+PUSH "\e[41m"
+ ?pen background = rgb(224,0,0)
+PUSH "\e[42m"
+ ?pen background = rgb(0,224,0)
+PUSH "\e[44m"
+ ?pen background = rgb(0,0,224)
+PUSH "\e[101m"
+ ?pen background = rgb(255,64,64)
+PUSH "\e[48:2:10:20:30m"
+ ?pen background = rgb(10,20,30)
+PUSH "\e[48:5:1m"
+ ?pen background = rgb(224,0,0)
+PUSH "\e[49m"
+ ?pen background = rgb(0,0,0)
+
+!Bold+ANSI colour == highbright
+PUSH "\e[m\e[1;37m"
+ ?pen bold = on
+ ?pen foreground = rgb(255,255,255)
+PUSH "\e[m\e[37;1m"
+ ?pen bold = on
+ ?pen foreground = rgb(255,255,255)
diff --git a/src/libvterm/t/40screen_ascii.test b/src/libvterm/t/40screen_ascii.test
new file mode 100644
index 0000000..c2f48fa
--- /dev/null
+++ b/src/libvterm/t/40screen_ascii.test
@@ -0,0 +1,69 @@
+INIT
+WANTSCREEN c
+
+!Get
+RESET
+PUSH "ABC"
+ movecursor 0,3
+ ?screen_chars 0,0,1,3 = 0x41,0x42,0x43
+ ?screen_chars 0,0,1,80 = 0x41,0x42,0x43
+ ?screen_text 0,0,1,3 = 0x41,0x42,0x43
+ ?screen_text 0,0,1,80 = 0x41,0x42,0x43
+ ?screen_cell 0,0 = {0x41} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)
+ ?screen_cell 0,1 = {0x42} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)
+ ?screen_cell 0,2 = {0x43} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)
+ ?screen_row 0 = "ABC"
+ ?screen_eol 0,0 = 0
+ ?screen_eol 0,2 = 0
+ ?screen_eol 0,3 = 1
+PUSH "\e[H"
+ movecursor 0,0
+ ?screen_chars 0,0,1,80 = 0x41,0x42,0x43
+ ?screen_text 0,0,1,80 = 0x41,0x42,0x43
+PUSH "E"
+ movecursor 0,1
+ ?screen_chars 0,0,1,80 = 0x45,0x42,0x43
+ ?screen_text 0,0,1,80 = 0x45,0x42,0x43
+
+WANTSCREEN -c
+
+!Erase
+RESET
+PUSH "ABCDE\e[H\e[K"
+ ?screen_chars 0,0,1,80 =
+ ?screen_text 0,0,1,80 =
+
+!Copycell
+RESET
+PUSH "ABC\e[H\e[@"
+PUSH "1"
+ ?screen_chars 0,0,1,80 = 0x31,0x41,0x42,0x43
+
+RESET
+PUSH "ABC\e[H\e[P"
+ ?screen_chars 0,0,1,1 = 0x42
+ ?screen_chars 0,1,1,2 = 0x43
+ ?screen_chars 0,0,1,80 = 0x42,0x43
+
+!Space padding
+RESET
+PUSH "Hello\e[CWorld"
+ ?screen_chars 0,0,1,80 = 0x48,0x65,0x6c,0x6c,0x6f,0x20,0x57,0x6f,0x72,0x6c,0x64
+ ?screen_text 0,0,1,80 = 0x48,0x65,0x6c,0x6c,0x6f,0x20,0x57,0x6f,0x72,0x6c,0x64
+
+!Linefeed padding
+RESET
+PUSH "Hello\r\nWorld"
+ ?screen_chars 0,0,2,80 = 0x48,0x65,0x6c,0x6c,0x6f,0x0a,0x57,0x6f,0x72,0x6c,0x64
+ ?screen_text 0,0,2,80 = 0x48,0x65,0x6c,0x6c,0x6f,0x0a,0x57,0x6f,0x72,0x6c,0x64
+
+!Altscreen
+RESET
+PUSH "P"
+ ?screen_chars 0,0,1,80 = 0x50
+PUSH "\e[?1049h"
+ ?screen_chars 0,0,1,80 =
+PUSH "\e[2K\e[HA"
+ ?screen_chars 0,0,1,80 = 0x41
+PUSH "\e[?1049l"
+ ?screen_chars 0,0,1,80 = 0x50
diff --git a/src/libvterm/t/41screen_unicode.test b/src/libvterm/t/41screen_unicode.test
new file mode 100644
index 0000000..79dcb68
--- /dev/null
+++ b/src/libvterm/t/41screen_unicode.test
@@ -0,0 +1,47 @@
+INIT
+UTF8 1
+WANTSCREEN
+
+!Single width UTF-8
+# U+00C1 = 0xC3 0x81 name: LATIN CAPITAL LETTER A WITH ACUTE
+# U+00E9 = 0xC3 0xA9 name: LATIN SMALL LETTER E WITH ACUTE
+RESET
+PUSH "\xC3\x81\xC3\xA9"
+ ?screen_chars 0,0,1,80 = 0xc1,0xe9
+ ?screen_text 0,0,1,80 = 0xc3,0x81,0xc3,0xa9
+ ?screen_cell 0,0 = {0xc1} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)
+
+!Wide char
+# U+FF10 = 0xEF 0xBC 0x90 name: FULLWIDTH DIGIT ZERO
+RESET
+PUSH "0123\e[H"
+PUSH "\xEF\xBC\x90"
+ ?screen_chars 0,0,1,80 = 0xff10,0x32,0x33
+ ?screen_text 0,0,1,80 = 0xef,0xbc,0x90,0x32,0x33
+ ?screen_cell 0,0 = {0xff10} width=2 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)
+
+!Combining char
+# U+0301 = 0xCC 0x81 name: COMBINING ACUTE
+RESET
+PUSH "0123\e[H"
+PUSH "e\xCC\x81"
+ ?screen_chars 0,0,1,80 = 0x65,0x301,0x31,0x32,0x33
+ ?screen_text 0,0,1,80 = 0x65,0xcc,0x81,0x31,0x32,0x33
+ ?screen_cell 0,0 = {0x65,0x301} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)
+
+!10 combining accents should not crash
+RESET
+PUSH "e\xCC\x81\xCC\x82\xCC\x83\xCC\x84\xCC\x85\xCC\x86\xCC\x87\xCC\x88\xCC\x89\xCC\x8A"
+ ?screen_cell 0,0 = {0x65,0x301,0x302,0x303,0x304,0x305} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)
+
+!40 combining accents in two split writes of 20 should not crash
+RESET
+PUSH "e\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81"
+PUSH "\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81"
+ ?screen_cell 0,0 = {0x65,0x301,0x301,0x301,0x301,0x301} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)
+
+!Outputing CJK doublewidth in 80th column should wraparound to next line and not crash"
+RESET
+PUSH "\e[80G\xEF\xBC\x90"
+ ?screen_cell 0,79 = {} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)
+ ?screen_cell 1,0 = {0xff10} width=2 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)
diff --git a/src/libvterm/t/42screen_damage.test b/src/libvterm/t/42screen_damage.test
new file mode 100644
index 0000000..791a96c
--- /dev/null
+++ b/src/libvterm/t/42screen_damage.test
@@ -0,0 +1,155 @@
+INIT
+WANTSCREEN Db
+
+!Putglyph
+RESET
+ damage 0..25,0..80
+PUSH "123"
+ damage 0..1,0..1 = 0<31>
+ damage 0..1,1..2 = 0<32>
+ damage 0..1,2..3 = 0<33>
+
+!Erase
+PUSH "\e[H"
+PUSH "\e[3X"
+ damage 0..1,0..3
+
+!Scroll damages entire line in two chunks
+PUSH "\e[H\e[5@"
+ damage 0..1,5..80
+ damage 0..1,0..5
+
+!Scroll down damages entire screen in two chunks
+PUSH "\e[T"
+ damage 1..25,0..80
+ damage 0..1,0..80
+
+!Altscreen damages entire area
+PUSH "\e[?1049h"
+ damage 0..25,0..80
+PUSH "\e[?1049l"
+ damage 0..25,0..80
+
+WANTSCREEN m
+
+!Scroll invokes moverect but not damage
+PUSH "\e[5@"
+ moverect 0..1,0..75 -> 0..1,5..80
+ damage 0..1,0..5
+
+WANTSCREEN -m
+
+!Merge to cells
+RESET
+ damage 0..25,0..80
+DAMAGEMERGE CELL
+
+PUSH "A"
+ damage 0..1,0..1 = 0<41>
+PUSH "B"
+ damage 0..1,1..2 = 0<42>
+PUSH "C"
+ damage 0..1,2..3 = 0<43>
+
+!Merge entire rows
+RESET
+ damage 0..25,0..80
+DAMAGEMERGE ROW
+
+PUSH "ABCDE\r\nEFGH"
+ damage 0..1,0..5 = 0<41 42 43 44 45>
+DAMAGEFLUSH
+ damage 1..2,0..4 = 1<45 46 47 48>
+PUSH "\e[3;6r\e[6H\eD"
+ damage 2..5,0..80
+DAMAGEFLUSH
+ damage 5..6,0..80
+
+!Merge entire screen
+RESET
+ damage 0..25,0..80
+DAMAGEMERGE SCREEN
+
+PUSH "ABCDE\r\nEFGH"
+DAMAGEFLUSH
+ damage 0..2,0..5 = 0<41 42 43 44 45> 1<45 46 47 48>
+PUSH "\e[3;6r\e[6H\eD"
+DAMAGEFLUSH
+ damage 2..6,0..80
+
+!Merge entire screen with moverect
+WANTSCREEN m
+
+RESET
+ damage 0..25,0..80
+DAMAGEMERGE SCREEN
+
+PUSH "ABCDE\r\nEFGH"
+PUSH "\e[3;6r\e[6H\eD"
+ damage 0..2,0..5 = 0<41 42 43 44 45> 1<45 46 47 48>
+ moverect 3..6,0..80 -> 2..5,0..80
+DAMAGEFLUSH
+ damage 5..6,0..80
+
+!Merge scroll
+RESET
+ damage 0..25,0..80
+DAMAGEMERGE SCROLL
+
+PUSH "\e[H1\r\n2\r\n3"
+PUSH "\e[25H\n\n\n"
+ sb_pushline 80 = 31
+ sb_pushline 80 = 32
+ sb_pushline 80 = 33
+DAMAGEFLUSH
+ moverect 3..25,0..80 -> 0..22,0..80
+ damage 0..25,0..80
+
+!Merge scroll with damage
+PUSH "\e[25H"
+PUSH "ABCDE\r\nEFGH\r\n"
+ sb_pushline 80 =
+ sb_pushline 80 =
+DAMAGEFLUSH
+ moverect 2..25,0..80 -> 0..23,0..80
+ damage 22..25,0..80 = 22<41 42 43 44 45> 23<45 46 47 48>
+
+!Merge scroll with damage past region
+PUSH "\e[3;6r\e[6H1\r\n2\r\n3\r\n4\r\n5"
+DAMAGEFLUSH
+ damage 2..6,0..80 = 2<32> 3<33> 4<34> 5<35>
+
+!Damage entirely outside scroll region
+PUSH "\e[HABC\e[3;6r\e[6H\r\n6"
+ damage 0..1,0..3 = 0<41 42 43>
+DAMAGEFLUSH
+ moverect 3..6,0..80 -> 2..5,0..80
+ damage 5..6,0..80 = 5<36>
+
+!Damage overlapping scroll region
+PUSH "\e[H\e[2J"
+DAMAGEFLUSH
+ damage 0..25,0..80
+
+PUSH "\e[HABCD\r\nEFGH\r\nIJKL\e[2;5r\e[5H\r\nMNOP"
+DAMAGEFLUSH
+ moverect 2..5,0..80 -> 1..4,0..80
+ damage 0..5,0..80 = 0<41 42 43 44> 1<49 4A 4B 4C>
+ ## TODO: is this right?
+
+!Merge scroll*2 with damage
+RESET
+ damage 0..25,0..80
+DAMAGEMERGE SCROLL
+
+PUSH "\e[25H\r\nABCDE\b\b\b\e[2P\r\n"
+ sb_pushline 80 =
+ moverect 1..25,0..80 -> 0..24,0..80
+ damage 24..25,0..80 = 24<41 42 43 44 45>
+ moverect 24..25,4..80 -> 24..25,2..78
+ damage 24..25,78..80
+ sb_pushline 80 =
+DAMAGEFLUSH
+ moverect 1..25,0..80 -> 0..24,0..80
+ damage 24..25,0..80
+ ?screen_chars 23,0,24,5 = 0x41,0x42,0x45
diff --git a/src/libvterm/t/43screen_resize.test b/src/libvterm/t/43screen_resize.test
new file mode 100644
index 0000000..9e5e5b2
--- /dev/null
+++ b/src/libvterm/t/43screen_resize.test
@@ -0,0 +1,90 @@
+INIT
+WANTSTATE
+WANTSCREEN
+
+!Resize wider preserves cells
+RESET
+RESIZE 25,80
+PUSH "AB\r\nCD"
+ ?screen_chars 0,0,1,80 = 0x41,0x42
+ ?screen_chars 1,0,2,80 = 0x43,0x44
+RESIZE 25,100
+ ?screen_chars 0,0,1,100 = 0x41,0x42
+ ?screen_chars 1,0,2,100 = 0x43,0x44
+
+!Resize wider allows print in new area
+RESET
+RESIZE 25,80
+PUSH "AB\e[79GCD"
+ ?screen_chars 0,0,1,2 = 0x41,0x42
+ ?screen_chars 0,78,1,80 = 0x43,0x44
+RESIZE 25,100
+ ?screen_chars 0,0,1,2 = 0x41,0x42
+ ?screen_chars 0,78,1,80 = 0x43,0x44
+PUSH "E"
+ ?screen_chars 0,78,1,81 = 0x43,0x44,0x45
+
+!Resize shorter with blanks just truncates
+RESET
+RESIZE 25,80
+PUSH "Top\e[10HLine 10"
+ ?screen_chars 0,0,1,80 = 0x54,0x6f,0x70
+ ?screen_chars 9,0,10,80 = 0x4c,0x69,0x6e,0x65,0x20,0x31,0x30
+ ?cursor = 9,7
+RESIZE 20,80
+ ?screen_chars 0,0,1,80 = 0x54,0x6f,0x70
+ ?screen_chars 9,0,10,80 = 0x4c,0x69,0x6e,0x65,0x20,0x31,0x30
+ ?cursor = 9,7
+
+!Resize shorter with content must scroll
+RESET
+RESIZE 25,80
+PUSH "Top\e[25HLine 25\e[15H"
+ ?screen_chars 0,0,1,80 = 0x54,0x6f,0x70
+ ?screen_chars 24,0,25,80 = 0x4c,0x69,0x6e,0x65,0x20,0x32,0x35
+ ?cursor = 14,0
+WANTSCREEN b
+RESIZE 20,80
+ sb_pushline 80 = 54 6F 70
+ sb_pushline 80 =
+ sb_pushline 80 =
+ sb_pushline 80 =
+ sb_pushline 80 =
+ ?screen_chars 0,0,1,80 =
+ ?screen_chars 19,0,20,80 = 0x4c,0x69,0x6e,0x65,0x20,0x32,0x35
+ ?cursor = 9,0
+
+!Resize shorter does not lose line with cursor
+# See also https://github.com/neovim/libvterm/commit/1b745d29d45623aa8d22a7b9288c7b0e331c7088
+RESET
+WANTSCREEN -b
+RESIZE 25,80
+WANTSCREEN b
+PUSH "\e[24HLine 24\r\nLine 25\r\n"
+ sb_pushline 80 =
+ ?screen_chars 23,0,24,10 = 0x4c,0x69,0x6e,0x65,0x20,0x32,0x35
+ ?cursor = 24,0
+RESIZE 24,80
+ sb_pushline 80 =
+ ?screen_chars 22,0,23,10 = 0x4c,0x69,0x6e,0x65,0x20,0x32,0x35
+ ?cursor = 23,0
+
+!Resize taller attempts to pop scrollback
+RESET
+WANTSCREEN -b
+RESIZE 25,80
+PUSH "Line 1\e[25HBottom\e[15H"
+ ?screen_chars 0,0,1,80 = 0x4c,0x69,0x6e,0x65,0x20,0x31
+ ?screen_chars 24,0,25,80 = 0x42,0x6f,0x74,0x74,0x6f,0x6d
+ ?cursor = 14,0
+WANTSCREEN b
+RESIZE 30,80
+ sb_popline 80
+ sb_popline 80
+ sb_popline 80
+ sb_popline 80
+ sb_popline 80
+ ?screen_chars 0,0,1,80 = 0x41,0x42,0x43,0x44,0x45
+ ?screen_chars 5,0,6,80 = 0x4c,0x69,0x6e,0x65,0x20,0x31
+ ?screen_chars 29,0,30,80 = 0x42,0x6f,0x74,0x74,0x6f,0x6d
+ ?cursor = 19,0
diff --git a/src/libvterm/t/44screen_pen.test b/src/libvterm/t/44screen_pen.test
new file mode 100644
index 0000000..f1ee639
--- /dev/null
+++ b/src/libvterm/t/44screen_pen.test
@@ -0,0 +1,55 @@
+INIT
+WANTSCREEN
+
+RESET
+
+!Plain
+PUSH "A"
+ ?screen_cell 0,0 = {0x41} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)
+
+!Bold
+PUSH "\e[1mB"
+ ?screen_cell 0,1 = {0x42} width=1 attrs={B} fg=rgb(240,240,240) bg=rgb(0,0,0)
+
+!Italic
+PUSH "\e[3mC"
+ ?screen_cell 0,2 = {0x43} width=1 attrs={BI} fg=rgb(240,240,240) bg=rgb(0,0,0)
+
+!Underline
+PUSH "\e[4mD"
+ ?screen_cell 0,3 = {0x44} width=1 attrs={BU1I} fg=rgb(240,240,240) bg=rgb(0,0,0)
+
+!Reset
+PUSH "\e[mE"
+ ?screen_cell 0,4 = {0x45} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)
+
+!Font
+PUSH "\e[11mF\e[m"
+ ?screen_cell 0,5 = {0x46} width=1 attrs={F1} fg=rgb(240,240,240) bg=rgb(0,0,0)
+
+!Foreground
+PUSH "\e[31mG\e[m"
+ ?screen_cell 0,6 = {0x47} width=1 attrs={} fg=rgb(224,0,0) bg=rgb(0,0,0)
+
+!Background
+PUSH "\e[42mH\e[m"
+ ?screen_cell 0,7 = {0x48} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,224,0)
+
+!EL sets reverse and colours to end of line
+PUSH "\e[H\e[7;33;44m\e[K"
+ ?screen_cell 0,0 = {} width=1 attrs={R} fg=rgb(224,224,0) bg=rgb(0,0,224)
+ ?screen_cell 0,79 = {} width=1 attrs={R} fg=rgb(224,224,0) bg=rgb(0,0,224)
+
+!DECSCNM xors reverse for entire screen
+PUSH "\e[?5h"
+ ?screen_cell 0,0 = {} width=1 attrs={} fg=rgb(224,224,0) bg=rgb(0,0,224)
+ ?screen_cell 0,79 = {} width=1 attrs={} fg=rgb(224,224,0) bg=rgb(0,0,224)
+ ?screen_cell 1,0 = {} width=1 attrs={R} fg=rgb(240,240,240) bg=rgb(0,0,0)
+PUSH "\e[?5\$p"
+ output "\e[?5;1\$y"
+PUSH "\e[?5l"
+ ?screen_cell 0,0 = {} width=1 attrs={R} fg=rgb(224,224,0) bg=rgb(0,0,224)
+ ?screen_cell 0,79 = {} width=1 attrs={R} fg=rgb(224,224,0) bg=rgb(0,0,224)
+ ?screen_cell 1,0 = {} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)
+PUSH "\e[?5\$p"
+ output "\e[?5;2\$y"
diff --git a/src/libvterm/t/45screen_protect.test b/src/libvterm/t/45screen_protect.test
new file mode 100644
index 0000000..718f853
--- /dev/null
+++ b/src/libvterm/t/45screen_protect.test
@@ -0,0 +1,16 @@
+INIT
+WANTSCREEN
+
+!Selective erase
+RESET
+PUSH "A\e[1\"qB\e[\"qC"
+ ?screen_chars 0,0,1,3 = 0x41,0x42,0x43
+PUSH "\e[G\e[?J"
+ ?screen_chars 0,0,1,3 = 0x20,0x42
+
+!Non-selective erase
+RESET
+PUSH "A\e[1\"qB\e[\"qC"
+ ?screen_chars 0,0,1,3 = 0x41,0x42,0x43
+PUSH "\e[G\e[J"
+ ?screen_chars 0,0,1,3 =
diff --git a/src/libvterm/t/46screen_extent.test b/src/libvterm/t/46screen_extent.test
new file mode 100644
index 0000000..a126cec
--- /dev/null
+++ b/src/libvterm/t/46screen_extent.test
@@ -0,0 +1,11 @@
+INIT
+WANTSCREEN
+
+!Bold extent
+RESET
+PUSH "AB\e[1mCD\e[mE"
+ ?screen_attrs_extent 0,0 = 0,0-1,1
+ ?screen_attrs_extent 0,1 = 0,0-1,1
+ ?screen_attrs_extent 0,2 = 0,2-1,3
+ ?screen_attrs_extent 0,3 = 0,2-1,3
+ ?screen_attrs_extent 0,4 = 0,4-1,79
diff --git a/src/libvterm/t/47screen_dbl_wh.test b/src/libvterm/t/47screen_dbl_wh.test
new file mode 100644
index 0000000..7d17d9a
--- /dev/null
+++ b/src/libvterm/t/47screen_dbl_wh.test
@@ -0,0 +1,32 @@
+INIT
+WANTSCREEN
+
+RESET
+
+!Single Width, Single Height
+RESET
+PUSH "\e#5"
+PUSH "abcde"
+ ?screen_cell 0,0 = {0x61} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)
+
+!Double Width, Single Height
+RESET
+PUSH "\e#6"
+PUSH "abcde"
+ ?screen_cell 0,0 = {0x61} width=1 attrs={} dwl fg=rgb(240,240,240) bg=rgb(0,0,0)
+
+!Double Height
+RESET
+PUSH "\e#3"
+PUSH "abcde"
+PUSH "\r\n\e#4"
+PUSH "abcde"
+ ?screen_cell 0,0 = {0x61} width=1 attrs={} dwl dhl-top fg=rgb(240,240,240) bg=rgb(0,0,0)
+ ?screen_cell 1,0 = {0x61} width=1 attrs={} dwl dhl-bottom fg=rgb(240,240,240) bg=rgb(0,0,0)
+
+!Late change
+RESET
+PUSH "abcde"
+ ?screen_cell 0,0 = {0x61} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)
+PUSH "\e#6"
+ ?screen_cell 0,0 = {0x61} width=1 attrs={} dwl fg=rgb(240,240,240) bg=rgb(0,0,0)
diff --git a/src/libvterm/t/48screen_termprops.test b/src/libvterm/t/48screen_termprops.test
new file mode 100644
index 0000000..adf7ec2
--- /dev/null
+++ b/src/libvterm/t/48screen_termprops.test
@@ -0,0 +1,17 @@
+INIT
+WANTSCREEN p
+
+RESET
+ settermprop 1 true
+ settermprop 2 true
+ settermprop 7 1
+
+!Cursor visibility
+PUSH "\e[?25h"
+ settermprop 1 true
+PUSH "\e[?25l"
+ settermprop 1 false
+
+!Title
+PUSH "\e]2;Here is my title\a"
+ settermprop 4 "Here is my title"
diff --git a/src/libvterm/t/90vttest_01-movement-1.test b/src/libvterm/t/90vttest_01-movement-1.test
new file mode 100644
index 0000000..c1a8cb9
--- /dev/null
+++ b/src/libvterm/t/90vttest_01-movement-1.test
@@ -0,0 +1,87 @@
+INIT
+WANTSTATE
+WANTSCREEN
+
+RESET
+
+PUSH "\e#8"
+
+PUSH "\e[9;10H\e[1J"
+PUSH "\e[18;60H\e[0J\e[1K"
+PUSH "\e[9;71H\e[0K"
+
+$SEQ 10 16: PUSH "\e[\#;10H\e[1K\e[\#;71H\e[0K"
+
+PUSH "\e[17;30H\e[2K"
+
+$SEQ 1 80: PUSH "\e[24;\#f*\e[1;\#f*"
+
+PUSH "\e[2;2H"
+
+$REP 22: PUSH "+\e[1D\eD"
+
+PUSH "\e[23;79H"
+$REP 22: PUSH "+\e[1D\eM"
+
+PUSH "\e[2;1H"
+$SEQ 2 23: PUSH "*\e[\#;80H*\e[10D\eE"
+
+PUSH "\e[2;10H\e[42D\e[2C"
+$REP 76: PUSH "+\e[0C\e[2D\e[1C"
+
+PUSH "\e[23;70H\e[42C\e[2D"
+
+$REP 76: PUSH "+\e[1D\e[1C\e[0D\b"
+
+PUSH "\e[1;1H"
+PUSH "\e[10A"
+PUSH "\e[1A"
+PUSH "\e[0A"
+PUSH "\e[24;80H"
+PUSH "\e[10B"
+PUSH "\e[1B"
+PUSH "\e[0B"
+PUSH "\e[10;12H"
+
+$REP 58: PUSH " "
+PUSH "\e[1B\e[58D"
+
+$REP 58: PUSH " "
+PUSH "\e[1B\e[58D"
+
+$REP 58: PUSH " "
+PUSH "\e[1B\e[58D"
+
+$REP 58: PUSH " "
+PUSH "\e[1B\e[58D"
+
+$REP 58: PUSH " "
+PUSH "\e[1B\e[58D"
+
+$REP 58: PUSH " "
+PUSH "\e[1B\e[58D"
+
+PUSH "\e[5A\e[1CThe screen should be cleared, and have an unbroken bor-"
+PUSH "\e[12;13Hder of *'s and +'s around the edge, and exactly in the"
+PUSH "\e[13;13Hmiddle there should be a frame of E's around this text"
+PUSH "\e[14;13Hwith one (1) free position around it. Push <RETURN>"
+
+# And the result is...
+
+!Output
+ ?screen_row 0 = "********************************************************************************"
+ ?screen_row 1 = "*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*"
+$SEQ 2 7: ?screen_row \# = "*+ +*"
+ ?screen_row 8 = "*+ EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE +*"
+ ?screen_row 9 = "*+ E E +*"
+ ?screen_row 10 = "*+ E The screen should be cleared, and have an unbroken bor- E +*"
+ ?screen_row 11 = "*+ E der of *'s and +'s around the edge, and exactly in the E +*"
+ ?screen_row 12 = "*+ E middle there should be a frame of E's around this text E +*"
+ ?screen_row 13 = "*+ E with one (1) free position around it. Push <RETURN> E +*"
+ ?screen_row 14 = "*+ E E +*"
+ ?screen_row 15 = "*+ EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE +*"
+$SEQ 16 21: ?screen_row \# = "*+ +*"
+ ?screen_row 22 = "*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*"
+ ?screen_row 23 = "********************************************************************************"
+
+?cursor = 13,67
diff --git a/src/libvterm/t/90vttest_01-movement-2.test b/src/libvterm/t/90vttest_01-movement-2.test
new file mode 100644
index 0000000..3a515e3
--- /dev/null
+++ b/src/libvterm/t/90vttest_01-movement-2.test
@@ -0,0 +1,40 @@
+INIT
+WANTSTATE
+WANTSCREEN
+
+RESET
+
+PUSH "\e[3;21r"
+PUSH "\e[?6h"
+
+PUSH "\e[19;1HA\e[19;80Ha\x0a\e[18;80HaB\e[19;80HB\b b\x0a\e[19;80HC\b\b\t\tc\e[19;2H\bC\x0a\e[19;80H\x0a\e[18;1HD\e[18;80Hd"
+PUSH "\e[19;1HE\e[19;80He\x0a\e[18;80HeF\e[19;80HF\b f\x0a\e[19;80HG\b\b\t\tg\e[19;2H\bG\x0a\e[19;80H\x0a\e[18;1HH\e[18;80Hh"
+PUSH "\e[19;1HI\e[19;80Hi\x0a\e[18;80HiJ\e[19;80HJ\b j\x0a\e[19;80HK\b\b\t\tk\e[19;2H\bK\x0a\e[19;80H\x0a\e[18;1HL\e[18;80Hl"
+PUSH "\e[19;1HM\e[19;80Hm\x0a\e[18;80HmN\e[19;80HN\b n\x0a\e[19;80HO\b\b\t\to\e[19;2H\bO\x0a\e[19;80H\x0a\e[18;1HP\e[18;80Hp"
+PUSH "\e[19;1HQ\e[19;80Hq\x0a\e[18;80HqR\e[19;80HR\b r\x0a\e[19;80HS\b\b\t\ts\e[19;2H\bS\x0a\e[19;80H\x0a\e[18;1HT\e[18;80Ht"
+PUSH "\e[19;1HU\e[19;80Hu\x0a\e[18;80HuV\e[19;80HV\b v\x0a\e[19;80HW\b\b\t\tw\e[19;2H\bW\x0a\e[19;80H\x0a\e[18;1HX\e[18;80Hx"
+PUSH "\e[19;1HY\e[19;80Hy\x0a\e[18;80HyZ\e[19;80HZ\b z\x0a"
+
+!Output
+
+?screen_row 2 = "I i"
+?screen_row 3 = "J j"
+?screen_row 4 = "K k"
+?screen_row 5 = "L l"
+?screen_row 6 = "M m"
+?screen_row 7 = "N n"
+?screen_row 8 = "O o"
+?screen_row 9 = "P p"
+?screen_row 10 = "Q q"
+?screen_row 11 = "R r"
+?screen_row 12 = "S s"
+?screen_row 13 = "T t"
+?screen_row 14 = "U u"
+?screen_row 15 = "V v"
+?screen_row 16 = "W w"
+?screen_row 17 = "X x"
+?screen_row 18 = "Y y"
+?screen_row 19 = "Z z"
+?screen_row 20 = ""
+
+?cursor = 20,79
diff --git a/src/libvterm/t/90vttest_01-movement-3.test b/src/libvterm/t/90vttest_01-movement-3.test
new file mode 100644
index 0000000..f9a99bf
--- /dev/null
+++ b/src/libvterm/t/90vttest_01-movement-3.test
@@ -0,0 +1,21 @@
+# Test of cursor-control characters inside ESC sequences
+INIT
+WANTSTATE
+WANTSCREEN
+
+RESET
+
+PUSH "A B C D E F G H I"
+PUSH "\x0d\x0a"
+PUSH "A\e[2\bCB\e[2\bCC\e[2\bCD\e[2\bCE\e[2\bCF\e[2\bCG\e[2\bCH\e[2\bCI"
+PUSH "\x0d\x0a"
+PUSH "A \e[\x0d2CB\e[\x0d4CC\e[\x0d6CD\e[\x0d8CE\e[\x0d10CF\e[\x0d12CG\e[\x0d14CH\e[\x0d16CI"
+PUSH "\x0d\x0a"
+PUSH "A \e[1\x0bAB \e[1\x0bAC \e[1\x0bAD \e[1\x0bAE \e[1\x0bAF \e[1\x0bAG \e[1\x0bAH \e[1\x0bAI \e[1\x0bA"
+
+!Output
+
+$SEQ 0 2: ?screen_row \# = "A B C D E F G H I"
+ ?screen_row 3 = "A B C D E F G H I "
+
+?cursor = 3,18
diff --git a/src/libvterm/t/90vttest_01-movement-4.test b/src/libvterm/t/90vttest_01-movement-4.test
new file mode 100644
index 0000000..0dab3c7
--- /dev/null
+++ b/src/libvterm/t/90vttest_01-movement-4.test
@@ -0,0 +1,36 @@
+# Test of leading zeroes in ESC sequences
+INIT
+WANTSCREEN
+
+RESET
+
+PUSH "\e[00000000004;000000001HT"
+PUSH "\e[00000000004;000000002Hh"
+PUSH "\e[00000000004;000000003Hi"
+PUSH "\e[00000000004;000000004Hs"
+PUSH "\e[00000000004;000000005H "
+PUSH "\e[00000000004;000000006Hi"
+PUSH "\e[00000000004;000000007Hs"
+PUSH "\e[00000000004;000000008H "
+PUSH "\e[00000000004;000000009Ha"
+PUSH "\e[00000000004;0000000010H "
+PUSH "\e[00000000004;0000000011Hc"
+PUSH "\e[00000000004;0000000012Ho"
+PUSH "\e[00000000004;0000000013Hr"
+PUSH "\e[00000000004;0000000014Hr"
+PUSH "\e[00000000004;0000000015He"
+PUSH "\e[00000000004;0000000016Hc"
+PUSH "\e[00000000004;0000000017Ht"
+PUSH "\e[00000000004;0000000018H "
+PUSH "\e[00000000004;0000000019Hs"
+PUSH "\e[00000000004;0000000020He"
+PUSH "\e[00000000004;0000000021Hn"
+PUSH "\e[00000000004;0000000022Ht"
+PUSH "\e[00000000004;0000000023He"
+PUSH "\e[00000000004;0000000024Hn"
+PUSH "\e[00000000004;0000000025Hc"
+PUSH "\e[00000000004;0000000026He"
+
+!Output
+
+?screen_row 3 = "This is a correct sentence"
diff --git a/src/libvterm/t/90vttest_02-screen-1.test b/src/libvterm/t/90vttest_02-screen-1.test
new file mode 100644
index 0000000..003d56f
--- /dev/null
+++ b/src/libvterm/t/90vttest_02-screen-1.test
@@ -0,0 +1,18 @@
+# Test of WRAP AROUND mode setting.
+INIT
+WANTSCREEN
+
+RESET
+
+PUSH "\e[?7h"
+$REP 170: PUSH "*"
+
+PUSH "\e[?7l\e[3;1H"
+$REP 177: PUSH "*"
+
+PUSH "\e[?7h\e[5;1HOK"
+
+!Output
+$SEQ 0 2: ?screen_row \# = "********************************************************************************"
+ ?screen_row 3 = ""
+ ?screen_row 4 = "OK"
diff --git a/src/libvterm/t/90vttest_02-screen-2.test b/src/libvterm/t/90vttest_02-screen-2.test
new file mode 100644
index 0000000..1c3a6a7
--- /dev/null
+++ b/src/libvterm/t/90vttest_02-screen-2.test
@@ -0,0 +1,29 @@
+# TAB setting/resetting
+INIT
+WANTSTATE
+WANTSCREEN
+
+RESET
+
+PUSH "\e[2J\e[3g"
+
+PUSH "\e[1;1H"
+$REP 26: PUSH "\e[3C\eH"
+
+PUSH "\e[1;4H"
+$REP 13: PUSH "\e[0g\e[6C"
+
+PUSH "\e[1;7H"
+PUSH "\e[1g\e[2g"
+
+PUSH "\e[1;1H"
+$REP 13: PUSH "\t*"
+
+PUSH "\e[2;2H"
+$REP 13: PUSH " *"
+
+!Output
+?screen_row 0 = " * * * * * * * * * * * * *"
+?screen_row 1 = " * * * * * * * * * * * * *"
+
+?cursor = 1,79
diff --git a/src/libvterm/t/90vttest_02-screen-3.test b/src/libvterm/t/90vttest_02-screen-3.test
new file mode 100644
index 0000000..8cdf8df
--- /dev/null
+++ b/src/libvterm/t/90vttest_02-screen-3.test
@@ -0,0 +1,16 @@
+# Origin mode
+INIT
+WANTSCREEN
+
+RESET
+
+PUSH "\e[?6h"
+PUSH "\e[23;24r"
+PUSH "\n"
+PUSH "Bottom"
+PUSH "\e[1;1H"
+PUSH "Above"
+
+!Output
+?screen_row 22 = "Above"
+?screen_row 23 = "Bottom"
diff --git a/src/libvterm/t/90vttest_02-screen-4.test b/src/libvterm/t/90vttest_02-screen-4.test
new file mode 100644
index 0000000..44d51f1
--- /dev/null
+++ b/src/libvterm/t/90vttest_02-screen-4.test
@@ -0,0 +1,17 @@
+# Origin mode (2)
+INIT
+WANTSCREEN
+
+RESET
+
+PUSH "\e[?6l"
+PUSH "\e[23;24r"
+PUSH "\e[24;1H"
+PUSH "Bottom"
+PUSH "\e[1;1H"
+PUSH "Top"
+
+!Output
+?screen_row 23 = "Bottom"
+?screen_row 0 = "Top"
+
diff --git a/src/libvterm/t/92lp1640917.test b/src/libvterm/t/92lp1640917.test
new file mode 100644
index 0000000..70de439
--- /dev/null
+++ b/src/libvterm/t/92lp1640917.test
@@ -0,0 +1,13 @@
+INIT
+WANTSTATE
+
+!Mouse reporting should not break by idempotent DECSM 1002
+PUSH "\e[?1002h"
+MOUSEMOVE 0,0 0
+MOUSEBTN d 1 0
+ output "\e[M\x20\x21\x21"
+MOUSEMOVE 1,0 0
+ output "\e[M\x40\x21\x22"
+PUSH "\e[?1002h"
+MOUSEMOVE 2,0 0
+ output "\e[M\x40\x21\x23"
diff --git a/src/libvterm/t/harness.c b/src/libvterm/t/harness.c
new file mode 100644
index 0000000..e2c7295
--- /dev/null
+++ b/src/libvterm/t/harness.c
@@ -0,0 +1,945 @@
+#include "vterm.h"
+#include "../src/vterm_internal.h" /* We pull in some internal bits too */
+
+#include <stdio.h>
+#include <string.h>
+
+#define streq(a,b) (!strcmp(a,b))
+#define strstartswith(a,b) (!strncmp(a,b,strlen(b)))
+
+static size_t inplace_hex2bytes(char *s)
+{
+ char *inpos = s, *outpos = s;
+
+ while(*inpos) {
+ unsigned int ch;
+ sscanf(inpos, "%2x", &ch);
+ *outpos = ch;
+ outpos += 1; inpos += 2;
+ }
+
+ return outpos - s;
+}
+
+static VTermModifier strpe_modifiers(char **strp)
+{
+ VTermModifier state = 0;
+
+ while((*strp)[0]) {
+ switch(((*strp)++)[0]) {
+ case 'S': state |= VTERM_MOD_SHIFT; break;
+ case 'C': state |= VTERM_MOD_CTRL; break;
+ case 'A': state |= VTERM_MOD_ALT; break;
+ default: return state;
+ }
+ }
+
+ return state;
+}
+
+static VTermKey strp_key(char *str)
+{
+ static struct {
+ char *name;
+ VTermKey key;
+ } keys[] = {
+ { "Up", VTERM_KEY_UP },
+ { "Tab", VTERM_KEY_TAB },
+ { "Enter", VTERM_KEY_ENTER },
+ { "KP0", VTERM_KEY_KP_0 },
+ { NULL, VTERM_KEY_NONE },
+ };
+ int i;
+
+ for(i = 0; keys[i].name; i++) {
+ if(streq(str, keys[i].name))
+ return keys[i].key;
+ }
+
+ return VTERM_KEY_NONE;
+}
+
+static VTerm *vt;
+static VTermState *state;
+static VTermScreen *screen;
+
+static VTermEncodingInstance encoding;
+
+static int parser_text(const char bytes[], size_t len, void *user)
+{
+ int i;
+
+ printf("text ");
+ for(i = 0; i < len; i++) {
+ unsigned char b = bytes[i];
+ if(b < 0x20 || b == 0x7f || (b >= 0x80 && b < 0xa0))
+ break;
+ printf(i ? ",%x" : "%x", b);
+ }
+ printf("\n");
+
+ return i;
+}
+
+static int parser_control(unsigned char control, void *user)
+{
+ printf("control %02x\n", control);
+
+ return 1;
+}
+
+static int parser_escape(const char bytes[], size_t len, void *user)
+{
+ int i;
+
+ if(bytes[0] >= 0x20 && bytes[0] < 0x30) {
+ if(len < 2)
+ return -1;
+ len = 2;
+ }
+ else {
+ len = 1;
+ }
+
+ printf("escape ");
+ for(i = 0; i < len; i++)
+ printf("%02x", bytes[i]);
+ printf("\n");
+
+ return len;
+}
+
+static int parser_csi(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user)
+{
+ int i;
+ printf("csi %02x", command);
+
+ if(leader && leader[0]) {
+ printf(" L=");
+ for(i = 0; leader[i]; i++)
+ printf("%02x", leader[i]);
+ }
+
+ for(i = 0; i < argcount; i++) {
+ char sep = i ? ',' : ' ';
+
+ if(args[i] == CSI_ARG_MISSING)
+ printf("%c*", sep);
+ else
+ printf("%c%ld%s", sep, CSI_ARG(args[i]), CSI_ARG_HAS_MORE(args[i]) ? "+" : "");
+ }
+
+ if(intermed && intermed[0]) {
+ printf(" I=");
+ for(i = 0; intermed[i]; i++)
+ printf("%02x", intermed[i]);
+ }
+
+ printf("\n");
+
+ return 1;
+}
+
+static int parser_osc(const char *command, size_t cmdlen, void *user)
+{
+ int i;
+ printf("osc ");
+ for(i = 0; i < cmdlen; i++)
+ printf("%02x", command[i]);
+ printf("\n");
+
+ return 1;
+}
+
+static int parser_dcs(const char *command, size_t cmdlen, void *user)
+{
+ int i;
+ printf("dcs ");
+ for(i = 0; i < cmdlen; i++)
+ printf("%02x", command[i]);
+ printf("\n");
+
+ return 1;
+}
+
+static VTermParserCallbacks parser_cbs = {
+ parser_text, /* text */
+ parser_control, /* control */
+ parser_escape, /* escape */
+ parser_csi, /* csi */
+ parser_osc, /* osc */
+ parser_dcs, /* dcs */
+ NULL /* resize */
+};
+
+/* These callbacks are shared by State and Screen */
+
+static int want_movecursor = 0;
+static VTermPos state_pos;
+static int movecursor(VTermPos pos, VTermPos oldpos, int visible, void *user)
+{
+ state_pos = pos;
+
+ if(want_movecursor)
+ printf("movecursor %d,%d\n", pos.row, pos.col);
+
+ return 1;
+}
+
+static int want_scrollrect = 0;
+static int scrollrect(VTermRect rect, int downward, int rightward, void *user)
+{
+ if(!want_scrollrect)
+ return 0;
+
+ printf("scrollrect %d..%d,%d..%d => %+d,%+d\n",
+ rect.start_row, rect.end_row, rect.start_col, rect.end_col,
+ downward, rightward);
+
+ return 1;
+}
+
+static int want_moverect = 0;
+static int moverect(VTermRect dest, VTermRect src, void *user)
+{
+ if(!want_moverect)
+ return 0;
+
+ printf("moverect %d..%d,%d..%d -> %d..%d,%d..%d\n",
+ src.start_row, src.end_row, src.start_col, src.end_col,
+ dest.start_row, dest.end_row, dest.start_col, dest.end_col);
+
+ return 1;
+}
+
+static int want_settermprop = 0;
+static int settermprop(VTermProp prop, VTermValue *val, void *user)
+{
+ VTermValueType type;
+ if(!want_settermprop)
+ return 1;
+
+ type = vterm_get_prop_type(prop);
+ switch(type) {
+ case VTERM_VALUETYPE_BOOL:
+ printf("settermprop %d %s\n", prop, val->boolean ? "true" : "false");
+ return 1;
+ case VTERM_VALUETYPE_INT:
+ printf("settermprop %d %d\n", prop, val->number);
+ return 1;
+ case VTERM_VALUETYPE_STRING:
+ printf("settermprop %d \"%s\"\n", prop, val->string);
+ return 1;
+ case VTERM_VALUETYPE_COLOR:
+ printf("settermprop %d rgb(%d,%d,%d)\n", prop, val->color.red, val->color.green, val->color.blue);
+ return 1;
+
+ case VTERM_N_VALUETYPES:
+ return 0;
+ }
+
+ return 0;
+}
+
+/* These callbacks are for State */
+
+static int want_state_putglyph = 0;
+static int state_putglyph(VTermGlyphInfo *info, VTermPos pos, void *user)
+{
+ int i;
+ if(!want_state_putglyph)
+ return 1;
+
+ printf("putglyph ");
+ for(i = 0; info->chars[i]; i++)
+ printf(i ? ",%x" : "%x", info->chars[i]);
+ printf(" %d %d,%d", info->width, pos.row, pos.col);
+ if(info->protected_cell)
+ printf(" prot");
+ if(info->dwl)
+ printf(" dwl");
+ if(info->dhl)
+ printf(" dhl-%s", info->dhl == 1 ? "top" : info->dhl == 2 ? "bottom" : "?" );
+ printf("\n");
+
+ return 1;
+}
+
+static int want_state_erase = 0;
+static int state_erase(VTermRect rect, int selective, void *user)
+{
+ if(!want_state_erase)
+ return 1;
+
+ printf("erase %d..%d,%d..%d%s\n",
+ rect.start_row, rect.end_row, rect.start_col, rect.end_col,
+ selective ? " selective" : "");
+
+ return 1;
+}
+
+static struct {
+ int bold;
+ int underline;
+ int italic;
+ int blink;
+ int reverse;
+ int strike;
+ int font;
+ VTermColor foreground;
+ VTermColor background;
+} state_pen;
+static int state_setpenattr(VTermAttr attr, VTermValue *val, void *user)
+{
+ switch(attr) {
+ case VTERM_ATTR_BOLD:
+ state_pen.bold = val->boolean;
+ break;
+ case VTERM_ATTR_UNDERLINE:
+ state_pen.underline = val->number;
+ break;
+ case VTERM_ATTR_ITALIC:
+ state_pen.italic = val->boolean;
+ break;
+ case VTERM_ATTR_BLINK:
+ state_pen.blink = val->boolean;
+ break;
+ case VTERM_ATTR_REVERSE:
+ state_pen.reverse = val->boolean;
+ break;
+ case VTERM_ATTR_STRIKE:
+ state_pen.strike = val->boolean;
+ break;
+ case VTERM_ATTR_FONT:
+ state_pen.font = val->number;
+ break;
+ case VTERM_ATTR_FOREGROUND:
+ state_pen.foreground = val->color;
+ break;
+ case VTERM_ATTR_BACKGROUND:
+ state_pen.background = val->color;
+ break;
+
+ case VTERM_N_ATTRS:
+ return 0;
+ }
+
+ return 1;
+}
+
+static int state_setlineinfo(int row, const VTermLineInfo *newinfo, const VTermLineInfo *oldinfo, void *user)
+{
+ return 1;
+}
+
+VTermStateCallbacks state_cbs = {
+ state_putglyph, /* putglyph */
+ movecursor, /* movecursor */
+ scrollrect, /* scrollrect */
+ moverect, /* moverect */
+ state_erase, /* erase */
+ NULL, /* initpen */
+ state_setpenattr, /* setpenattr */
+ settermprop, /* settermprop */
+ NULL, /* bell */
+ NULL, /* resize */
+ state_setlineinfo, /* setlineinfo */
+};
+
+static int want_screen_damage = 0;
+static int want_screen_damage_cells = 0;
+static int screen_damage(VTermRect rect, void *user)
+{
+ if(!want_screen_damage)
+ return 1;
+
+ printf("damage %d..%d,%d..%d",
+ rect.start_row, rect.end_row, rect.start_col, rect.end_col);
+
+ if(want_screen_damage_cells) {
+ int equals = FALSE;
+ int row;
+ int col;
+
+ for(row = rect.start_row; row < rect.end_row; row++) {
+ int eol = rect.end_col;
+ while(eol > rect.start_col) {
+ VTermScreenCell cell;
+ VTermPos pos;
+ pos.row = row;
+ pos.col = eol-1;
+ vterm_screen_get_cell(screen, pos, &cell);
+ if(cell.chars[0])
+ break;
+
+ eol--;
+ }
+
+ if(eol == rect.start_col)
+ break;
+
+ if(!equals)
+ printf(" ="), equals = TRUE;
+
+ printf(" %d<", row);
+ for(col = rect.start_col; col < eol; col++) {
+ VTermScreenCell cell;
+ VTermPos pos;
+ pos.row = row;
+ pos.col = col;
+ vterm_screen_get_cell(screen, pos, &cell);
+ printf(col == rect.start_col ? "%02X" : " %02X", cell.chars[0]);
+ }
+ printf(">");
+ }
+ }
+
+ printf("\n");
+
+ return 1;
+}
+
+static int want_screen_scrollback = 0;
+static int screen_sb_pushline(int cols, const VTermScreenCell *cells, void *user)
+{
+ int eol;
+ int c;
+
+ if(!want_screen_scrollback)
+ return 1;
+
+ eol = cols;
+ while(eol && !cells[eol-1].chars[0])
+ eol--;
+
+ printf("sb_pushline %d =", cols);
+ for(c = 0; c < eol; c++)
+ printf(" %02X", cells[c].chars[0]);
+ printf("\n");
+
+ return 1;
+}
+
+static int screen_sb_popline(int cols, VTermScreenCell *cells, void *user)
+{
+ int col;
+
+ if(!want_screen_scrollback)
+ return 0;
+
+ /* All lines of scrollback contain "ABCDE" */
+ for(col = 0; col < cols; col++) {
+ if(col < 5)
+ cells[col].chars[0] = 'A' + col;
+ else
+ cells[col].chars[0] = 0;
+
+ cells[col].width = 1;
+ }
+
+ printf("sb_popline %d\n", cols);
+ return 1;
+}
+
+VTermScreenCallbacks screen_cbs = {
+ screen_damage, /* damage */
+ moverect, /* moverect */
+ movecursor, /* movecursor */
+ settermprop, /* settermprop */
+ NULL, /* bell */
+ NULL, /* resize */
+ screen_sb_pushline, /* sb_pushline */
+ screen_sb_popline /* sb_popline */
+};
+
+int main(int argc, char **argv)
+{
+ char line[1024] = {0};
+ int flag;
+
+ int err;
+
+ setvbuf(stdout, NULL, _IONBF, 0);
+
+ while(fgets(line, sizeof line, stdin)) {
+ char *nl;
+ size_t outlen;
+ err = 0;
+
+ if((nl = strchr(line, '\n')))
+ *nl = '\0';
+
+ if(streq(line, "INIT")) {
+ if(!vt)
+ vt = vterm_new(25, 80);
+ }
+
+ else if(streq(line, "WANTPARSER")) {
+ vterm_parser_set_callbacks(vt, &parser_cbs, NULL);
+ }
+
+ else if(strstartswith(line, "WANTSTATE") && (line[9] == '\0' || line[9] == ' ')) {
+ int i = 9;
+ int sense = 1;
+ if(!state) {
+ state = vterm_obtain_state(vt);
+ vterm_state_set_callbacks(state, &state_cbs, NULL);
+ vterm_state_set_bold_highbright(state, 1);
+ vterm_state_reset(state, 1);
+ }
+
+ while(line[i] == ' ')
+ i++;
+ for( ; line[i]; i++)
+ switch(line[i]) {
+ case '+':
+ sense = 1;
+ break;
+ case '-':
+ sense = 0;
+ break;
+ case 'g':
+ want_state_putglyph = sense;
+ break;
+ case 's':
+ want_scrollrect = sense;
+ break;
+ case 'm':
+ want_moverect = sense;
+ break;
+ case 'e':
+ want_state_erase = sense;
+ break;
+ case 'p':
+ want_settermprop = sense;
+ break;
+ case 'f':
+ vterm_state_set_unrecognised_fallbacks(state, sense ? &parser_cbs : NULL, NULL);
+ break;
+ default:
+ fprintf(stderr, "Unrecognised WANTSTATE flag '%c'\n", line[i]);
+ }
+ }
+
+ else if(strstartswith(line, "WANTSCREEN") && (line[10] == '\0' || line[10] == ' ')) {
+ int i = 10;
+ int sense = 1;
+ if(!screen)
+ screen = vterm_obtain_screen(vt);
+ vterm_screen_enable_altscreen(screen, 1);
+ vterm_screen_set_callbacks(screen, &screen_cbs, NULL);
+
+ while(line[i] == ' ')
+ i++;
+ for( ; line[i]; i++)
+ switch(line[i]) {
+ case '-':
+ sense = 0;
+ break;
+ case 'd':
+ want_screen_damage = sense;
+ break;
+ case 'D':
+ want_screen_damage = sense;
+ want_screen_damage_cells = sense;
+ break;
+ case 'm':
+ want_moverect = sense;
+ break;
+ case 'c':
+ want_movecursor = sense;
+ break;
+ case 'p':
+ want_settermprop = 1;
+ break;
+ case 'b':
+ want_screen_scrollback = sense;
+ break;
+ default:
+ fprintf(stderr, "Unrecognised WANTSCREEN flag '%c'\n", line[i]);
+ }
+ }
+
+ else if(sscanf(line, "UTF8 %d", &flag)) {
+ vterm_set_utf8(vt, flag);
+ }
+
+ else if(streq(line, "RESET")) {
+ if(state) {
+ vterm_state_reset(state, 1);
+ vterm_state_get_cursorpos(state, &state_pos);
+ }
+ if(screen) {
+ vterm_screen_reset(screen, 1);
+ }
+ }
+
+ else if(strstartswith(line, "RESIZE ")) {
+ int rows, cols;
+ char *linep = line + 7;
+ while(linep[0] == ' ')
+ linep++;
+ sscanf(linep, "%d, %d", &rows, &cols);
+ vterm_set_size(vt, rows, cols);
+ }
+
+ else if(strstartswith(line, "PUSH ")) {
+ char *bytes = line + 5;
+ size_t len = inplace_hex2bytes(bytes);
+ size_t written = vterm_input_write(vt, bytes, len);
+ if(written < len)
+ fprintf(stderr, "! short write\n");
+ }
+
+ else if(streq(line, "WANTENCODING")) {
+ /* This isn't really external API but it's hard to get this out any
+ * other way
+ */
+ encoding.enc = vterm_lookup_encoding(ENC_UTF8, 'u');
+ if(encoding.enc->init)
+ (*encoding.enc->init)(encoding.enc, encoding.data);
+ }
+
+ else if(strstartswith(line, "ENCIN ")) {
+ char *bytes = line + 6;
+ size_t len = inplace_hex2bytes(bytes);
+
+ uint32_t cp[1024];
+ int cpi = 0;
+ size_t pos = 0;
+
+ (*encoding.enc->decode)(encoding.enc, encoding.data,
+ cp, &cpi, len, bytes, &pos, len);
+
+ if(cpi > 0) {
+ int i;
+ printf("encout ");
+ for(i = 0; i < cpi; i++) {
+ printf(i ? ",%x" : "%x", cp[i]);
+ }
+ printf("\n");
+ }
+ }
+
+ else if(strstartswith(line, "INCHAR ")) {
+ char *linep = line + 7;
+ unsigned int c = 0;
+ VTermModifier mod;
+ while(linep[0] == ' ')
+ linep++;
+ mod = strpe_modifiers(&linep);
+ sscanf(linep, " %x", &c);
+
+ vterm_keyboard_unichar(vt, c, mod);
+ }
+
+ else if(strstartswith(line, "INKEY ")) {
+ VTermModifier mod;
+ VTermKey key;
+ char *linep = line + 6;
+ while(linep[0] == ' ')
+ linep++;
+ mod = strpe_modifiers(&linep);
+ while(linep[0] == ' ')
+ linep++;
+ key = strp_key(linep);
+
+ vterm_keyboard_key(vt, key, mod);
+ }
+
+ else if(strstartswith(line, "PASTE ")) {
+ char *linep = line + 6;
+ if(streq(linep, "START"))
+ vterm_keyboard_start_paste(vt);
+ else if(streq(linep, "END"))
+ vterm_keyboard_end_paste(vt);
+ else
+ goto abort_line;
+ }
+
+ else if(strstartswith(line, "FOCUS ")) {
+ char *linep = line + 6;
+ if(streq(linep, "IN"))
+ vterm_state_focus_in(state);
+ else if(streq(linep, "OUT"))
+ vterm_state_focus_out(state);
+ else
+ goto abort_line;
+ }
+
+ else if(strstartswith(line, "MOUSEMOVE ")) {
+ char *linep = line + 10;
+ int row, col, len;
+ VTermModifier mod;
+ while(linep[0] == ' ')
+ linep++;
+ sscanf(linep, "%d,%d%n", &row, &col, &len);
+ linep += len;
+ while(linep[0] == ' ')
+ linep++;
+ mod = strpe_modifiers(&linep);
+ vterm_mouse_move(vt, row, col, mod);
+ }
+
+ else if(strstartswith(line, "MOUSEBTN ")) {
+ char *linep = line + 9;
+ char press;
+ int button, len;
+ VTermModifier mod;
+ while(linep[0] == ' ')
+ linep++;
+ sscanf(linep, "%c %d%n", &press, &button, &len);
+ linep += len;
+ while(linep[0] == ' ')
+ linep++;
+ mod = strpe_modifiers(&linep);
+ vterm_mouse_button(vt, button, (press == 'd' || press == 'D'), mod);
+ }
+
+ else if(strstartswith(line, "DAMAGEMERGE ")) {
+ char *linep = line + 12;
+ while(linep[0] == ' ')
+ linep++;
+ if(streq(linep, "CELL"))
+ vterm_screen_set_damage_merge(screen, VTERM_DAMAGE_CELL);
+ else if(streq(linep, "ROW"))
+ vterm_screen_set_damage_merge(screen, VTERM_DAMAGE_ROW);
+ else if(streq(linep, "SCREEN"))
+ vterm_screen_set_damage_merge(screen, VTERM_DAMAGE_SCREEN);
+ else if(streq(linep, "SCROLL"))
+ vterm_screen_set_damage_merge(screen, VTERM_DAMAGE_SCROLL);
+ }
+
+ else if(strstartswith(line, "DAMAGEFLUSH")) {
+ vterm_screen_flush_damage(screen);
+ }
+
+ else if(line[0] == '?') {
+ if(streq(line, "?cursor")) {
+ VTermPos pos;
+ vterm_state_get_cursorpos(state, &pos);
+ if(pos.row != state_pos.row)
+ printf("! row mismatch: state=%d,%d event=%d,%d\n",
+ pos.row, pos.col, state_pos.row, state_pos.col);
+ else if(pos.col != state_pos.col)
+ printf("! col mismatch: state=%d,%d event=%d,%d\n",
+ pos.row, pos.col, state_pos.row, state_pos.col);
+ else
+ printf("%d,%d\n", state_pos.row, state_pos.col);
+ }
+ else if(strstartswith(line, "?pen ")) {
+ VTermValue val;
+ char *linep = line + 5;
+ while(linep[0] == ' ')
+ linep++;
+
+#define BOOLSTR(v) ((v) ? "on" : "off")
+
+ if(streq(linep, "bold")) {
+ vterm_state_get_penattr(state, VTERM_ATTR_BOLD, &val);
+ if(val.boolean != state_pen.bold)
+ printf("! pen bold mismatch; state=%s, event=%s\n",
+ BOOLSTR(val.boolean), BOOLSTR(state_pen.bold));
+ else
+ printf("%s\n", BOOLSTR(state_pen.bold));
+ }
+ else if(streq(linep, "underline")) {
+ vterm_state_get_penattr(state, VTERM_ATTR_UNDERLINE, &val);
+ if(val.boolean != state_pen.underline)
+ printf("! pen underline mismatch; state=%d, event=%d\n",
+ val.boolean, state_pen.underline);
+ else
+ printf("%d\n", state_pen.underline);
+ }
+ else if(streq(linep, "italic")) {
+ vterm_state_get_penattr(state, VTERM_ATTR_ITALIC, &val);
+ if(val.boolean != state_pen.italic)
+ printf("! pen italic mismatch; state=%s, event=%s\n",
+ BOOLSTR(val.boolean), BOOLSTR(state_pen.italic));
+ else
+ printf("%s\n", BOOLSTR(state_pen.italic));
+ }
+ else if(streq(linep, "blink")) {
+ vterm_state_get_penattr(state, VTERM_ATTR_BLINK, &val);
+ if(val.boolean != state_pen.blink)
+ printf("! pen blink mismatch; state=%s, event=%s\n",
+ BOOLSTR(val.boolean), BOOLSTR(state_pen.blink));
+ else
+ printf("%s\n", BOOLSTR(state_pen.blink));
+ }
+ else if(streq(linep, "reverse")) {
+ vterm_state_get_penattr(state, VTERM_ATTR_REVERSE, &val);
+ if(val.boolean != state_pen.reverse)
+ printf("! pen reverse mismatch; state=%s, event=%s\n",
+ BOOLSTR(val.boolean), BOOLSTR(state_pen.reverse));
+ else
+ printf("%s\n", BOOLSTR(state_pen.reverse));
+ }
+ else if(streq(linep, "font")) {
+ vterm_state_get_penattr(state, VTERM_ATTR_FONT, &val);
+ if(val.boolean != state_pen.font)
+ printf("! pen font mismatch; state=%d, event=%d\n",
+ val.boolean, state_pen.font);
+ else
+ printf("%d\n", state_pen.font);
+ }
+ else if(streq(linep, "foreground")) {
+ printf("rgb(%d,%d,%d)\n", state_pen.foreground.red, state_pen.foreground.green, state_pen.foreground.blue);
+ }
+ else if(streq(linep, "background")) {
+ printf("rgb(%d,%d,%d)\n", state_pen.background.red, state_pen.background.green, state_pen.background.blue);
+ }
+ else
+ printf("?\n");
+ }
+ else if(strstartswith(line, "?screen_chars ")) {
+ char *linep = line + 13;
+ VTermRect rect;
+ size_t len;
+ while(linep[0] == ' ')
+ linep++;
+ if(sscanf(linep, "%d,%d,%d,%d", &rect.start_row, &rect.start_col, &rect.end_row, &rect.end_col) < 4) {
+ printf("! screen_chars unrecognised input\n");
+ goto abort_line;
+ }
+ len = vterm_screen_get_chars(screen, NULL, 0, rect);
+ if(len == (size_t)-1)
+ printf("! screen_chars error\n");
+ else if(len == 0)
+ printf("\n");
+ else {
+ uint32_t *chars = malloc(sizeof(uint32_t) * len);
+ size_t i;
+ vterm_screen_get_chars(screen, chars, len, rect);
+ for(i = 0; i < len; i++) {
+ printf("0x%02x%s", chars[i], i < len-1 ? "," : "\n");
+ }
+ free(chars);
+ }
+ }
+ else if(strstartswith(line, "?screen_text ")) {
+ char *linep = line + 12;
+ VTermRect rect;
+ size_t len;
+ while(linep[0] == ' ')
+ linep++;
+ if(sscanf(linep, "%d,%d,%d,%d", &rect.start_row, &rect.start_col, &rect.end_row, &rect.end_col) < 4) {
+ printf("! screen_text unrecognised input\n");
+ goto abort_line;
+ }
+ len = vterm_screen_get_text(screen, NULL, 0, rect);
+ if(len == (size_t)-1)
+ printf("! screen_text error\n");
+ else if(len == 0)
+ printf("\n");
+ else {
+ /* Put an overwrite guard at both ends of the buffer */
+ unsigned char *buffer = malloc(len + 4);
+ unsigned char *text = buffer + 2;
+ text[-2] = 0x55; text[-1] = 0xAA;
+ text[len] = 0x55; text[len+1] = 0xAA;
+
+ vterm_screen_get_text(screen, (char *)text, len, rect);
+
+ if(text[-2] != 0x55 || text[-1] != 0xAA)
+ printf("! screen_get_text buffer overrun left [%02x,%02x]\n", text[-2], text[-1]);
+ else if(text[len] != 0x55 || text[len+1] != 0xAA)
+ printf("! screen_get_text buffer overrun right [%02x,%02x]\n", text[len], text[len+1]);
+ else
+ {
+ size_t i;
+ for(i = 0; i < len; i++) {
+ printf("0x%02x%s", text[i], i < len-1 ? "," : "\n");
+ }
+ }
+
+ free(buffer);
+ }
+ }
+ else if(strstartswith(line, "?screen_cell ")) {
+ char *linep = line + 12;
+ int i;
+ VTermPos pos;
+ VTermScreenCell cell;
+ while(linep[0] == ' ')
+ linep++;
+ if(sscanf(linep, "%d,%d\n", &pos.row, &pos.col) < 2) {
+ printf("! screen_cell unrecognised input\n");
+ goto abort_line;
+ }
+ if(!vterm_screen_get_cell(screen, pos, &cell))
+ goto abort_line;
+ printf("{");
+ for(i = 0; i < VTERM_MAX_CHARS_PER_CELL && cell.chars[i]; i++) {
+ printf("%s0x%x", i ? "," : "", cell.chars[i]);
+ }
+ printf("} width=%d attrs={", cell.width);
+ if(cell.attrs.bold) printf("B");
+ if(cell.attrs.underline) printf("U%d", cell.attrs.underline);
+ if(cell.attrs.italic) printf("I");
+ if(cell.attrs.blink) printf("K");
+ if(cell.attrs.reverse) printf("R");
+ if(cell.attrs.font) printf("F%d", cell.attrs.font);
+ printf("} ");
+ if(cell.attrs.dwl) printf("dwl ");
+ if(cell.attrs.dhl) printf("dhl-%s ", cell.attrs.dhl == 2 ? "bottom" : "top");
+ printf("fg=rgb(%d,%d,%d) ", cell.fg.red, cell.fg.green, cell.fg.blue);
+ printf("bg=rgb(%d,%d,%d)\n", cell.bg.red, cell.bg.green, cell.bg.blue);
+ }
+ else if(strstartswith(line, "?screen_eol ")) {
+ VTermPos pos;
+ char *linep = line + 12;
+ while(linep[0] == ' ')
+ linep++;
+ if(sscanf(linep, "%d,%d\n", &pos.row, &pos.col) < 2) {
+ printf("! screen_eol unrecognised input\n");
+ goto abort_line;
+ }
+ printf("%d\n", vterm_screen_is_eol(screen, pos));
+ }
+ else if(strstartswith(line, "?screen_attrs_extent ")) {
+ VTermPos pos;
+ VTermRect rect;
+ char *linep = line + 21;
+ while(linep[0] == ' ')
+ linep++;
+ if(sscanf(linep, "%d,%d\n", &pos.row, &pos.col) < 2) {
+ printf("! screen_attrs_extent unrecognised input\n");
+ goto abort_line;
+ }
+ rect.start_col = 0;
+ rect.end_col = -1;
+ if(!vterm_screen_get_attrs_extent(screen, &rect, pos, ~0)) {
+ printf("! screen_attrs_extent failed\n");
+ goto abort_line;
+ }
+ printf("%d,%d-%d,%d\n", rect.start_row, rect.start_col, rect.end_row, rect.end_col);
+ }
+ else
+ printf("?\n");
+
+ memset(line, 0, sizeof line);
+ continue;
+ }
+
+ else
+ abort_line: err = 1;
+
+ outlen = vterm_output_get_buffer_current(vt);
+ if(outlen > 0) {
+ int i;
+ char outbuff[1024];
+ vterm_output_read(vt, outbuff, outlen);
+
+ printf("output ");
+ for(i = 0; i < outlen; i++)
+ printf("%x%s", (unsigned char)outbuff[i], i < outlen-1 ? "," : "\n");
+ }
+
+ printf(err ? "?\n" : "DONE\n");
+ }
+
+ vterm_free(vt);
+
+ return 0;
+}
diff --git a/src/libvterm/t/run-test.pl b/src/libvterm/t/run-test.pl
new file mode 100644
index 0000000..12e1180
--- /dev/null
+++ b/src/libvterm/t/run-test.pl
@@ -0,0 +1,196 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use Getopt::Long;
+use IO::Handle;
+use IPC::Open2 qw( open2 );
+use POSIX qw( WIFEXITED WEXITSTATUS WIFSIGNALED WTERMSIG );
+
+my $VALGRIND = 0;
+GetOptions(
+ 'valgrind|v+' => \$VALGRIND,
+) or exit 1;
+
+my ( $hin, $hout, $hpid );
+{
+ local $ENV{LD_LIBRARY_PATH} = ".libs";
+ my @command = "t/.libs/harness";
+ unshift @command, "valgrind", "--tool=memcheck", "--leak-check=yes", "--num-callers=25", "--log-file=valgrind.out", "--error-exitcode=126" if $VALGRIND;
+
+ $hpid = open2 $hout, $hin, @command or die "Cannot open2 harness - $!";
+}
+
+my $exitcode = 0;
+
+my $command;
+my @expect;
+
+sub do_onetest
+{
+ $hin->print( "$command\n" );
+ undef $command;
+
+ my $fail_printed = 0;
+
+ while( my $outline = <$hout> ) {
+ last if $outline eq "DONE\n" or $outline eq "?\n";
+
+ chomp $outline;
+
+ if( !@expect ) {
+ print "# Test failed\n" unless $fail_printed++;
+ print "# expected nothing more\n" .
+ "# Actual: $outline\n";
+ next;
+ }
+
+ my $expectation = shift @expect;
+
+ next if $expectation eq $outline;
+
+ print "# Test failed\n" unless $fail_printed++;
+ print "# Expected: $expectation\n" .
+ "# Actual: $outline\n";
+ }
+
+ if( @expect ) {
+ print "# Test failed\n" unless $fail_printed++;
+ print "# Expected: $_\n" .
+ "# didn't happen\n" for @expect;
+ }
+
+ $exitcode = 1 if $fail_printed;
+}
+
+sub do_line
+{
+ my ( $line ) = @_;
+
+ if( $line =~ m/^!(.*)/ ) {
+ do_onetest if defined $command;
+ print "> $1\n";
+ }
+
+ # Commands have capitals
+ elsif( $line =~ m/^([A-Z]+)/ ) {
+ # Some convenience formatting
+ if( $line =~ m/^(PUSH|ENCIN) (.*)$/ ) {
+ # we're evil
+ my $string = eval($2);
+ $line = "$1 " . unpack "H*", $string;
+ }
+
+ do_onetest if defined $command;
+
+ $command = $line;
+ undef @expect;
+ }
+ # Expectations have lowercase
+ elsif( $line =~ m/^([a-z]+)/ ) {
+ # Convenience formatting
+ if( $line =~ m/^(text|encout) (.*)$/ ) {
+ $line = "$1 " . join ",", map sprintf("%x", $_), eval($2);
+ }
+ elsif( $line =~ m/^(output) (.*)$/ ) {
+ $line = "$1 " . join ",", map sprintf("%x", $_), unpack "C*", eval($2);
+ }
+ elsif( $line =~ m/^control (.*)$/ ) {
+ $line = sprintf "control %02x", eval($1);
+ }
+ elsif( $line =~ m/^csi (\S+) (.*)$/ ) {
+ $line = sprintf "csi %02x %s", eval($1), $2; # TODO
+ }
+ elsif( $line =~ m/^(escape|osc|dcs) (.*)$/ ) {
+ $line = "$1 " . join "", map sprintf("%02x", $_), unpack "C*", eval($2);
+ }
+ elsif( $line =~ m/^putglyph (\S+) (.*)$/ ) {
+ $line = "putglyph " . join( ",", map sprintf("%x", $_), eval($1) ) . " $2";
+ }
+ elsif( $line =~ m/^(?:movecursor|scrollrect|moverect|erase|damage|sb_pushline|sb_popline|settermprop|setmousefunc) / ) {
+ # no conversion
+ }
+ else {
+ warn "Unrecognised test expectation '$line'\n";
+ }
+
+ push @expect, $line;
+ }
+ # ?screen_row assertion is emulated here
+ elsif( $line =~ s/^\?screen_row\s+(\d+)\s*=\s*// ) {
+ my $row = $1;
+ my $row1 = $row + 1;
+ my $want = eval($line);
+
+ do_onetest if defined $command;
+
+ # TODO: may not be 80
+ $hin->print( "\?screen_chars $row,0,$row1,80\n" );
+ my $response = <$hout>;
+ chomp $response;
+
+ $response = pack "C*", map hex, split m/,/, $response;
+ if( $response ne $want ) {
+ print "# Assert ?screen_row $row failed:\n" .
+ "# Expected: $want\n" .
+ "# Actual: $response\n";
+ $exitcode = 1;
+ }
+ }
+ # Assertions start with '?'
+ elsif( $line =~ s/^\?([a-z]+.*?=)\s+// ) {
+ do_onetest if defined $command;
+
+ my ( $assertion ) = $1 =~ m/^(.*)\s+=/;
+
+ $hin->print( "\?$assertion\n" );
+ my $response = <$hout>; defined $response or wait, die "Test harness failed - $?\n";
+ chomp $response;
+
+ if( $response ne $line ) {
+ print "# Assert $assertion failed:\n" .
+ "# Expected: $line\n" .
+ "# Actual: $response\n";
+ $exitcode = 1;
+ }
+ }
+ # Test controls start with '$'
+ elsif( $line =~ s/\$SEQ\s+(\d+)\s+(\d+):\s*// ) {
+ my ( $low, $high ) = ( $1, $2 );
+ foreach my $val ( $low .. $high ) {
+ ( my $inner = $line ) =~ s/\\#/$val/g;
+ do_line( $inner );
+ }
+ }
+ elsif( $line =~ s/\$REP\s+(\d+):\s*// ) {
+ my $count = $1;
+ do_line( $line ) for 1 .. $count;
+ }
+ else {
+ die "Unrecognised TEST line $line\n";
+ }
+}
+
+open my $test, "<", $ARGV[0] or die "Cannot open test script $ARGV[0] - $!";
+
+while( my $line = <$test> ) {
+ $line =~ s/^\s+//;
+ next if $line =~ m/^(?:#|$)/;
+
+ chomp $line;
+ do_line( $line );
+}
+
+do_onetest if defined $command;
+
+close $hin;
+close $hout;
+
+waitpid $hpid, 0;
+if( $? ) {
+ printf STDERR "Harness exited %d\n", WEXITSTATUS($?) if WIFEXITED($?);
+ printf STDERR "Harness exit signal %d\n", WTERMSIG($?) if WIFSIGNALED($?);
+ $exitcode = WIFEXITED($?) ? WEXITSTATUS($?) : 125;
+}
+
+exit $exitcode;
diff --git a/src/libvterm/tbl2inc_c.pl b/src/libvterm/tbl2inc_c.pl
new file mode 100644
index 0000000..527fc98
--- /dev/null
+++ b/src/libvterm/tbl2inc_c.pl
@@ -0,0 +1,51 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+my ( $encname ) = $ARGV[0] =~ m{/([^/.]+).tbl}
+ or die "Cannot parse encoding name out of $ARGV[0]\n";
+
+print <<"EOF";
+static const struct StaticTableEncoding encoding_$encname = {
+ {
+ NULL, /* init */
+ &decode_table /* decode */
+ },
+ {
+EOF
+
+my $row = 0;
+while( <> ) {
+ s/\s*#.*//; # strip comment
+
+ if ($_ =~ m{^\d+/\d+}) {
+ my ($up, $low) = ($_ =~ m{^(\d+)/(\d+)});
+ my $thisrow = $up * 16 + $low;
+ while ($row < $thisrow) {
+ print " 0x0, /* $row */\n";
+ ++$row;
+ }
+ }
+
+ s{^(\d+)/(\d+)}{""}e; # Remove 3/1
+ s{ = }{""}e; # Remove " = "
+ s{"(.)"}{sprintf "0x%04x", ord $1}e; # Convert "A" to 0x41
+ s{U\+}{0x}; # Convert U+0041 to 0x0041
+
+ s{$}{, /* $row */}; # append comma and index
+
+ print " $_";
+
+ ++$row;
+}
+
+while ($row < 128) {
+ print " 0x0, /* $row */\n";
+ ++$row;
+}
+
+print <<"EOF";
+ }
+};
+EOF
diff --git a/src/libvterm/vterm.pc.in b/src/libvterm/vterm.pc.in
new file mode 100644
index 0000000..c64c72d
--- /dev/null
+++ b/src/libvterm/vterm.pc.in
@@ -0,0 +1,9 @@
+prefix=@PREFIX@
+libdir=@LIBDIR@
+includedir=${prefix}/include
+
+Name: vterm
+Description: Abstract VT220/Xterm/ECMA-48 emulation library
+Version: @VERSION@
+Libs: -L${libdir} -lvterm
+Cflags: -I${includedir}