summaryrefslogtreecommitdiffstats
path: root/src/libvterm
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 08:50:31 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 08:50:31 +0000
commitaed8ce9da277f5ecffe968b324f242c41c3b752a (patch)
treed2e538394cb7a8a7c42a4aac6ccf1a8e3256999b /src/libvterm
parentInitial commit. (diff)
downloadvim-aed8ce9da277f5ecffe968b324f242c41c3b752a.tar.xz
vim-aed8ce9da277f5ecffe968b324f242c41c3b752a.zip
Adding upstream version 2:9.0.1378.upstream/2%9.0.1378upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--src/libvterm/.bzrignore13
-rw-r--r--src/libvterm/.gitignore17
-rw-r--r--src/libvterm/CONTRIBUTING22
-rw-r--r--src/libvterm/LICENSE23
-rw-r--r--src/libvterm/Makefile142
-rw-r--r--src/libvterm/README39
-rw-r--r--src/libvterm/doc/URLs14
-rw-r--r--src/libvterm/doc/seqs.txt283
-rw-r--r--src/libvterm/find-wide-chars.pl30
-rw-r--r--src/libvterm/include/vterm.h671
-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/fullwidth.inc111
-rw-r--r--src/libvterm/src/keyboard.c259
-rw-r--r--src/libvterm/src/mouse.c101
-rw-r--r--src/libvterm/src/parser.c404
-rw-r--r--src/libvterm/src/pen.c629
-rw-r--r--src/libvterm/src/rect.h56
-rw-r--r--src/libvterm/src/screen.c1206
-rw-r--r--src/libvterm/src/state.c2473
-rw-r--r--src/libvterm/src/unicode.c587
-rw-r--r--src/libvterm/src/utf8.h47
-rw-r--r--src/libvterm/src/vterm.c451
-rw-r--r--src/libvterm/src/vterm_internal.h329
-rw-r--r--src/libvterm/t/02parser.test266
-rw-r--r--src/libvterm/t/03encoding_utf8.test122
-rw-r--r--src/libvterm/t/10state_putglyph.test74
-rw-r--r--src/libvterm/t/11state_movecursor.test224
-rw-r--r--src/libvterm/t/12state_scroll.test156
-rw-r--r--src/libvterm/t/13state_edit.test304
-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.test181
-rw-r--r--src/libvterm/t/18state_termprops.test42
-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.test155
-rw-r--r--src/libvterm/t/26state_query.test67
-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.test31
-rw-r--r--src/libvterm/t/30state_pen.test125
-rw-r--r--src/libvterm/t/31state_rep.test128
-rw-r--r--src/libvterm/t/32state_flow.test28
-rw-r--r--src/libvterm/t/40state_selection.test55
-rw-r--r--src/libvterm/t/60screen_ascii.test69
-rw-r--r--src/libvterm/t/61screen_unicode.test47
-rw-r--r--src/libvterm/t/62screen_damage.test155
-rw-r--r--src/libvterm/t/63screen_resize.test117
-rw-r--r--src/libvterm/t/64screen_pen.test61
-rw-r--r--src/libvterm/t/65screen_protect.test16
-rw-r--r--src/libvterm/t/66screen_extent.test11
-rw-r--r--src/libvterm/t/67screen_dbl_wh.test38
-rw-r--r--src/libvterm/t/68screen_termprops.test17
-rw-r--r--src/libvterm/t/69screen_reflow.test79
-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.c1233
-rw-r--r--src/libvterm/t/run-test.pl233
-rw-r--r--src/libvterm/tbl2inc_c.pl51
-rw-r--r--src/libvterm/vterm.pc.in8
74 files changed, 13404 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/CONTRIBUTING b/src/libvterm/CONTRIBUTING
new file mode 100644
index 0000000..e9a8f0c
--- /dev/null
+++ b/src/libvterm/CONTRIBUTING
@@ -0,0 +1,22 @@
+How to Contribute
+-----------------
+
+The main resources for this library are:
+
+ Launchpad
+ https://launchpad.net/libvterm
+
+ IRC:
+ ##tty or #tickit on irc.libera.chat
+
+ Email:
+ Paul "LeoNerd" Evans <leonerd@leonerd.org.uk>
+
+
+Bug reports and feature requests can be sent to any of the above resources.
+
+New features, bug patches, etc.. should in the first instance be discussed via
+any of the resources listed above, before starting work on the actual code.
+There may be future plans or development already in-progress that could be
+affected so it is better to discuss the ideas first before starting work
+actually writing any code.
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..ba4a315
--- /dev/null
+++ b/src/libvterm/Makefile
@@ -0,0 +1,142 @@
+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=3
+
+VERSION_CURRENT=0
+VERSION_REVISION=0
+VERSION_AGE=0
+
+VERSION=$(VERSION_MAJOR).$(VERSION_MINOR)
+
+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/fullwidth.inc:
+ @perl find-wide-chars.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) -static
+
+.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,@INCDIR@,$(INCDIR)," -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
+
+DISTDIR=libvterm-$(VERSION)
+
+distdir: $(INCFILES)
+ mkdir __distdir
+ cp LICENSE CONTRIBUTING __distdir
+ mkdir __distdir/src
+ cp src/*.c src/*.h src/*.inc __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..2641284
--- /dev/null
+++ b/src/libvterm/README
@@ -0,0 +1,39 @@
+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:
+- revisions up to 817 have been included
+- Added a .gitignore file.
+- use TRUE and FALSE instead of true and false
+- use int or unsigned int instead of bool
+- Converted some code from C99 to C90.
+- Other changes to support embedding in Vim.
+
+To get the latest version of libvterm you need the "bzr" command and do:
+ bzr co http://bazaar.leonerd.org.uk/c/libvterm/
+
+To find the diff of a libvterm revision edit this URL, changing "999" to the
+patch number:
+ https://bazaar.launchpad.net/~libvterm/libvterm/trunk/diff/999?context=3
+
+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/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..27f28b9
--- /dev/null
+++ b/src/libvterm/doc/seqs.txt
@@ -0,0 +1,283 @@
+Sequences documented in parens are implicit ones from parser.c, which move
+between states.
+
+1 = VT100
+2 = VT220
+3 = VT320
+x = xterm
+
+ C0 controls
+
+123 0x00 = NUL
+123x 0x07 = BEL
+123x 0x08 = BS
+123x 0x09 = HT
+123x 0x0A = LF
+123x 0x0B = VT
+123x 0x0C = FF
+123x 0x0D = CR
+123x 0x0E = LS1
+123x 0x0F = LS0
+ (0x18 = CAN)
+ (0x1A = SUB)
+ (0x1B = ESC)
+
+123 0x7f = DEL (ignored)
+
+ C1 controls
+
+123x 0x84 = IND
+123x 0x85 = NEL
+123x 0x88 = HTS
+123x 0x8D = RI
+ 23x 0x8E = SS2
+ 23x 0x8F = SS3
+ (0x90 = DCS)
+ (0x98 = SOS)
+ (0x9B = CSI)
+ (0x9C = ST)
+ (0x9D = OSC)
+ (0x9E = PM)
+ (0x9F = APC)
+
+ Escape sequences
+ - excluding sequences that are C1 aliases
+
+123x ESC ( = SCS, select character set G0
+123x ESC ) = SCS, select character set G1
+ 23x ESC * = SCS, select character set G2
+ 23x ESC + = SCS, select character set G3
+123x ESC 7 = DECSC - save cursor
+123x ESC 8 = DECRC - restore cursor
+123x ESC # 3 = DECDHL, double-height line (top half)
+123x ESC # 4 = DECDHL, double-height line (bottom half)
+123x ESC # 5 = DECSWL, single-width single-height line
+123x ESC # 6 = DECDWL, double-width single-height line
+123x ESC # 8 = DECALN
+123 ESC < = Ignored (used by VT100 to exit VT52 mode)
+123x ESC = = DECKPAM, keypad application mode
+123x ESC > = DECKPNM, keypad numeric mode
+ 23x ESC Sp F = S7C1T
+ 23x ESC Sp G = S8C1T
+ (ESC P = DCS)
+ (ESC X = SOS)
+ (ESC [ = CSI)
+ (ESC \ = ST)
+ (ESC ] = OSC)
+ (ESC ^ = PM)
+ (ESC _ = APC)
+123x ESC c = RIS, reset initial state
+ 3x ESC n = LS2
+ 3x ESC o = LS3
+ 3x ESC | = LS3R
+ 3x ESC } = LS2R
+ 3x ESC ~ = LS1R
+
+ DCSes
+
+ 3x DCS $ q ST = DECRQSS
+ 3x m = Request SGR
+ x Sp q = Request DECSCUSR
+ 3x " q = Request DECSCA
+ 3x r = Request DECSTBM
+ x s = Request DECSLRM
+
+ CSIs
+ 23x CSI @ = ICH
+123x CSI A = CUU
+123x CSI B = CUD
+123x CSI C = CUF
+123x CSI D = CUB
+ x CSI E = CNL
+ x CSI F = CPL
+ x CSI G = CHA
+123x CSI H = CUP
+ x CSI I = CHT
+123x CSI J = ED
+ 23x CSI ? J = DECSED, selective erase in display
+123x CSI K = EL
+ 23x CSI ? K = DECSEL, selective erase in line
+ 23x CSI L = IL
+ 23x CSI M = DL
+ 23x CSI P = DCH
+ x CSI S = SU
+ x CSI T = SD
+ 23x CSI X = ECH
+ x CSI Z = CBT
+ x CSI ` = HPA
+ x CSI a = HPR
+ x CSI b = REP
+123x CSI c = DA, device attributes
+123 0 = DA
+ 23x CSI > c = DECSDA
+ 23 0 = SDA
+ x CSI d = VPA
+ x CSI e = VPR
+123x CSI f = HVP
+123x CSI g = TBC
+123x CSI h = SM, Set mode
+123x CSI ? h = DECSM, DEC set mode
+ CSI j = HPB
+ CSI k = VPB
+123x CSI l = RM, Reset mode
+123x CSI ? l = DECRM, DEC reset mode
+123x CSI m = SGR, Set Graphic Rendition
+ CSI ? m = DECSGR, private Set Graphic Rendition
+123x CSI n = DSR, Device Status Report
+ 23x 5 = operating status
+ 23x 6 = CPR = cursor position
+ 23x CSI ? n = DECDSR; behaves as DSR but uses CSI ? instead of CSI to respond
+ 23x CSI ! p = DECSTR, soft terminal reset
+ 3x CSI ? $ p = DECRQM, request private mode
+ x 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
+ x CSI > q = XTVERSION, request version string
+ 23x CSI " q = DECSCA, select character attributes
+123x CSI r = DECSTBM
+ x CSI s = DECSLRM
+ x CSI ' } = DECIC
+ x CSI ' ~ = DECDC
+
+ OSCs
+
+ x OSC 0; = Set icon name and title
+ x OSC 1; = Set icon name
+ x OSC 2; = Set title
+ x OSC 52; = Selection management
+
+ Standard modes
+
+ 23x SM 4 = IRM
+123x SM 20 = NLM, linefeed/newline
+
+ DEC modes
+
+123x DECSM 1 = DECCKM, cursor keys
+123x DECSM 5 = DECSCNM, screen
+123x DECSM 6 = DECOM, origin
+123x DECSM 7 = DECAWM, autowrap
+ x DECSM 12 = Cursor blink
+ 23x DECSM 25 = DECTCEM, text cursor enable
+ x DECSM 69 = DECVSSM, vertical screen split
+ x DECSM 1000 = Mouse click/release tracking
+ x DECSM 1002 = Mouse click/release/drag tracking
+ x DECSM 1003 = Mouse all movements tracking
+ x DECSM 1004 = Focus in/out reporting
+ x DECSM 1005 = Mouse protocol extended (UTF-8) - not recommended
+ x DECSM 1006 = Mouse protocol SGR
+ x DECSM 1015 = Mouse protocol rxvt
+ x DECSM 1047 = Altscreen
+ x DECSM 1048 = Save cursor
+ x DECSM 1049 = 1047 + 1048
+ x DECSM 2004 = Bracketed paste
+
+ Graphic Renditions
+
+123x SGR 0 = Reset
+123x SGR 1 = Bold on
+ x SGR 3 = Italic on
+123x SGR 4 = Underline single
+ SGR 4:x = Underline style
+123x SGR 5 = Blink on
+123x SGR 7 = Reverse on
+ x SGR 8 = Conceal on
+ x SGR 9 = Strikethrough on
+ SGR 10-19 = Select font
+ x SGR 21 = Underline double
+ 23x SGR 22 = Bold off
+ x SGR 23 = Italic off
+ 23x SGR 24 = Underline off
+ 23x SGR 25 = Blink off
+ 23x SGR 27 = Reverse off
+ x SGR 28 = Conceal off
+ x SGR 29 = Strikethrough off
+ x SGR 30-37 = Foreground ANSI
+ x SGR 38 = Foreground alternative palette
+ x SGR 39 = Foreground default
+ x SGR 40-47 = Background ANSI
+ x SGR 48 = Background alternative palette
+ x SGR 49 = Background default
+ SGR 73 = Superscript on
+ SGR 74 = Subscript on
+ SGR 75 = Superscript/subscript off
+ x SGR 90-97 = Foreground ANSI high-intensity
+ x 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.
+
+123x 0x05 = ENQ
+ 3 0x11 = DC1 (XON)
+ 3 0x13 = DC3 (XOFF)
+ x ESC % @ = Select default character set
+ x ESC % G = Select UTF-8 character set
+ x ESC 6 = DECBI, Back Index
+12 ESC Z = DECID, identify terminal
+ x DCS + Q = XTGETXRES, Request resource values
+ DCS $ q = [DECRQSS]
+ 3x " p = Request DECSCL
+ x t = Request DECSLPP
+ x $ | = Request DECSCPP
+ x * | = Request DECSLNS
+ 3 $ } = Request DECSASD
+ 3 $ ~ = Request DECSSDT
+ x DCS + p = XTSETTCAP, set termcap/terminfo data
+ x DCS + q = XTGETTCAP, request termcap/terminfo
+ 23 DCS { = DECDLD, down-line-loadable character set
+ 23x DCS | = DECUDK, user-defined key
+ x CSI Sp @ = Shift left columns
+ x CSI Sp A = Shift right columns
+ x CSI # P = XTPUSHCOLORS, push current dynamic colours to stack
+ x CSI # Q = XTPOPCOLORS, pop dynamic colours from stack
+ x CSI # R = XTREPORTCOLORS, report current entry on palette stack
+ x CSI ? S = XTSMGRAPHICS, set/request graphics attribute
+ x CSI > T = XTRMTITLE, reset title mode features
+ 23x CSI i = DEC printer control
+ x CSI > m = XTMODKEYS, set key modifier options
+ x CSI > n = (XTMODKEYS), reset key modifier options
+ x CSI $ p = DECRQM, request ANSI mode
+ 23x CSI " p = DECSCL, set compatibility level
+ x CSI > p = XTSMPOINTER, set resource value pointer mode
+1 x CSI q = DECLL, load LEDs
+ x CSI ? r = XTRESTORE, restore DEC private mode values
+ x CSI $ r = DECCARA, change attributes in rectangular area
+ x CSI > s = XTSHIFTESCAPE, set/reset shift-escape options
+ x CSI ? s = XTSAVE, save DEC private mode values
+ x CSI t = XTWINOPS, window operations
+ x CSI > t = XTSMTITLE, set title mode features
+ x CSI $ t = DECRARA, reset attributes in rectangular area
+ 3 CSI $ u = DECRQTSR, request terminal state report
+ 3 1 = terminal state report
+ 3 CSI & u = DECRQUPSS, request user-preferred supplemental set
+ x CSI $ v = DECCRA, copy rectangular area
+ 3x CSI $ w = DECRQPSR, request presentation state report
+ 3x 1 = cursor information report
+ 3x 2 = tab stop report
+ x CSI ' w = DECEFR, enable filter rectangle
+1 x CSI x = DECREQTPARM, request terminal parameters
+ x CSI * x = DECSACE, select attribute change extent
+ x CSI $ x = DECFRA, fill rectangular area
+123 CSI y = DECTST, invoke confidence test
+ x CSI $ z = DECERA, erase rectangular area
+ x CSI # { = XTPUSHSGR, push video attributes onto stack
+ x CSI $ { = DECSERA, selective erase in rectangular area
+ x CSI # | = XTREPORTSGR, report selected graphic rendition
+ x CSI $ | = DECSCPP, select columns per page
+ x CSI # } = XTPOPSGR, pop video attributes from stack
+ 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/find-wide-chars.pl b/src/libvterm/find-wide-chars.pl
new file mode 100644
index 0000000..f7f2205
--- /dev/null
+++ b/src/libvterm/find-wide-chars.pl
@@ -0,0 +1,30 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+STDOUT->autoflush(1);
+
+sub iswide
+{
+ my ( $cp ) = @_;
+ return chr($cp) =~ m/\p{East_Asian_Width=Wide}|\p{East_Asian_Width=Fullwidth}/;
+}
+
+my ( $start, $end );
+foreach my $cp ( 0 .. 0x1FFFF ) {
+ iswide($cp) or next;
+
+ if( defined $end and $end == $cp-1 ) {
+ # extend the range
+ $end = $cp;
+ next;
+ }
+
+ # start a new range
+ printf " { %#04x, %#04x },\n", $start, $end if defined $start;
+
+ $start = $end = $cp;
+}
+
+printf " { %#04x, %#04x },\n", $start, $end if defined $start;
diff --git a/src/libvterm/include/vterm.h b/src/libvterm/include/vterm.h
new file mode 100644
index 0000000..6a967db
--- /dev/null
+++ b/src/libvterm/include/vterm.h
@@ -0,0 +1,671 @@
+/*
+ * 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"
+
+// VIM: use TRUE and FALSE instead of true and false
+#define TRUE 1
+#define FALSE 0
+
+// VIM: from stdint.h
+typedef unsigned char uint8_t;
+typedef unsigned short uint16_t;
+typedef unsigned int uint32_t;
+
+#define VTERM_VERSION_MAJOR 0
+#define VTERM_VERSION_MINOR 3
+
+#define VTERM_CHECK_VERSION \
+ vterm_check_version(VTERM_VERSION_MAJOR, VTERM_VERSION_MINOR)
+
+/* Any cell can contain at most one basic printing character and 5 combining
+ * characters. This number could be changed but will be ABI-incompatible if
+ * you do */
+#define VTERM_MAX_CHARS_PER_CELL 6
+
+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;
+
+/* true if the rect contains the point */
+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 a rect */
+// 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
+
+/**
+ * Bit-field describing the value of VTermColor.type
+ */
+typedef enum {
+ /**
+ * If the lower bit of `type` is not set, the colour is 24-bit RGB.
+ */
+ VTERM_COLOR_RGB = 0x00,
+
+ /**
+ * The colour is an index into a palette of 256 colours.
+ */
+ VTERM_COLOR_INDEXED = 0x01,
+
+ /**
+ * Mask that can be used to extract the RGB/Indexed bit.
+ */
+ VTERM_COLOR_TYPE_MASK = 0x01,
+
+ /**
+ * If set, indicates that this colour should be the default foreground
+ * color, i.e. there was no SGR request for another colour. When
+ * rendering this colour it is possible to ignore "idx" and just use a
+ * colour that is not in the palette.
+ */
+ VTERM_COLOR_DEFAULT_FG = 0x02,
+
+ /**
+ * If set, indicates that this colour should be the default background
+ * color, i.e. there was no SGR request for another colour. A common
+ * option when rendering this colour is to not render a background at
+ * all, for example by rendering the window transparently at this spot.
+ */
+ VTERM_COLOR_DEFAULT_BG = 0x04,
+
+ /**
+ * Mask that can be used to extract the default foreground/background bit.
+ */
+ VTERM_COLOR_DEFAULT_MASK = 0x06,
+
+ /**
+ * VIM: If set, indicates that the color is invalid.
+ */
+ VTERM_COLOR_INVALID = 0x08
+} VTermColorType;
+
+/**
+ * Returns true if the VTERM_COLOR_RGB `type` flag is set, indicating that the
+ * given VTermColor instance is an indexed colour.
+ */
+#define VTERM_COLOR_IS_INDEXED(col) \
+ (((col)->type & VTERM_COLOR_TYPE_MASK) == VTERM_COLOR_INDEXED)
+
+/**
+ * Returns true if the VTERM_COLOR_INDEXED `type` flag is set, indicating that
+ * the given VTermColor instance is an rgb colour.
+ */
+#define VTERM_COLOR_IS_RGB(col) \
+ (((col)->type & VTERM_COLOR_TYPE_MASK) == VTERM_COLOR_RGB)
+
+/**
+ * Returns true if the VTERM_COLOR_DEFAULT_FG `type` flag is set, indicating
+ * that the given VTermColor instance corresponds to the default foreground
+ * color.
+ */
+#define VTERM_COLOR_IS_DEFAULT_FG(col) \
+ (!!((col)->type & VTERM_COLOR_DEFAULT_FG))
+
+/**
+ * Returns true if the VTERM_COLOR_DEFAULT_BG `type` flag is set, indicating
+ * that the given VTermColor instance corresponds to the default background
+ * color.
+ */
+#define VTERM_COLOR_IS_DEFAULT_BG(col) \
+ (!!((col)->type & VTERM_COLOR_DEFAULT_BG))
+
+/**
+ * Returns true if the VTERM_COLOR_INVALID `type` flag is set, indicating
+ * that the given VTermColor instance is an invalid color.
+ */
+#define VTERM_COLOR_IS_INVALID(col) (!!((col)->type & VTERM_COLOR_INVALID))
+
+// VIM: this was a union, but that doesn't always work.
+typedef struct {
+ /**
+ * Tag indicating which member is actually valid.
+ * Please use the `VTERM_COLOR_IS_*` test macros to check whether a
+ * particular type flag is set.
+ */
+ uint8_t type;
+
+ uint8_t red, green, blue;
+
+ uint8_t index;
+} VTermColor;
+
+/**
+ * Constructs a new VTermColor instance representing the given RGB values.
+ */
+void vterm_color_rgb(VTermColor *col, uint8_t red, uint8_t green, uint8_t blue);
+
+/**
+ * Construct a new VTermColor instance representing an indexed color with the
+ * given index.
+ */
+void vterm_color_indexed(VTermColor *col, uint8_t idx);
+
+/**
+ * Compares two colours. Returns true if the colors are equal, false otherwise.
+ */
+int vterm_color_is_equal(const VTermColor *a, const VTermColor *b);
+
+typedef enum {
+ /* VTERM_VALUETYPE_NONE = 0 */
+ VTERM_VALUETYPE_BOOL = 1,
+ VTERM_VALUETYPE_INT,
+ VTERM_VALUETYPE_STRING,
+ VTERM_VALUETYPE_COLOR,
+
+ VTERM_N_VALUETYPES
+} VTermValueType;
+
+typedef struct {
+ const char *str;
+ size_t len : 30;
+ unsigned int initial : 1;
+ unsigned int final : 1;
+} VTermStringFragment;
+
+typedef union {
+ int boolean;
+ int number;
+ VTermStringFragment 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_CONCEAL, // bool: 8, 28
+ 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_ATTR_SMALL, // bool: 73, 74, 75
+ VTERM_ATTR_BASELINE, // number: 73, 74, 75
+
+ 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, // VIM - 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 enum {
+ VTERM_SELECTION_CLIPBOARD = (1<<0),
+ VTERM_SELECTION_PRIMARY = (1<<1),
+ VTERM_SELECTION_SECONDARY = (1<<2),
+ VTERM_SELECTION_SELECT = (1<<3),
+ VTERM_SELECTION_CUT0 = (1<<4), /* also CUT1 .. CUT7 by bitshifting */
+} VTermSelectionMask;
+
+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) */
+ unsigned int continuation:1; /* Line is a flow continuation of the previous */
+} VTermLineInfo;
+
+/* Copies of VTermState fields that the 'resize' callback might have reason to
+ * edit. 'resize' callback gets total control of these fields and may
+ * free-and-reallocate them if required. They will be copied back from the
+ * struct after the callback has returned.
+ */
+typedef struct {
+ VTermPos pos; /* current cursor position */
+ VTermLineInfo *lineinfos[2]; /* [1] may be NULL */
+} VTermStateFields;
+
+typedef struct {
+ /* libvterm relies on this 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;
+
+void vterm_check_version(int major, int minor);
+
+struct VTermBuilder {
+ int ver; /* currently unused but reserved for some sort of ABI version flag */
+
+ int rows, cols;
+
+ const VTermAllocatorFunctions *allocator;
+ void *allocdata;
+
+ /* Override default sizes for various structures */
+ size_t outbuffer_len; /* default: 4096 */
+ size_t tmpbuffer_len; /* default: 4096 */
+};
+
+VTerm *vterm_build(const struct VTermBuilder *builder);
+
+/* A convenient shortcut for default cases */
+// Allocate and initialize a new terminal with default allocators.
+VTerm *vterm_new(int rows, int cols);
+/* These shortcuts are generally discouraged in favour of just using vterm_build() */
+
+// 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);
+
+/* Setting output callback will override the buffer logic */
+typedef void VTermOutputCallback(const char *s, size_t len, void *user);
+void vterm_output_set_callback(VTerm *vt, VTermOutputCallback *func, void *user);
+
+/* These buffer functions only work if output callback is NOT set
+ * These are deprecated and will be removed in a later version */
+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);
+
+/* This too */
+size_t vterm_output_read(VTerm *vt, char *buffer, size_t len);
+
+int vterm_is_modify_other_keys(VTerm *vt);
+int vterm_is_kitty_keyboard(VTerm *vt);
+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 */
+// VIM: changed 31 to 30 to avoid an overflow warning
+#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)(int command, VTermStringFragment frag, void *user);
+ int (*dcs)(const char *command, size_t commandlen, VTermStringFragment frag, void *user);
+ int (*apc)(VTermStringFragment frag, void *user);
+ int (*pm)(VTermStringFragment frag, void *user);
+ int (*sos)(VTermStringFragment frag, 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, VTermStateFields *fields, void *user);
+ int (*setlineinfo)(int row, const VTermLineInfo *newinfo, const VTermLineInfo *oldinfo, void *user);
+ int (*sb_clear)(void *user);
+} VTermStateCallbacks;
+
+// VIM: added
+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;
+
+typedef struct {
+ int (*control)(unsigned char control, void *user);
+ int (*csi)(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user);
+ int (*osc)(int command, VTermStringFragment frag, void *user);
+ int (*dcs)(const char *command, size_t commandlen, VTermStringFragment frag, void *user);
+ int (*apc)(VTermStringFragment frag, void *user);
+ int (*pm)(VTermStringFragment frag, void *user);
+ int (*sos)(VTermStringFragment frag, void *user);
+} VTermStateFallbacks;
+
+typedef struct {
+ int (*set)(VTermSelectionMask mask, VTermStringFragment frag, void *user);
+ int (*query)(VTermSelectionMask mask, void *user);
+} VTermSelectionCallbacks;
+
+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);
+
+void vterm_state_set_unrecognised_fallbacks(VTermState *state, const VTermStateFallbacks *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);
+// VIM: added
+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);
+
+/**
+ * Makes sure that the given color `col` is indeed an RGB colour. After this
+ * function returns, VTERM_COLOR_IS_RGB(col) will return true, while all other
+ * flags stored in `col->type` will have been reset.
+ *
+ * @param state is the VTermState instance from which the colour palette should
+ * be extracted.
+ * @param col is a pointer at the VTermColor instance that should be converted
+ * to an RGB colour.
+ */
+void vterm_state_convert_color_to_rgb(const VTermState *state, VTermColor *col);
+
+void vterm_state_set_selection_callbacks(VTermState *state, const VTermSelectionCallbacks *callbacks, void *user,
+ char *buffer, size_t buflen);
+
+void vterm_state_send_selection(VTermState *state, VTermSelectionMask mask, VTermStringFragment frag);
+
+// ------------
+// 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 conceal : 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) */
+ unsigned int small : 1;
+ unsigned int baseline : 2;
+} VTermScreenCellAttrs;
+
+enum {
+ VTERM_UNDERLINE_OFF,
+ VTERM_UNDERLINE_SINGLE,
+ VTERM_UNDERLINE_DOUBLE,
+ VTERM_UNDERLINE_CURLY,
+};
+
+enum {
+ VTERM_BASELINE_NORMAL,
+ VTERM_BASELINE_RAISE,
+ VTERM_BASELINE_LOWER,
+};
+
+typedef struct {
+ 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);
+ int (*sb_clear)(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);
+
+void vterm_screen_set_unrecognised_fallbacks(VTermScreen *screen, const VTermStateFallbacks *fallbacks, void *user);
+void *vterm_screen_get_unrecognised_fbdata(VTermScreen *screen);
+
+void vterm_screen_enable_reflow(VTermScreen *screen, int reflow);
+
+// Back-compat alias for the brief time it was in 0.3-RC1
+#define vterm_screen_set_reflow vterm_screen_enable_reflow
+
+// 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_ATTR_CONCEAL_MASK = 1 << 9,
+ VTERM_ATTR_SMALL_MASK = 1 << 10,
+ VTERM_ATTR_BASELINE_MASK = 1 << 11,
+
+ VTERM_ALL_ATTRS_MASK = (1 << 12) - 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);
+
+/**
+ * Same as vterm_state_convert_color_to_rgb(), but takes a `screen` instead of a `state`
+ * instance.
+ */
+void vterm_screen_convert_color_to_rgb(const VTermScreen *screen, VTermColor *col);
+
+// ---------
+// 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..e5fb5ab
--- /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..817344f
--- /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;
+ // VIM: avoid going over the end
+ 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)
+{
+ for(int 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/fullwidth.inc b/src/libvterm/src/fullwidth.inc
new file mode 100644
index 0000000..a703529
--- /dev/null
+++ b/src/libvterm/src/fullwidth.inc
@@ -0,0 +1,111 @@
+ { 0x1100, 0x115f },
+ { 0x231a, 0x231b },
+ { 0x2329, 0x232a },
+ { 0x23e9, 0x23ec },
+ { 0x23f0, 0x23f0 },
+ { 0x23f3, 0x23f3 },
+ { 0x25fd, 0x25fe },
+ { 0x2614, 0x2615 },
+ { 0x2648, 0x2653 },
+ { 0x267f, 0x267f },
+ { 0x2693, 0x2693 },
+ { 0x26a1, 0x26a1 },
+ { 0x26aa, 0x26ab },
+ { 0x26bd, 0x26be },
+ { 0x26c4, 0x26c5 },
+ { 0x26ce, 0x26ce },
+ { 0x26d4, 0x26d4 },
+ { 0x26ea, 0x26ea },
+ { 0x26f2, 0x26f3 },
+ { 0x26f5, 0x26f5 },
+ { 0x26fa, 0x26fa },
+ { 0x26fd, 0x26fd },
+ { 0x2705, 0x2705 },
+ { 0x270a, 0x270b },
+ { 0x2728, 0x2728 },
+ { 0x274c, 0x274c },
+ { 0x274e, 0x274e },
+ { 0x2753, 0x2755 },
+ { 0x2757, 0x2757 },
+ { 0x2795, 0x2797 },
+ { 0x27b0, 0x27b0 },
+ { 0x27bf, 0x27bf },
+ { 0x2b1b, 0x2b1c },
+ { 0x2b50, 0x2b50 },
+ { 0x2b55, 0x2b55 },
+ { 0x2e80, 0x2e99 },
+ { 0x2e9b, 0x2ef3 },
+ { 0x2f00, 0x2fd5 },
+ { 0x2ff0, 0x2ffb },
+ { 0x3000, 0x303e },
+ { 0x3041, 0x3096 },
+ { 0x3099, 0x30ff },
+ { 0x3105, 0x312f },
+ { 0x3131, 0x318e },
+ { 0x3190, 0x31ba },
+ { 0x31c0, 0x31e3 },
+ { 0x31f0, 0x321e },
+ { 0x3220, 0x3247 },
+ { 0x3250, 0x4dbf },
+ { 0x4e00, 0xa48c },
+ { 0xa490, 0xa4c6 },
+ { 0xa960, 0xa97c },
+ { 0xac00, 0xd7a3 },
+ { 0xf900, 0xfaff },
+ { 0xfe10, 0xfe19 },
+ { 0xfe30, 0xfe52 },
+ { 0xfe54, 0xfe66 },
+ { 0xfe68, 0xfe6b },
+ { 0xff01, 0xff60 },
+ { 0xffe0, 0xffe6 },
+ { 0x16fe0, 0x16fe3 },
+ { 0x17000, 0x187f7 },
+ { 0x18800, 0x18af2 },
+ { 0x1b000, 0x1b11e },
+ { 0x1b150, 0x1b152 },
+ { 0x1b164, 0x1b167 },
+ { 0x1b170, 0x1b2fb },
+ { 0x1f004, 0x1f004 },
+ { 0x1f0cf, 0x1f0cf },
+ { 0x1f18e, 0x1f18e },
+ { 0x1f191, 0x1f19a },
+ { 0x1f200, 0x1f202 },
+ { 0x1f210, 0x1f23b },
+ { 0x1f240, 0x1f248 },
+ { 0x1f250, 0x1f251 },
+ { 0x1f260, 0x1f265 },
+ { 0x1f300, 0x1f320 },
+ { 0x1f32d, 0x1f335 },
+ { 0x1f337, 0x1f37c },
+ { 0x1f37e, 0x1f393 },
+ { 0x1f3a0, 0x1f3ca },
+ { 0x1f3cf, 0x1f3d3 },
+ { 0x1f3e0, 0x1f3f0 },
+ { 0x1f3f4, 0x1f3f4 },
+ { 0x1f3f8, 0x1f43e },
+ { 0x1f440, 0x1f440 },
+ { 0x1f442, 0x1f4fc },
+ { 0x1f4ff, 0x1f53d },
+ { 0x1f54b, 0x1f54e },
+ { 0x1f550, 0x1f567 },
+ { 0x1f57a, 0x1f57a },
+ { 0x1f595, 0x1f596 },
+ { 0x1f5a4, 0x1f5a4 },
+ { 0x1f5fb, 0x1f64f },
+ { 0x1f680, 0x1f6c5 },
+ { 0x1f6cc, 0x1f6cc },
+ { 0x1f6d0, 0x1f6d2 },
+ { 0x1f6d5, 0x1f6d5 },
+ { 0x1f6eb, 0x1f6ec },
+ { 0x1f6f4, 0x1f6fa },
+ { 0x1f7e0, 0x1f7eb },
+ { 0x1f90d, 0x1f971 },
+ { 0x1f973, 0x1f976 },
+ { 0x1f97a, 0x1f9a2 },
+ { 0x1f9a5, 0x1f9aa },
+ { 0x1f9ae, 0x1f9ca },
+ { 0x1f9cd, 0x1f9ff },
+ { 0x1fa70, 0x1fa73 },
+ { 0x1fa78, 0x1fa7a },
+ { 0x1fa80, 0x1fa82 },
+ { 0x1fa90, 0x1fa95 },
diff --git a/src/libvterm/src/keyboard.c b/src/libvterm/src/keyboard.c
new file mode 100644
index 0000000..7a44670
--- /dev/null
+++ b/src/libvterm/src/keyboard.c
@@ -0,0 +1,259 @@
+#include "vterm_internal.h"
+
+#include <stdio.h>
+
+#include "utf8.h"
+
+// VIM: added
+int vterm_is_modify_other_keys(VTerm *vt)
+{
+ return vt->state->mode.modify_other_keys;
+}
+
+// VIM: added
+int vterm_is_kitty_keyboard(VTerm *vt)
+{
+ return vt->state->mode.kitty_keyboard;
+}
+
+
+void vterm_keyboard_unichar(VTerm *vt, uint32_t c, VTermModifier mod)
+{
+ // VIM: added modifyOtherKeys support
+ if (vterm_is_modify_other_keys(vt) && mod != 0) {
+ vterm_push_output_sprintf_ctrl(vt, C1_CSI, "27;%d;%d~", mod+1, c);
+ return;
+ }
+
+ // VIM: added kitty keyboard protocol support
+ if (vterm_is_kitty_keyboard(vt) && mod != 0) {
+ vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d;%du", c, mod+1);
+ return;
+ }
+
+ /* 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;
+ }
+
+ int needs_CSIu;
+ 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_SS3, 'P', 0 }, // F1
+ { KEYCODE_SS3, 'Q', 0 }, // F2
+ { KEYCODE_SS3, 'R', 0 }, // F3
+ { KEYCODE_SS3, '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)
+{
+ if(key == VTERM_KEY_NONE)
+ return;
+
+ keycodes_s k;
+ 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:
+ if (vterm_is_kitty_keyboard(vt) && mod != 0)
+ vterm_push_output_sprintf_ctrl(vt, C1_CSI, "9;%du", mod+1);
+ /* Shift-Tab is CSI Z but plain Tab is 0x09 */
+ else 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 (vterm_is_modify_other_keys(vt) && mod != 0)
+ vterm_push_output_sprintf_ctrl(vt, C1_CSI, "27;%d;%d~", mod+1, k.literal);
+ else if (vterm_is_kitty_keyboard(vt) && mod == 0 && k.literal == '\x1b')
+ vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%du", k.literal);
+ else if ((vterm_is_kitty_keyboard(vt) && mod != 0)
+ || (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..5f35d56
--- /dev/null
+++ b/src/libvterm/src/mouse.c
@@ -0,0 +1,101 @@
+#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/6/7 */
+ if(state->mouse_buttons == old_buttons && button < 4)
+ return;
+ if (!(state->mouse_flags & MOUSE_WANT_CLICK))
+ return;
+
+ if(!state->mouse_flags)
+ return;
+
+ if(button < 4) {
+ output_mouse(state, button-1, pressed, mod, state->mouse_col, state->mouse_row);
+ }
+ else if(button < 8) {
+ 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..0d1e12b
--- /dev/null
+++ b/src/libvterm/src/parser.c
@@ -0,0 +1,404 @@
+#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.v.csi.leader);
+ for(int argi = 0; argi < vt->parser.v.csi.argi; argi++) {
+ printf(" %lu", CSI_ARG(vt->parser.v.csi.args[argi]));
+ if(!CSI_ARG_HAS_MORE(vt->parser.v.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.v.csi.leaderlen ? vt->parser.v.csi.leader : NULL,
+ vt->parser.v.csi.args,
+ vt->parser.v.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 string_fragment(VTerm *vt, const char *str, size_t len, int final)
+{
+ VTermStringFragment frag;
+
+ frag.str = str;
+ frag.len = len;
+ frag.initial = vt->parser.string_initial;
+ frag.final = final;
+
+ switch(vt->parser.state) {
+ case OSC:
+ if(vt->parser.callbacks && vt->parser.callbacks->osc)
+ (*vt->parser.callbacks->osc)(vt->parser.v.osc.command, frag, vt->parser.cbdata);
+ break;
+
+ case DCS:
+ if(vt->parser.callbacks && vt->parser.callbacks->dcs)
+ (*vt->parser.callbacks->dcs)(vt->parser.v.dcs.command, vt->parser.v.dcs.commandlen, frag, vt->parser.cbdata);
+ break;
+
+ case APC:
+ if(vt->parser.callbacks && vt->parser.callbacks->apc)
+ (*vt->parser.callbacks->apc)(frag, vt->parser.cbdata);
+ break;
+
+ case PM:
+ if(vt->parser.callbacks && vt->parser.callbacks->pm)
+ (*vt->parser.callbacks->pm)(frag, vt->parser.cbdata);
+ break;
+
+ case SOS:
+ if(vt->parser.callbacks && vt->parser.callbacks->sos)
+ (*vt->parser.callbacks->sos)(frag, vt->parser.cbdata);
+ break;
+
+ case NORMAL:
+ case CSI_LEADER:
+ case CSI_ARGS:
+ case CSI_INTERMED:
+ case OSC_COMMAND:
+ case DCS_COMMAND:
+ break;
+ }
+
+ vt->parser.string_initial = FALSE;
+}
+
+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
+
+ vt->in_backspace = 0; // Count down with BS key and activate when
+ // it reaches 1
+
+ switch(vt->parser.state) {
+ case NORMAL:
+ case CSI_LEADER:
+ case CSI_ARGS:
+ case CSI_INTERMED:
+ case OSC_COMMAND:
+ case DCS_COMMAND:
+ string_start = NULL;
+ break;
+ case OSC:
+ case DCS:
+ case APC:
+ case PM:
+ case SOS:
+ string_start = bytes;
+ break;
+ }
+
+#define ENTER_STATE(st) do { vt->parser.state = st; string_start = NULL; } while(0)
+#define ENTER_NORMAL_STATE() ENTER_STATE(NORMAL)
+
+#define IS_STRING_STATE() (vt->parser.state >= OSC_COMMAND)
+
+ for( ; pos < len; pos++) {
+ unsigned char c = bytes[pos];
+ int c1_allowed = !vt->mode.utf8;
+
+ if(c == 0x00 || c == 0x7f) { // NUL, DEL
+ if(IS_STRING_STATE()) {
+ string_fragment(vt, string_start, bytes + pos - string_start, FALSE);
+ string_start = bytes + pos + 1;
+ }
+ continue;
+ }
+ if(c == 0x18 || c == 0x1a) { // CAN, SUB
+ vt->parser.in_esc = FALSE;
+ ENTER_NORMAL_STATE();
+ continue;
+ }
+ else if(c == 0x1b) { // ESC
+ vt->parser.intermedlen = 0;
+ if(!IS_STRING_STATE())
+ vt->parser.state = NORMAL;
+ vt->parser.in_esc = TRUE;
+ continue;
+ }
+ else if(c == 0x07 && // BEL, can stand for ST in OSC or DCS state
+ IS_STRING_STATE()) {
+ // fallthrough
+ }
+ else if(c < 0x20) { // other C0
+ if(vt->parser.state == SOS)
+ continue; // All other C0s permitted in SOS
+
+ if(vterm_get_special_pty_type() == 2) {
+ if(c == 0x08) // BS
+ // Set the trick for BS output after a sequence, to delay backspace
+ // activation
+ if(pos + 2 < len && bytes[pos + 1] == 0x20 && bytes[pos + 2] == 0x08)
+ vt->in_backspace = 2; // Trigger when count down to 1
+ }
+ if(IS_STRING_STATE())
+ string_fragment(vt, string_start, bytes + pos - string_start, FALSE);
+ do_control(vt, c);
+ if(IS_STRING_STATE())
+ string_start = bytes + pos + 1;
+ continue;
+ }
+ // else fallthrough
+
+ size_t string_len = bytes + pos - string_start;
+
+ if(vt->parser.in_esc) {
+ // Hoist an ESC letter into a C1 if we're not in a string mode
+ // Always accept ESC \ == ST even in string mode
+ if(!vt->parser.intermedlen &&
+ c >= 0x40 && c < 0x60 &&
+ ((!IS_STRING_STATE() || c == 0x5c))) {
+ c += 0x40;
+ c1_allowed = TRUE;
+ if(string_len)
+ string_len -= 1;
+ vt->parser.in_esc = FALSE;
+ }
+ else {
+ string_start = NULL;
+ vt->parser.state = NORMAL;
+ }
+ }
+
+ switch(vt->parser.state) {
+ case CSI_LEADER:
+ /* Extract leader bytes 0x3c to 0x3f */
+ if(c >= 0x3c && c <= 0x3f) {
+ if(vt->parser.v.csi.leaderlen < CSI_LEADER_MAX-1)
+ vt->parser.v.csi.leader[vt->parser.v.csi.leaderlen++] = c;
+ break;
+ }
+
+ /* else fallthrough */
+ vt->parser.v.csi.leader[vt->parser.v.csi.leaderlen] = 0;
+
+ vt->parser.v.csi.argi = 0;
+ vt->parser.v.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.v.csi.args[vt->parser.v.csi.argi] == CSI_ARG_MISSING)
+ vt->parser.v.csi.args[vt->parser.v.csi.argi] = 0;
+ vt->parser.v.csi.args[vt->parser.v.csi.argi] *= 10;
+ vt->parser.v.csi.args[vt->parser.v.csi.argi] += c - '0';
+ break;
+ }
+ if(c == ':') {
+ vt->parser.v.csi.args[vt->parser.v.csi.argi] |= CSI_ARG_FLAG_MORE;
+ c = ';';
+ }
+ if(c == ';') {
+ vt->parser.v.csi.argi++;
+ vt->parser.v.csi.args[vt->parser.v.csi.argi] = CSI_ARG_MISSING;
+ break;
+ }
+
+ /* else fallthrough */
+ vt->parser.v.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 OSC_COMMAND:
+ /* Numerical value of command */
+ if(c >= '0' && c <= '9') {
+ if(vt->parser.v.osc.command == -1)
+ vt->parser.v.osc.command = 0;
+ else
+ vt->parser.v.osc.command *= 10;
+ vt->parser.v.osc.command += c - '0';
+ break;
+ }
+ if(c == ';') {
+ vt->parser.state = OSC;
+ string_start = bytes + pos + 1;
+ break;
+ }
+
+ /* else fallthrough */
+ string_start = bytes + pos;
+ string_len = 0;
+ vt->parser.state = OSC;
+ goto string_state;
+
+ case DCS_COMMAND:
+ if(vt->parser.v.dcs.commandlen < CSI_LEADER_MAX)
+ vt->parser.v.dcs.command[vt->parser.v.dcs.commandlen++] = c;
+
+ if(c >= 0x40 && c<= 0x7e) {
+ string_start = bytes + pos + 1;
+ vt->parser.state = DCS;
+ }
+ break;
+
+string_state:
+ case OSC:
+ case DCS:
+ case APC:
+ case PM:
+ case SOS:
+ if(c == 0x07 || (c1_allowed && c == 0x9c)) {
+ string_fragment(vt, string_start, string_len, TRUE);
+ ENTER_NORMAL_STATE();
+ }
+ break;
+
+ case NORMAL:
+ if(vt->parser.in_esc) {
+ if(is_intermed(c)) {
+ if(vt->parser.intermedlen < INTERMED_MAX-1)
+ vt->parser.intermed[vt->parser.intermedlen++] = c;
+ }
+ else if(c >= 0x30 && c < 0x7f) {
+ do_escape(vt, c);
+ vt->parser.in_esc = 0;
+ ENTER_NORMAL_STATE();
+ }
+ else {
+ DEBUG_LOG1("TODO: Unhandled byte %02x in Escape\n", c);
+ }
+ break;
+ }
+ if(c1_allowed && c >= 0x80 && c < 0xa0) {
+ switch(c) {
+ case 0x90: // DCS
+ vt->parser.string_initial = TRUE;
+ vt->parser.v.dcs.commandlen = 0;
+ ENTER_STATE(DCS_COMMAND);
+ break;
+ case 0x98: // SOS
+ vt->parser.string_initial = TRUE;
+ ENTER_STATE(SOS);
+ string_start = bytes + pos + 1;
+ string_len = 0;
+ break;
+ case 0x9b: // CSI
+ vt->parser.v.csi.leaderlen = 0;
+ ENTER_STATE(CSI_LEADER);
+ break;
+ case 0x9d: // OSC
+ vt->parser.v.osc.command = -1;
+ vt->parser.string_initial = TRUE;
+ string_start = bytes + pos + 1;
+ ENTER_STATE(OSC_COMMAND);
+ break;
+ case 0x9e: // PM
+ vt->parser.string_initial = TRUE;
+ ENTER_STATE(PM);
+ string_start = bytes + pos + 1;
+ string_len = 0;
+ break;
+ case 0x9f: // APC
+ vt->parser.string_initial = TRUE;
+ ENTER_STATE(APC);
+ string_start = bytes + pos + 1;
+ string_len = 0;
+ 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;
+ }
+ }
+
+ if(string_start) {
+ size_t string_len = bytes + pos - string_start;
+ if(vt->parser.in_esc)
+ string_len -= 1;
+ string_fragment(vt, string_start, string_len, FALSE);
+ }
+
+ 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..1c6cd4e
--- /dev/null
+++ b/src/libvterm/src/pen.c
@@ -0,0 +1,629 @@
+#include "vterm_internal.h"
+
+#include <stdio.h>
+
+/**
+ * Structure used to store RGB triples without the additional metadata stored in
+ * VTermColor.
+ */
+typedef struct {
+ uint8_t red, green, blue;
+} VTermRGB;
+
+static const VTermRGB ansi_colors[] = {
+ /* R G B */
+ { 0, 0, 0 }, // black
+ { 224, 0, 0 }, // red
+ { 0, 224, 0 }, // green
+ { 224, 224, 0 }, // yellow
+ { 0, 0, 224 }, // blue
+ { 224, 0, 224 }, // magenta
+ { 0, 224, 224 }, // cyan
+ { 224, 224, 224 }, // white == light grey
+
+ // high intensity
+ { 128, 128, 128 }, // black
+ { 255, 64, 64 }, // red
+ { 64, 255, 64 }, // green
+ { 255, 255, 64 }, // yellow
+ { 64, 64, 255 }, // blue
+ { 255, 64, 255 }, // magenta
+ { 64, 255, 255 }, // cyan
+ { 255, 255, 255 }, // 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 void lookup_default_colour_ansi(long idx, VTermColor *col)
+{
+ // VIM: store both RGB color and index
+ vterm_color_rgb(
+ col,
+ ansi_colors[idx].red, ansi_colors[idx].green, ansi_colors[idx].blue);
+ col->index = (uint8_t)idx;
+ col->type = VTERM_COLOR_INDEXED;
+}
+
+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;
+
+ vterm_color_rgb(col, ramp6[index/6/6 % 6],
+ ramp6[index/6 % 6],
+ ramp6[index % 6]);
+
+ return TRUE;
+ }
+ else if(index >= 232 && index < 256) {
+ // 24 greyscales
+ index -= 232;
+
+ vterm_color_rgb(col, ramp24[index], ramp24[index], ramp24[index]);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static int lookup_colour(const VTermState *state, int palette, const long args[], int argcount, VTermColor *col)
+{
+ switch(palette) {
+ case 2: // RGB mode - 3 args contain colour values directly
+ if(argcount < 3)
+ return argcount;
+
+ vterm_color_rgb(col, (uint8_t)CSI_ARG(args[0]), (uint8_t)CSI_ARG(args[1]), (uint8_t)CSI_ARG(args[2]));
+
+ return 3;
+
+ case 5: // XTerm 256-colour mode
+ if (!argcount || CSI_ARG_IS_MISSING(args[0])) {
+ return argcount ? 1 : 0;
+ }
+
+ lookup_colour_palette(state, args[0], col);
+ return 1;
+
+ 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)
+{
+ // 90% grey so that pure white is brighter
+ vterm_color_rgb(&state->default_fg, 240, 240, 240);
+ vterm_color_rgb(&state->default_bg, 0, 0, 0);
+ vterm_state_set_default_colors(state, &state->default_fg, &state->default_bg);
+
+ for(int col = 0; col < 16; col++)
+ lookup_default_colour_ansi(col, &state->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.conceal = 0; setpenattr_bool(state, VTERM_ATTR_CONCEAL, 0);
+ state->pen.strike = 0; setpenattr_bool(state, VTERM_ATTR_STRIKE, 0);
+ state->pen.font = 0; setpenattr_int (state, VTERM_ATTR_FONT, 0);
+ state->pen.small = 0; setpenattr_bool(state, VTERM_ATTR_SMALL, 0);
+ state->pen.baseline = 0; setpenattr_int (state, VTERM_ATTR_BASELINE, 0);
+
+ 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_CONCEAL, state->pen.conceal);
+ setpenattr_bool(state, VTERM_ATTR_STRIKE, state->pen.strike);
+ setpenattr_int (state, VTERM_ATTR_FONT, state->pen.font);
+ setpenattr_bool(state, VTERM_ATTR_SMALL, state->pen.small);
+ setpenattr_int (state, VTERM_ATTR_BASELINE, state->pen.baseline);
+
+ setpenattr_col( state, VTERM_ATTR_FOREGROUND, state->pen.fg);
+ setpenattr_col( state, VTERM_ATTR_BACKGROUND, state->pen.bg);
+ }
+}
+
+void vterm_color_rgb(VTermColor *col, uint8_t red, uint8_t green, uint8_t blue)
+{
+ col->type = VTERM_COLOR_RGB;
+ col->red = red;
+ col->green = green;
+ col->blue = blue;
+}
+
+void vterm_color_indexed(VTermColor *col, uint8_t idx)
+{
+ col->type = VTERM_COLOR_INDEXED;
+ col->index = idx;
+}
+
+int vterm_color_is_equal(const VTermColor *a, const VTermColor *b)
+{
+ /* First make sure that the two colours are of the same type (RGB/Indexed) */
+ if (a->type != b->type) {
+ return FALSE;
+ }
+
+ /* Depending on the type inspect the corresponding members */
+ if (VTERM_COLOR_IS_INDEXED(a)) {
+ return a->index == b->index;
+ }
+ else if (VTERM_COLOR_IS_RGB(a)) {
+ return (a->red == b->red)
+ && (a->green == b->green)
+ && (a->blue == b->blue);
+ }
+
+ return 0;
+}
+
+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)
+{
+ /* Copy the given colors */
+ state->default_fg = *default_fg;
+ state->default_bg = *default_bg;
+
+ /* Make sure the correct type flags are set */
+ state->default_fg.type = (state->default_fg.type & ~VTERM_COLOR_DEFAULT_MASK)
+ | VTERM_COLOR_DEFAULT_FG;
+ state->default_bg.type = (state->default_bg.type & ~VTERM_COLOR_DEFAULT_MASK)
+ | VTERM_COLOR_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].index = index + 1;
+ }
+}
+
+void vterm_state_convert_color_to_rgb(const VTermState *state, VTermColor *col)
+{
+ if (VTERM_COLOR_IS_INDEXED(col)) { /* Convert indexed colors to RGB */
+ lookup_colour_palette(state, col->index, col);
+ }
+ col->type &= VTERM_COLOR_TYPE_MASK; /* Reset any metadata but the type */
+}
+
+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
+ const VTermColor *fg = &state->pen.fg;
+ state->pen.bold = 1;
+ setpenattr_bool(state, VTERM_ATTR_BOLD, 1);
+ if(!VTERM_COLOR_IS_DEFAULT_FG(fg) && VTERM_COLOR_IS_INDEXED(fg) && fg->index < 8 && state->bold_is_highbright)
+ set_pen_col_ansi(state, VTERM_ATTR_FOREGROUND, 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
+ state->pen.underline = VTERM_UNDERLINE_SINGLE;
+ if(CSI_ARG_HAS_MORE(args[argi])) {
+ argi++;
+ switch(CSI_ARG(args[argi])) {
+ case 0:
+ state->pen.underline = 0;
+ break;
+ case 1:
+ state->pen.underline = VTERM_UNDERLINE_SINGLE;
+ break;
+ case 2:
+ state->pen.underline = VTERM_UNDERLINE_DOUBLE;
+ break;
+ case 3:
+ state->pen.underline = VTERM_UNDERLINE_CURLY;
+ break;
+ }
+ }
+ setpenattr_int(state, VTERM_ATTR_UNDERLINE, state->pen.underline);
+ 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 8: // Conceal on
+ state->pen.conceal = 1;
+ setpenattr_bool(state, VTERM_ATTR_CONCEAL, 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 = VTERM_UNDERLINE_DOUBLE;
+ setpenattr_int(state, VTERM_ATTR_UNDERLINE, state->pen.underline);
+ 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 28: // Conceal off (Reveal)
+ state->pen.conceal = 0;
+ setpenattr_bool(state, VTERM_ATTR_CONCEAL, 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;
+ 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
+ if(argcount - argi < 1)
+ return;
+ argi += 1 + lookup_colour(state, CSI_ARG(args[argi+1]), args+argi+2, argcount-argi-2, &state->pen.fg);
+ setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->pen.fg);
+ break;
+
+ case 39: // Foreground colour default
+ 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;
+ set_pen_col_ansi(state, VTERM_ATTR_BACKGROUND, value);
+ break;
+
+ case 48: // Background colour alternative palette
+ if(argcount - argi < 1)
+ return;
+ argi += 1 + lookup_colour(state, CSI_ARG(args[argi+1]), args+argi+2, argcount-argi-2, &state->pen.bg);
+ setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->pen.bg);
+ break;
+
+ case 49: // Default background
+ state->pen.bg = state->default_bg;
+ setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->pen.bg);
+ break;
+
+ case 73: // Superscript
+ case 74: // Subscript
+ case 75: // Superscript/subscript off
+ state->pen.small = (arg != 75);
+ state->pen.baseline =
+ (arg == 73) ? VTERM_BASELINE_RAISE :
+ (arg == 74) ? VTERM_BASELINE_LOWER :
+ VTERM_BASELINE_NORMAL;
+ setpenattr_bool(state, VTERM_ATTR_SMALL, state->pen.small);
+ setpenattr_int (state, VTERM_ATTR_BASELINE, state->pen.baseline);
+ 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;
+ 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;
+ set_pen_col_ansi(state, VTERM_ATTR_BACKGROUND, value);
+ break;
+
+ default:
+ done = 0;
+ break;
+ }
+
+ if(!done)
+ {
+ DEBUG_LOG1("libvterm: Unhandled CSI SGR %ld\n", arg);
+ }
+
+ while(CSI_ARG_HAS_MORE(args[argi++]))
+ ;
+ }
+}
+
+static int vterm_state_getpen_color(const VTermColor *col, int argi, long args[], int fg)
+{
+ /* Do nothing if the given color is the default color */
+ if (( fg && VTERM_COLOR_IS_DEFAULT_FG(col)) ||
+ (!fg && VTERM_COLOR_IS_DEFAULT_BG(col))) {
+ return argi;
+ }
+
+ /* Decide whether to send an indexed color or an RGB color */
+ if (VTERM_COLOR_IS_INDEXED(col)) {
+ const uint8_t idx = col->index;
+ if (idx < 8) {
+ args[argi++] = (idx + (fg ? 30 : 40));
+ }
+ else if (idx < 16) {
+ args[argi++] = (idx - 8 + (fg ? 90 : 100));
+ }
+ else {
+ args[argi++] = CSI_ARG_FLAG_MORE | (fg ? 38 : 48);
+ args[argi++] = CSI_ARG_FLAG_MORE | 5;
+ args[argi++] = idx;
+ }
+ }
+ else if (VTERM_COLOR_IS_RGB(col)) {
+ args[argi++] = CSI_ARG_FLAG_MORE | (fg ? 38 : 48);
+ args[argi++] = CSI_ARG_FLAG_MORE | 2;
+ args[argi++] = CSI_ARG_FLAG_MORE | col->red;
+ args[argi++] = CSI_ARG_FLAG_MORE | col->green;
+ args[argi++] = col->blue;
+ }
+ return 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 == VTERM_UNDERLINE_SINGLE)
+ args[argi++] = 4;
+ if(state->pen.underline == VTERM_UNDERLINE_CURLY)
+ args[argi++] = 4 | CSI_ARG_FLAG_MORE, args[argi++] = 3;
+
+ if(state->pen.blink)
+ args[argi++] = 5;
+
+ if(state->pen.reverse)
+ args[argi++] = 7;
+
+ if(state->pen.conceal)
+ args[argi++] = 8;
+
+ if(state->pen.strike)
+ args[argi++] = 9;
+
+ if(state->pen.font)
+ args[argi++] = 10 + state->pen.font;
+
+ if(state->pen.underline == VTERM_UNDERLINE_DOUBLE)
+ args[argi++] = 21;
+
+ argi = vterm_state_getpen_color(&state->pen.fg, argi, args, TRUE);
+
+ argi = vterm_state_getpen_color(&state->pen.bg, argi, args, FALSE);
+
+ if(state->pen.small) {
+ if(state->pen.baseline == VTERM_BASELINE_RAISE)
+ args[argi++] = 73;
+ else if(state->pen.baseline == VTERM_BASELINE_LOWER)
+ args[argi++] = 74;
+ }
+
+ 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_CONCEAL:
+ val->boolean = state->pen.conceal;
+ 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_ATTR_SMALL:
+ val->boolean = state->pen.small;
+ return 1;
+
+ case VTERM_ATTR_BASELINE:
+ val->number = state->pen.baseline;
+ 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/screen.c b/src/libvterm/src/screen.c
new file mode 100644
index 0000000..fb1d26e
--- /dev/null
+++ b/src/libvterm/src/screen.c
@@ -0,0 +1,1206 @@
+#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
+
+#undef DEBUG_REFLOW
+
+/* 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 conceal : 1;
+ unsigned int strike : 1;
+ unsigned int font : 4; /* 0 to 9 */
+ unsigned int small : 1;
+ unsigned int baseline : 2;
+
+ /* 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;
+
+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;
+
+ unsigned int global_reverse : 1;
+ unsigned int reflow : 1;
+
+ /* 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 void clearcell(const VTermScreen *screen, ScreenCell *cell)
+{
+ cell->chars[0] = 0;
+ cell->pen = screen->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;
+ if (screen->buffer == NULL)
+ return NULL;
+ return screen->buffer + (screen->cols * row) + col;
+}
+
+static ScreenCell *alloc_buffer(VTermScreen *screen, int rows, int cols)
+{
+ ScreenCell *new_buffer = vterm_allocator_malloc(screen->vt, sizeof(ScreenCell) * rows * cols);
+
+ for(int row = 0; row < rows; row++) {
+ for(int col = 0; col < cols; col++) {
+ clearcell(screen, &new_buffer[row * cols + col]);
+ }
+ }
+
+ 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)
+{
+ VTermScreen *screen = user;
+ ScreenCell *cell = getcell(screen, pos.row, pos.col);
+
+ if(!cell)
+ return 0;
+
+ int i;
+ 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(int col = 1; col < info->width; col++)
+ {
+ ScreenCell *onecell = getcell(screen, pos.row, pos.col + col);
+ if (onecell == NULL)
+ break;
+ onecell->chars[0] = (uint32_t)-1;
+ }
+
+ VTermRect rect;
+ 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 void sb_pushline_from_row(VTermScreen *screen, int row)
+{
+ VTermPos pos;
+ pos.row = row;
+ for(pos.col = 0; pos.col < screen->cols; pos.col++)
+ vterm_screen_get_cell(screen, pos, screen->sb_buffer + pos.col);
+
+ (screen->callbacks->sb_pushline)(screen->cols, screen->sb_buffer, screen->cbdata);
+}
+
+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[BUFIDX_PRIMARY]) { // not altscreen
+ for(int row = 0; row < src.start_row; row++)
+ sb_pushline_from_row(screen, row);
+ }
+
+ int cols = src.end_col - src.start_col;
+ int downward = src.start_row - dest.start_row;
+
+ int init_row, test_row, inc_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(int 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;
+
+ for(int row = rect.start_row; row < screen->state->rows && row < rect.end_row; row++) {
+ const VTermLineInfo *info = vterm_state_get_lineinfo(screen->state, row);
+
+ for(int col = rect.start_col; col < rect.end_col; col++) {
+ ScreenCell *cell = getcell(screen, row, col);
+
+ if (cell == NULL)
+ {
+ DEBUG_LOG2("libvterm: erase_internal() position invalid: %d / %d",
+ row, col);
+ return 1;
+ }
+ 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_CONCEAL:
+ screen->pen.conceal = 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_ATTR_SMALL:
+ screen->pen.small = val->boolean;
+ return 1;
+ case VTERM_ATTR_BASELINE:
+ screen->pen.baseline = val->number;
+ 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[BUFIDX_ALTSCREEN])
+ return 0;
+
+ screen->buffer = val->boolean ? screen->buffers[BUFIDX_ALTSCREEN] : screen->buffers[BUFIDX_PRIMARY];
+ /* 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;
+}
+
+/* How many cells are non-blank
+ * Returns the position of the first blank cell in the trailing blank end */
+static int line_popcount(ScreenCell *buffer, int row, int rows UNUSED, int cols)
+{
+ int col = cols - 1;
+ while(col >= 0 && buffer[row * cols + col].chars[0] == 0)
+ col--;
+ return col + 1;
+}
+
+#define REFLOW (screen->reflow)
+
+static void resize_buffer(VTermScreen *screen, int bufidx, int new_rows, int new_cols, int active, VTermStateFields *statefields)
+{
+ int old_rows = screen->rows;
+ int old_cols = screen->cols;
+
+ ScreenCell *old_buffer = screen->buffers[bufidx];
+ VTermLineInfo *old_lineinfo = statefields->lineinfos[bufidx];
+
+ ScreenCell *new_buffer = vterm_allocator_malloc(screen->vt, sizeof(ScreenCell) * new_rows * new_cols);
+ VTermLineInfo *new_lineinfo = vterm_allocator_malloc(screen->vt, sizeof(new_lineinfo[0]) * new_rows);
+
+ // Find the final row of old buffer content
+ int old_row = old_rows - 1;
+ int new_row = new_rows - 1;
+
+ VTermPos old_cursor = statefields->pos;
+ VTermPos new_cursor = { -1, -1 };
+
+#ifdef DEBUG_REFLOW
+ fprintf(stderr, "Resizing from %dx%d to %dx%d; cursor was at (%d,%d)\n",
+ old_cols, old_rows, new_cols, new_rows, old_cursor.col, old_cursor.row);
+#endif
+
+ /* Keep track of the final row that is knonw to be blank, so we know what
+ * spare space we have for scrolling into
+ */
+ int final_blank_row = new_rows;
+
+ while(old_row >= 0) {
+ int old_row_end = old_row;
+ /* TODO: Stop if dwl or dhl */
+ while(REFLOW && old_lineinfo && old_row >= 0 && old_lineinfo[old_row].continuation)
+ old_row--;
+ int old_row_start = old_row;
+
+ int width = 0;
+ for(int row = old_row_start; row <= old_row_end; row++) {
+ if(REFLOW && row < (old_rows - 1) && old_lineinfo[row + 1].continuation)
+ width += old_cols;
+ else
+ width += line_popcount(old_buffer, row, old_rows, old_cols);
+ }
+
+ if(final_blank_row == (new_row + 1) && width == 0)
+ final_blank_row = new_row;
+
+ int new_height = REFLOW
+ ? width ? (width + new_cols - 1) / new_cols : 1
+ : 1;
+
+ int new_row_end = new_row;
+ int new_row_start = new_row - new_height + 1;
+
+ old_row = old_row_start;
+ int old_col = 0;
+
+ int spare_rows = new_rows - final_blank_row;
+
+ if(new_row_start < 0 && /* we'd fall off the top */
+ spare_rows >= 0 && /* we actually have spare rows */
+ (!active || new_cursor.row == -1 || (new_cursor.row - new_row_start) < new_rows))
+ {
+ /* Attempt to scroll content down into the blank rows at the bottom to
+ * make it fit
+ */
+ int downwards = -new_row_start;
+ if(downwards > spare_rows)
+ downwards = spare_rows;
+ int rowcount = new_rows - downwards;
+
+#ifdef DEBUG_REFLOW
+ fprintf(stderr, " scroll %d rows +%d downwards\n", rowcount, downwards);
+#endif
+
+ memmove(&new_buffer[downwards * new_cols], &new_buffer[0], rowcount * new_cols * sizeof(ScreenCell));
+ memmove(&new_lineinfo[downwards], &new_lineinfo[0], rowcount * sizeof(new_lineinfo[0]));
+
+ new_row += downwards;
+ new_row_start += downwards;
+ new_row_end += downwards;
+
+ if(new_cursor.row >= 0)
+ new_cursor.row += downwards;
+
+ final_blank_row += downwards;
+ }
+
+#ifdef DEBUG_REFLOW
+ fprintf(stderr, " rows [%d..%d] <- [%d..%d] width=%d\n",
+ new_row_start, new_row_end, old_row_start, old_row_end, width);
+#endif
+
+ if(new_row_start < 0)
+ break;
+
+ for(new_row = new_row_start, old_row = old_row_start; new_row <= new_row_end; new_row++) {
+ int count = width >= new_cols ? new_cols : width;
+ width -= count;
+
+ int new_col = 0;
+
+ while(count) {
+ /* TODO: This could surely be done a lot faster by memcpy()'ing the entire range */
+ new_buffer[new_row * new_cols + new_col] = old_buffer[old_row * old_cols + old_col];
+
+ if(old_cursor.row == old_row && old_cursor.col == old_col)
+ new_cursor.row = new_row, new_cursor.col = new_col;
+
+ old_col++;
+ if(old_col == old_cols) {
+ old_row++;
+
+ if(!REFLOW) {
+ new_col++;
+ break;
+ }
+ old_col = 0;
+ }
+
+ new_col++;
+ count--;
+ }
+
+ if(old_cursor.row == old_row && old_cursor.col >= old_col) {
+ new_cursor.row = new_row, new_cursor.col = (old_cursor.col - old_col + new_col);
+ if(new_cursor.col >= new_cols)
+ new_cursor.col = new_cols-1;
+ }
+
+ while(new_col < new_cols) {
+ clearcell(screen, &new_buffer[new_row * new_cols + new_col]);
+ new_col++;
+ }
+
+ new_lineinfo[new_row].continuation = (new_row > new_row_start);
+ }
+
+ old_row = old_row_start - 1;
+ new_row = new_row_start - 1;
+ }
+
+ if(old_cursor.row <= old_row) {
+ /* cursor would have moved entirely off the top of the screen; lets just
+ * bring it within range */
+ new_cursor.row = 0, new_cursor.col = old_cursor.col;
+ if(new_cursor.col >= new_cols)
+ new_cursor.col = new_cols-1;
+ }
+
+ /* We really expect the cursor position to be set by now */
+ /* Unfortunately we do get here when "new_rows" is one. We don't want
+ * to crash, so until the above code is fixed let's just set the cursor. */
+ if(active && (new_cursor.row == -1 || new_cursor.col == -1)) {
+ /* fprintf(stderr, "screen_resize failed to update cursor position\n");
+ * abort(); */
+ if (new_cursor.row < 0)
+ new_cursor.row = 0;
+ if (new_cursor.col < 0)
+ new_cursor.col = 0;
+ }
+
+ if(old_row >= 0 && bufidx == BUFIDX_PRIMARY) {
+ /* Push spare lines to scrollback buffer */
+ for(int row = 0; row <= old_row; row++)
+ sb_pushline_from_row(screen, row);
+ if(active)
+ statefields->pos.row -= (old_row + 1);
+ }
+ if(new_row >= 0 && bufidx == BUFIDX_PRIMARY &&
+ screen->callbacks && screen->callbacks->sb_popline) {
+ /* Try to backfill rows by popping scrollback buffer */
+ while(new_row >= 0) {
+ VTermPos pos;
+ if(!(screen->callbacks->sb_popline(old_cols, screen->sb_buffer, screen->cbdata)))
+ break;
+
+ pos.row = new_row;
+ for(pos.col = 0; pos.col < old_cols && pos.col < new_cols; pos.col += screen->sb_buffer[pos.col].width) {
+ VTermScreenCell *src = &screen->sb_buffer[pos.col];
+ ScreenCell *dst = &new_buffer[pos.row * new_cols + pos.col];
+
+ for(int i = 0; i < VTERM_MAX_CHARS_PER_CELL; i++) {
+ dst->chars[i] = src->chars[i];
+ if(!src->chars[i])
+ break;
+ }
+
+ dst->pen.bold = src->attrs.bold;
+ dst->pen.underline = src->attrs.underline;
+ dst->pen.italic = src->attrs.italic;
+ dst->pen.blink = src->attrs.blink;
+ dst->pen.reverse = src->attrs.reverse ^ screen->global_reverse;
+ dst->pen.conceal = src->attrs.conceal;
+ dst->pen.strike = src->attrs.strike;
+ dst->pen.font = src->attrs.font;
+ dst->pen.small = src->attrs.small;
+ dst->pen.baseline = src->attrs.baseline;
+
+ dst->pen.fg = src->fg;
+ dst->pen.bg = src->bg;
+
+ if(src->width == 2 && pos.col < (new_cols-1))
+ (dst + 1)->chars[0] = (uint32_t) -1;
+ }
+ for( ; pos.col < new_cols; pos.col++)
+ clearcell(screen, &new_buffer[pos.row * new_cols + pos.col]);
+ new_row--;
+
+ if(active)
+ statefields->pos.row++;
+ }
+ }
+ if(new_row >= 0) {
+ /* Scroll new rows back up to the top and fill in blanks at the bottom */
+ int moverows = new_rows - new_row - 1;
+ memmove(&new_buffer[0], &new_buffer[(new_row + 1) * new_cols], moverows * new_cols * sizeof(ScreenCell));
+ memmove(&new_lineinfo[0], &new_lineinfo[new_row + 1], moverows * sizeof(new_lineinfo[0]));
+
+ new_cursor.row -= (new_row + 1);
+
+ for(new_row = moverows; new_row < new_rows; new_row++) {
+ for(int col = 0; col < new_cols; col++)
+ clearcell(screen, &new_buffer[new_row * new_cols + col]);
+ new_lineinfo[new_row] = (VTermLineInfo){ 0 };
+ }
+ }
+
+ vterm_allocator_free(screen->vt, old_buffer);
+ screen->buffers[bufidx] = new_buffer;
+
+ vterm_allocator_free(screen->vt, old_lineinfo);
+ statefields->lineinfos[bufidx] = new_lineinfo;
+
+ if(active)
+ statefields->pos = new_cursor;
+
+ return;
+}
+
+static int resize(int new_rows, int new_cols, VTermStateFields *fields, void *user)
+{
+ VTermScreen *screen = user;
+
+ int altscreen_active = (screen->buffers[BUFIDX_ALTSCREEN] && screen->buffer == screen->buffers[BUFIDX_ALTSCREEN]);
+
+ int old_rows = screen->rows;
+ int old_cols = screen->cols;
+
+ if(new_cols > old_cols) {
+ /* Ensure that ->sb_buffer is large enough for a new or and old row */
+ if(screen->sb_buffer)
+ vterm_allocator_free(screen->vt, screen->sb_buffer);
+
+ screen->sb_buffer = vterm_allocator_malloc(screen->vt, sizeof(VTermScreenCell) * new_cols);
+ }
+
+ resize_buffer(screen, 0, new_rows, new_cols, !altscreen_active, fields);
+ if(screen->buffers[BUFIDX_ALTSCREEN])
+ resize_buffer(screen, 1, new_rows, new_cols, altscreen_active, fields);
+ else if(new_rows != old_rows) {
+ /* We don't need a full resize of the altscreen because it isn't enabled
+ * but we should at least keep the lineinfo the right size */
+ vterm_allocator_free(screen->vt, fields->lineinfos[BUFIDX_ALTSCREEN]);
+
+ VTermLineInfo *new_lineinfo = vterm_allocator_malloc(screen->vt, sizeof(new_lineinfo[0]) * new_rows);
+ for(int row = 0; row < new_rows; row++)
+ new_lineinfo[row] = (VTermLineInfo){ 0 };
+
+ fields->lineinfos[BUFIDX_ALTSCREEN] = new_lineinfo;
+ }
+
+ screen->buffer = altscreen_active ? screen->buffers[BUFIDX_ALTSCREEN] : screen->buffers[BUFIDX_PRIMARY];
+
+ screen->rows = new_rows;
+ screen->cols = new_cols;
+
+ if(new_cols <= old_cols) {
+ if(screen->sb_buffer)
+ vterm_allocator_free(screen->vt, screen->sb_buffer);
+
+ screen->sb_buffer = vterm_allocator_malloc(screen->vt, sizeof(VTermScreenCell) * new_cols);
+ }
+
+ /* TODO: Maaaaybe we can optimise this if there's no reflow happening */
+ damagescreen(screen);
+
+ 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;
+
+ if(newinfo->doublewidth != oldinfo->doublewidth ||
+ newinfo->doubleheight != oldinfo->doubleheight) {
+ for(int col = 0; col < screen->cols; col++) {
+ ScreenCell *cell = getcell(screen, row, col);
+ if (cell == NULL)
+ {
+ DEBUG_LOG2("libvterm: setlineinfo() position invalid: %d / %d",
+ row, col);
+ return 1;
+ }
+ cell->pen.dwl = newinfo->doublewidth;
+ cell->pen.dhl = newinfo->doubleheight;
+ }
+
+ VTermRect rect;
+ 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 int sb_clear(void *user) {
+ VTermScreen *screen = user;
+
+ if(screen->callbacks && screen->callbacks->sb_clear)
+ if((*screen->callbacks->sb_clear)(screen->cbdata))
+ return 1;
+
+ return 0;
+}
+
+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
+ &sb_clear, //sb_clear
+};
+
+/*
+ * 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);
+ if(!state)
+ return NULL;
+
+ VTermScreen *screen = vterm_allocator_malloc(vt, sizeof(VTermScreen));
+ if (screen == NULL)
+ return NULL;
+ int rows, cols;
+
+ 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->global_reverse = FALSE;
+ screen->reflow = FALSE;
+
+ screen->callbacks = NULL;
+ screen->cbdata = NULL;
+
+ screen->buffers[BUFIDX_PRIMARY] = alloc_buffer(screen, rows, cols);
+
+ screen->buffer = screen->buffers[BUFIDX_PRIMARY];
+
+ 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[BUFIDX_PRIMARY]);
+ if(screen->buffers[BUFIDX_ALTSCREEN])
+ vterm_allocator_free(screen->vt, screen->buffers[BUFIDX_ALTSCREEN]);
+
+ 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;
+
+#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(int row = rect.start_row; row < rect.end_row; row++) {
+ for(int col = rect.start_col; col < rect.end_col; col++) {
+ ScreenCell *cell = getcell(screen, row, col);
+
+ if (cell == NULL)
+ {
+ DEBUG_LOG2("libvterm: _get_chars() position invalid: %d / %d",
+ row, col);
+ return 1;
+ }
+ 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(int 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);
+
+ if(!intcell)
+ return 0;
+
+ for(int i = 0; i < VTERM_MAX_CHARS_PER_CELL; 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.conceal = intcell->pen.conceal;
+ cell->attrs.strike = intcell->pen.strike;
+ cell->attrs.font = intcell->pen.font;
+ cell->attrs.small = intcell->pen.small;
+ cell->attrs.baseline = intcell->pen.baseline;
+
+ cell->attrs.dwl = intcell->pen.dwl;
+ cell->attrs.dhl = intcell->pen.dhl;
+
+ cell->fg = intcell->pen.fg;
+ cell->bg = intcell->pen.bg;
+
+ if(vterm_get_special_pty_type() == 2) {
+ // Get correct cell width from cell information contained in line buffer
+ if(pos.col < (screen->cols - 1) &&
+ getcell(screen, pos.row, pos.col + 1)->chars[0] == (uint32_t)-1) {
+ if(getcell(screen, pos.row, pos.col)->chars[0] == 0x20) {
+ getcell(screen, pos.row, pos.col)->chars[0] = 0;
+ cell->width = 2;
+ } else if(getcell(screen, pos.row, pos.col)->chars[0] == 0) {
+ getcell(screen, pos.row, pos.col + 1)->chars[0] = 0;
+ cell->width = 1;
+ } else {
+ cell->width = 2;
+ }
+ } else
+ cell->width = 1;
+ } else {
+ 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;
+}
+
+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)
+ return vt->screen;
+
+ vt->screen = screen_new(vt);
+ return vt->screen;
+}
+
+void vterm_screen_enable_reflow(VTermScreen *screen, int reflow)
+{
+ screen->reflow = reflow;
+}
+
+// Removed, causes a compiler warning and isn't used
+// #undef vterm_screen_set_reflow
+// void vterm_screen_set_reflow(VTermScreen *screen, int reflow)
+// {
+// vterm_screen_enable_reflow(screen, reflow);
+// }
+
+void vterm_screen_enable_altscreen(VTermScreen *screen, int altscreen)
+{
+ if(!screen->buffers[BUFIDX_ALTSCREEN] && altscreen) {
+ int rows, cols;
+ vterm_get_size(screen->vt, &rows, &cols);
+
+ screen->buffers[BUFIDX_ALTSCREEN] = alloc_buffer(screen, 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 VTermStateFallbacks *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_CONCEAL_MASK) && (a->pen.conceal != b->pen.conceal))
+ 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_is_equal(&a->pen.fg, &b->pen.fg))
+ return 1;
+ if((attrs & VTERM_ATTR_BACKGROUND_MASK) && !vterm_color_is_equal(&a->pen.bg, &b->pen.bg))
+ return 1;
+ if((attrs & VTERM_ATTR_SMALL_MASK) && (a->pen.small != b->pen.small))
+ return 1;
+ if((attrs & VTERM_ATTR_BASELINE_MASK) && (a->pen.baseline != b->pen.baseline))
+ return 1;
+
+ return 0;
+}
+
+int vterm_screen_get_attrs_extent(const VTermScreen *screen, VTermRect *extent, VTermPos pos, VTermAttrMask attrs)
+{
+ 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;
+
+ int col;
+
+ 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;
+}
+
+void vterm_screen_convert_color_to_rgb(const VTermScreen *screen, VTermColor *col)
+{
+ vterm_state_convert_color_to_rgb(screen->state, col);
+}
diff --git a/src/libvterm/src/state.c b/src/libvterm/src/state.c
new file mode 100644
index 0000000..ee44824
--- /dev/null
+++ b/src/libvterm/src/state.c
@@ -0,0 +1,2473 @@
+#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(rect.end_col == state->cols) {
+ int row;
+ /* If we're erasing the final cells of any lines, cancel the continuation
+ * marker on the subsequent line
+ */
+ for(row = rect.start_row + 1; row < rect.end_row + 1 && row < state->rows; row++)
+ state->lineinfo[row].continuation = 0;
+ }
+
+ 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;
+
+ state->selection.callbacks = NULL;
+ state->selection.user = NULL;
+ state->selection.buffer = NULL;
+
+ vterm_state_newpen(state);
+
+ state->bold_is_highbright = 0;
+
+ 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->lineinfos[BUFIDX_PRIMARY] = vterm_allocator_malloc(state->vt, state->rows * sizeof(VTermLineInfo));
+ /* TODO: Make an 'enable' function */
+ state->lineinfos[BUFIDX_ALTSCREEN] = vterm_allocator_malloc(state->vt, state->rows * sizeof(VTermLineInfo));
+ state->lineinfo = state->lineinfos[BUFIDX_PRIMARY];
+
+ state->encoding_utf8.enc = vterm_lookup_encoding(ENC_UTF8, 'u');
+ if(state->encoding_utf8.enc->init)
+ (*state->encoding_utf8.enc->init)(state->encoding_utf8.enc, state->encoding_utf8.data);
+
+ return state;
+}
+
+INTERNAL void vterm_state_free(VTermState *state)
+{
+ vterm_allocator_free(state->vt, state->tabstops);
+ vterm_allocator_free(state->vt, state->lineinfos[BUFIDX_PRIMARY]);
+ if(state->lineinfos[BUFIDX_ALTSCREEN])
+ vterm_allocator_free(state->vt, state->lineinfos[BUFIDX_ALTSCREEN]);
+ 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);
+ int row;
+ VTermLineInfo zeroLineInfo = {0x0};
+
+ if(downward > 0) {
+ memmove(state->lineinfo + rect.start_row,
+ state->lineinfo + rect.start_row + downward,
+ height * sizeof(state->lineinfo[0]));
+ for(row = rect.end_row - downward; row < rect.end_row; row++)
+ state->lineinfo[row] = zeroLineInfo;
+ }
+ else {
+ memmove(state->lineinfo + rect.start_row - downward,
+ state->lineinfo + rect.start_row,
+ height * sizeof(state->lineinfo[0]));
+ for(row = rect.start_row; row < rect.start_row - downward; row++)
+ state->lineinfo[row] = zeroLineInfo;
+ }
+ }
+
+ 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;
+ 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.
+ uint32_t *codepoints = (uint32_t *)(state->vt->tmpbuffer);
+ size_t maxpoints = (state->vt->tmpbuffer_len) / 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)maxpoints,
+ 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)
+ {
+ 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;
+
+ for(glyph_ends = i + 1;
+ (glyph_ends < npoints) && (glyph_ends < glyph_starts + VTERM_MAX_CHARS_PER_CELL);
+ glyph_ends++)
+ if(!vterm_unicode_is_combining(codepoints[glyph_ends]))
+ break;
+
+ uint32_t *chars = vterm_allocator_malloc(state->vt, (VTERM_MAX_CHARS_PER_CELL + 1) * sizeof(uint32_t));
+ if (chars == NULL)
+ break;
+
+ for( ; i < glyph_ends; i++) {
+ int this_width;
+ if(vterm_get_special_pty_type() == 2) {
+ state->vt->in_backspace -= (state->vt->in_backspace > 0) ? 1 : 0;
+ if(state->vt->in_backspace == 1)
+ codepoints[i] = 0; // codepoints under this condition must be 0
+ }
+ 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
+ if (i == glyph_starts || this_width > width)
+ width = this_width; // TODO: should be += ?
+ }
+
+ while(i < npoints && vterm_unicode_is_combining(codepoints[i]))
+ i++;
+
+ 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;
+ state->lineinfo[state->pos.row].continuation = 1;
+ }
+
+ 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
+
+ return (int)eaten;
+}
+
+static int on_control(unsigned char control, void *user)
+{
+ VTermState *state = user;
+
+ VTermPos oldpos = state->pos;
+
+ VTermScreenCell cell;
+
+ // Preparing to see the leading byte
+ VTermPos leadpos = state->pos;
+ leadpos.col -= (leadpos.col >= 2 ? 2 : 0);
+
+ 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--;
+ if(vterm_get_special_pty_type() == 2) {
+ // In 2 cell letters, go back 2 cells
+ vterm_screen_get_cell(state->vt->screen, leadpos, &cell);
+ if(vterm_unicode_width(cell.chars[0]) == 2)
+ 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, VTermStringFragment frag)
+{
+ VTermValue val;
+
+ val.string = frag;
+ return vterm_state_set_termprop(state, prop, &val);
+}
+
+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 void request_version_string(VTermState *state)
+{
+ vterm_push_output_sprintf_str(state->vt, C1_DCS, TRUE, ">|libvterm(%d.%d)",
+ VTERM_VERSION_MAJOR, VTERM_VERSION_MINOR);
+}
+
+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;
+ int cancel_phantom = 1;
+ VTermPos oldpos = state->pos;
+ int handled = 1;
+
+ // 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
+ if(vterm_get_special_pty_type() == 2) {
+ // Fix a sequence that is not correct right now
+ if(state->pos.row == row - 1) {
+ int cnt, ptr = 0;
+ for(cnt = 0; cnt < col - 1; ++cnt) {
+ VTermPos p;
+ VTermScreenCell c0, c1;
+ p.row = row - 1;
+ p.col = ptr;
+ vterm_screen_get_cell(state->vt->screen, p, &c0);
+ p.col++;
+ vterm_screen_get_cell(state->vt->screen, p, &c1);
+ ptr += (c1.chars[0] == (uint32_t)-1) // double cell?
+ ? (vterm_unicode_is_ambiguous(c0.chars[0])) // is ambiguous?
+ ? vterm_unicode_width(0x00a1) : 1 // &ambiwidth
+ : 1; // not ambiguous
+ }
+ col = ptr + 1;
+ }
+ }
+ 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;
+
+ case 3:
+ if(state->callbacks && state->callbacks->sb_clear)
+ if((*state->callbacks->sb_clear)(state->cbdata))
+ return 1;
+ 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 0x62: { // REP - ECMA-48 8.3.103
+ const int row_width = THISROWWIDTH(state);
+ count = CSI_ARG_COUNT(args[0]);
+ col = state->pos.col + count;
+ UBOUND(col, row_width);
+ while (state->pos.col < col) {
+ putglyph(state, state->combine_chars, state->combine_width, state->pos);
+ state->pos.col += state->combine_width;
+ }
+ if (state->pos.col + state->combine_width >= row_width) {
+ if (state->mode.autowrap) {
+ state->at_phantom = 1;
+ cancel_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 LEADER('?', 0x6d): // DECSGR and XTQMODKEYS
+ // CSI ? 4 m XTQMODKEYS: request modifyOtherKeys level
+ if (argcount == 1 && CSI_ARG(args[0]) == 4)
+ {
+ vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, ">4;%dm",
+ state->mode.modify_other_keys ? 2 : 0);
+ break;
+ }
+
+ /* No actual DEC terminal recognised these, but some printers did. These
+ * are alternative ways to request subscript/superscript/off
+ */
+ for(int argi = 0; argi < argcount; argi++) {
+ long arg;
+ switch(arg = CSI_ARG(args[argi])) {
+ case 4: // Superscript on
+ arg = 73;
+ vterm_state_setpen(state, &arg, 1);
+ break;
+ case 5: // Subscript on
+ arg = 74;
+ vterm_state_setpen(state, &arg, 1);
+ break;
+ case 24: // Super+subscript off
+ arg = 75;
+ vterm_state_setpen(state, &arg, 1);
+ break;
+ }
+ }
+ break;
+
+ case LEADER('>', 0x6d): // CSI > 4 ; Pv m xterm resource modifyOtherKeys
+ if (argcount == 2 && CSI_ARG(args[0]) == 4)
+ {
+ // can't have both modify_other_keys and kitty_keyboard
+ state->mode.kitty_keyboard = 0;
+
+ state->mode.modify_other_keys = CSI_ARG(args[1]) == 2;
+ }
+ break;
+
+ case LEADER('>', 0x75): // CSI > 1 u enable kitty keyboard protocol
+ if (argcount == 1 && CSI_ARG(args[0]) == 1)
+ {
+ // can't have both modify_other_keys and kitty_keyboard
+ state->mode.modify_other_keys = 0;
+
+ state->mode.kitty_keyboard = 1;
+ }
+ break;
+
+ case LEADER('<', 0x75): // CSI < u disable kitty keyboard protocol
+ if (argcount <= 1)
+ state->mode.kitty_keyboard = 0;
+ break;
+
+ case LEADER('?', 0x75): // CSI ? u request kitty keyboard protocol state
+ if (argcount <= 1)
+ // TODO: this only uses the values zero and one. The protocol specifies
+ // more values, the progressive enhancement flags.
+ vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?%du",
+ state->mode.kitty_keyboard);
+ 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 LEADER('>', 0x71): // XTVERSION - xterm query version string
+ request_version_string(state);
+ 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;
+ }
+
+ // Setting the scrolling region restores the cursor to the home position
+ state->pos.row = 0;
+ state->pos.col = 0;
+ if(state->mode.origin) {
+ state->pos.row += state->scrollregion_top;
+ state->pos.col += SCROLLREGION_LEFT(state);
+ }
+
+ 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;
+ }
+
+ // Setting the scrolling region restores the cursor to the home position
+ state->pos.row = 0;
+ state->pos.col = 0;
+ if(state->mode.origin) {
+ state->pos.row += state->scrollregion_top;
+ state->pos.col += SCROLLREGION_LEFT(state);
+ }
+
+ 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;
+ default:
+ handled = 0;
+ break;
+ }
+ 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:
+ handled = 0;
+ break;
+ }
+
+ if (!handled) {
+ 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, cancel_phantom);
+
+#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 char base64_one(uint8_t b)
+{
+ if(b < 26)
+ return 'A' + b;
+ else if(b < 52)
+ return 'a' + b - 26;
+ else if(b < 62)
+ return '0' + b - 52;
+ else if(b == 62)
+ return '+';
+ else if(b == 63)
+ return '/';
+ return 0;
+}
+
+static uint8_t unbase64one(char c)
+{
+ if(c >= 'A' && c <= 'Z')
+ return c - 'A';
+ else if(c >= 'a' && c <= 'z')
+ return c - 'a' + 26;
+ else if(c >= '0' && c <= '9')
+ return c - '0' + 52;
+ else if(c == '+')
+ return 62;
+ else if(c == '/')
+ return 63;
+
+ return 0xFF;
+}
+
+static void osc_selection(VTermState *state, VTermStringFragment frag)
+{
+ if(frag.initial) {
+ state->tmp.selection.mask = 0;
+ state->tmp.selection.state = SELECTION_INITIAL;
+ }
+
+ while(!state->tmp.selection.state && frag.len) {
+ /* Parse selection parameter */
+ switch(frag.str[0]) {
+ case 'c':
+ state->tmp.selection.mask |= VTERM_SELECTION_CLIPBOARD;
+ break;
+ case 'p':
+ state->tmp.selection.mask |= VTERM_SELECTION_PRIMARY;
+ break;
+ case 'q':
+ state->tmp.selection.mask |= VTERM_SELECTION_SECONDARY;
+ break;
+ case 's':
+ state->tmp.selection.mask |= VTERM_SELECTION_SELECT;
+ break;
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ state->tmp.selection.mask |= (VTERM_SELECTION_CUT0 << (frag.str[0] - '0'));
+ break;
+
+ case ';':
+ state->tmp.selection.state = SELECTION_SELECTED;
+ if(!state->tmp.selection.mask)
+ state->tmp.selection.mask = VTERM_SELECTION_SELECT|VTERM_SELECTION_CUT0;
+ break;
+ }
+
+ frag.str++;
+ frag.len--;
+ }
+
+ if(!frag.len)
+ return;
+
+ if(state->tmp.selection.state == SELECTION_SELECTED) {
+ if(frag.str[0] == '?') {
+ state->tmp.selection.state = SELECTION_QUERY;
+ }
+ else {
+ state->tmp.selection.state = SELECTION_SET_INITIAL;
+ state->tmp.selection.recvpartial = 0;
+ }
+ }
+
+ if(state->tmp.selection.state == SELECTION_QUERY) {
+ if(state->selection.callbacks->query)
+ (*state->selection.callbacks->query)(state->tmp.selection.mask, state->selection.user);
+ return;
+ }
+
+ if(state->selection.callbacks->set) {
+ size_t bufcur = 0;
+ char *buffer = state->selection.buffer;
+
+ uint32_t x = 0; /* Current decoding value */
+ int n = 0; /* Number of sextets consumed */
+
+ if(state->tmp.selection.recvpartial) {
+ n = state->tmp.selection.recvpartial >> 24;
+ x = state->tmp.selection.recvpartial & 0x03FFFF; /* could be up to 18 bits of state in here */
+
+ state->tmp.selection.recvpartial = 0;
+ }
+
+ while((state->selection.buflen - bufcur) >= 3 && frag.len) {
+ if(frag.str[0] == '=') {
+ if(n == 2) {
+ buffer[0] = (x >> 4) & 0xFF;
+ buffer += 1, bufcur += 1;
+ }
+ if(n == 3) {
+ buffer[0] = (x >> 10) & 0xFF;
+ buffer[1] = (x >> 2) & 0xFF;
+ buffer += 2, bufcur += 2;
+ }
+
+ while(frag.len && frag.str[0] == '=')
+ frag.str++, frag.len--;
+
+ n = 0;
+ }
+ else {
+ uint8_t b = unbase64one(frag.str[0]);
+ if(b == 0xFF) {
+ DEBUG_LOG1("base64decode bad input %02X\n", (uint8_t)frag.str[0]);
+ }
+ else {
+ x = (x << 6) | b;
+ n++;
+ }
+ frag.str++, frag.len--;
+
+ if(n == 4) {
+ buffer[0] = (x >> 16) & 0xFF;
+ buffer[1] = (x >> 8) & 0xFF;
+ buffer[2] = (x >> 0) & 0xFF;
+
+ buffer += 3, bufcur += 3;
+ x = 0;
+ n = 0;
+ }
+ }
+
+ if(!frag.len || (state->selection.buflen - bufcur) < 3) {
+ if(bufcur) {
+ VTermStringFragment setfrag = {
+ state->selection.buffer, // str
+ bufcur, // len
+ state->tmp.selection.state == SELECTION_SET_INITIAL, // initial
+ frag.final // final
+ };
+ (*state->selection.callbacks->set)(state->tmp.selection.mask,
+ setfrag, state->selection.user);
+ state->tmp.selection.state = SELECTION_SET;
+ }
+
+ buffer = state->selection.buffer;
+ bufcur = 0;
+ }
+ }
+
+ if(n)
+ state->tmp.selection.recvpartial = (n << 24) | x;
+ }
+}
+
+static int on_osc(int command, VTermStringFragment frag, void *user)
+{
+ VTermState *state = user;
+
+ switch(command) {
+ case 0:
+ settermprop_string(state, VTERM_PROP_ICONNAME, frag);
+ settermprop_string(state, VTERM_PROP_TITLE, frag);
+ return 1;
+
+ case 1:
+ settermprop_string(state, VTERM_PROP_ICONNAME, frag);
+ return 1;
+
+ case 2:
+ settermprop_string(state, VTERM_PROP_TITLE, frag);
+ return 1;
+
+ case 10:
+ {
+ // 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;
+ }
+
+ case 11:
+ {
+ // 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;
+ }
+ case 12:
+ settermprop_string(state, VTERM_PROP_CURSORCOLOR, frag);
+ return 1;
+
+ case 52:
+ if(state->selection.callbacks)
+ osc_selection(state, frag);
+
+ return 1;
+
+ default:
+ if(state->fallbacks && state->fallbacks->osc)
+ if((*state->fallbacks->osc)(command, frag, state->fbdata))
+ return 1;
+ }
+
+ return 0;
+}
+
+static void request_status_string(VTermState *state, VTermStringFragment frag)
+{
+ VTerm *vt = state->vt;
+
+ char *tmp = state->tmp.decrqss;
+
+ if(frag.initial)
+ tmp[0] = tmp[1] = tmp[2] = tmp[3] = 0;
+
+ size_t i = 0;
+ while(i < sizeof(state->tmp.decrqss)-1 && tmp[i])
+ i++;
+ while(i < sizeof(state->tmp.decrqss)-1 && frag.len--)
+ tmp[i++] = (frag.str++)[0];
+ tmp[i] = 0;
+
+ if(!frag.final)
+ return;
+
+ switch(tmp[0] | tmp[1]<<8 | tmp[2]<<16) {
+ case 'm': {
+ // Query SGR
+ long args[20];
+ int argc = vterm_state_getpen(state, args, sizeof(args)/sizeof(args[0]));
+ size_t cur = 0;
+
+ cur += SNPRINTF(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur,
+ vt->mode.ctrl8bit ? "\x90" "1$r" : ESC_S "P" "1$r"); // DCS 1$r ...
+ if(cur >= vt->tmpbuffer_len)
+ return;
+
+ for(int argi = 0; argi < argc; argi++) {
+ cur += SNPRINTF(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur,
+ argi == argc - 1 ? "%ld" :
+ CSI_ARG_HAS_MORE(args[argi]) ? "%ld:" :
+ "%ld;",
+ CSI_ARG(args[argi]));
+ if(cur >= vt->tmpbuffer_len)
+ return;
+ }
+
+ cur += SNPRINTF(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur,
+ vt->mode.ctrl8bit ? "m" "\x9C" : "m" ESC_S "\\"); // ... m ST
+ if(cur >= vt->tmpbuffer_len)
+ return;
+
+ vterm_push_output_bytes(vt, vt->tmpbuffer, cur);
+ return;
+ }
+
+ case 'r':
+ // Query DECSTBM
+ vterm_push_output_sprintf_str(vt, C1_DCS, TRUE,
+ "1$r%d;%dr", state->scrollregion_top+1, SCROLLREGION_BOTTOM(state));
+ return;
+
+ case 's':
+ // Query DECSLRM
+ vterm_push_output_sprintf_str(vt, C1_DCS, TRUE,
+ "1$r%d;%ds", SCROLLREGION_LEFT(state)+1, SCROLLREGION_RIGHT(state));
+ return;
+
+ case ' '|('q'<<8): {
+ // Query DECSCUSR
+ 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_str(vt, C1_DCS, TRUE,
+ "1$r%d q", reply);
+ return;
+ }
+
+ case '\"'|('q'<<8):
+ // Query DECSCA
+ vterm_push_output_sprintf_str(vt, C1_DCS, TRUE,
+ "1$r%d\"q", state->protected_cell ? 1 : 2);
+ return;
+ }
+
+ vterm_push_output_sprintf_str(state->vt, C1_DCS, TRUE, "0$r%s", tmp);
+}
+
+static int on_dcs(const char *command, size_t commandlen, VTermStringFragment frag, void *user)
+{
+ VTermState *state = user;
+
+ if(commandlen == 2 && strneq(command, "$q", 2)) {
+ request_status_string(state, frag);
+ return 1;
+ }
+ else if(state->fallbacks && state->fallbacks->dcs)
+ if((*state->fallbacks->dcs)(command, commandlen, frag, state->fbdata))
+ return 1;
+
+ DEBUG_LOG2("libvterm: Unhandled DCS %.*s\n", (int)commandlen, command);
+ return 0;
+}
+
+static int on_apc(VTermStringFragment frag, void *user)
+{
+ VTermState *state = user;
+
+ if(state->fallbacks && state->fallbacks->apc)
+ if((*state->fallbacks->apc)(frag, state->fbdata))
+ return 1;
+
+ /* No DEBUG_LOG because all APCs are unhandled */
+ return 0;
+}
+
+static int on_pm(VTermStringFragment frag, void *user)
+{
+ VTermState *state = user;
+
+ if(state->fallbacks && state->fallbacks->pm)
+ if((*state->fallbacks->pm)(frag, state->fbdata))
+ return 1;
+
+ /* No DEBUG_LOG because all PMs are unhandled */
+ return 0;
+}
+
+static int on_sos(VTermStringFragment frag, void *user)
+{
+ VTermState *state = user;
+
+ if(state->fallbacks && state->fallbacks->sos)
+ if((*state->fallbacks->sos)(frag, state->fbdata))
+ return 1;
+
+ /* No DEBUG_LOG because all SOSs are unhandled */
+ return 0;
+}
+
+static int on_resize(int rows, int cols, void *user)
+{
+ VTermState *state = user;
+ VTermPos oldpos = state->pos;
+
+ if(cols != state->cols) {
+ unsigned char *newtabstops = vterm_allocator_malloc(state->vt, (cols + 7) / 8);
+ if (newtabstops == NULL)
+ return 0;
+
+ /* 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;
+ }
+
+ 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);
+
+ VTermStateFields fields;
+ fields.pos = state->pos;
+ fields.lineinfos[0] = state->lineinfos[0];
+ fields.lineinfos[1] = state->lineinfos[1];
+
+ if(state->callbacks && state->callbacks->resize) {
+ (*state->callbacks->resize)(rows, cols, &fields, state->cbdata);
+ state->pos = fields.pos;
+
+ state->lineinfos[0] = fields.lineinfos[0];
+ state->lineinfos[1] = fields.lineinfos[1];
+ }
+ else {
+ if(rows != state->rows) {
+ for(int bufidx = BUFIDX_PRIMARY; bufidx <= BUFIDX_ALTSCREEN; bufidx++) {
+ VTermLineInfo *oldlineinfo = state->lineinfos[bufidx];
+ if(!oldlineinfo)
+ continue;
+
+ VTermLineInfo *newlineinfo = vterm_allocator_malloc(state->vt, rows * sizeof(VTermLineInfo));
+
+ int row;
+ for(row = 0; row < state->rows && row < rows; row++) {
+ newlineinfo[row] = oldlineinfo[row];
+ }
+
+ for( ; row < rows; row++) {
+ newlineinfo[row] = (VTermLineInfo){
+ .doublewidth = 0,
+ };
+ }
+
+ vterm_allocator_free(state->vt, state->lineinfos[bufidx]);
+ state->lineinfos[bufidx] = newlineinfo;
+ }
+ }
+ }
+
+ state->lineinfo = state->lineinfos[state->mode.alt_screen ? BUFIDX_ALTSCREEN : BUFIDX_PRIMARY];
+
+ if(state->at_phantom && state->pos.col < cols-1) {
+ state->at_phantom = 0;
+ state->pos.col++;
+ }
+
+ if(state->pos.row < 0)
+ state->pos.row = 0;
+ if(state->pos.row >= rows)
+ state->pos.row = rows - 1;
+ if(state->pos.col < 0)
+ state->pos.col = 0;
+ 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_apc, // apc
+ on_pm, // pm
+ on_sos, // sos
+ on_resize // resize
+};
+
+/*
+ * Return the existing state or create a new one.
+ * Returns NULL when out of memory.
+ */
+VTermState *vterm_obtain_state(VTerm *vt)
+{
+ if(vt->state)
+ return vt->state;
+
+ VTermState *state = vterm_state_new(vt);
+ if (state == NULL)
+ return NULL;
+ vt->state = state;
+
+ vterm_parser_set_callbacks(vt, &parser_callbacks, state);
+
+ return state;
+}
+
+void vterm_state_reset(VTermState *state, int hard)
+{
+ 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->mouse_flags = 0;
+
+ state->vt->mode.ctrl8bit = 0;
+
+ for(int col = 0; col < state->cols; col++)
+ if(col % 8 == 0)
+ set_col_tabstop(state, col);
+ else
+ clear_col_tabstop(state, col);
+
+ for(int 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);
+
+ VTermEncoding *default_enc = state->vt->mode.utf8 ?
+ vterm_lookup_encoding(ENC_UTF8, 'u') :
+ vterm_lookup_encoding(ENC_SINGLE_94, 'B');
+
+ for(int 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) {
+ state->pos.row = 0;
+ state->pos.col = 0;
+ state->at_phantom = 0;
+
+ VTermRect rect = { 0, 0, 0, 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 VTermStateFallbacks *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;
+ state->lineinfo = state->lineinfos[state->mode.alt_screen ? BUFIDX_ALTSCREEN : BUFIDX_PRIMARY];
+ 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;
+}
+
+void vterm_state_set_selection_callbacks(VTermState *state, const VTermSelectionCallbacks *callbacks, void *user,
+ char *buffer, size_t buflen)
+{
+ if(buflen && !buffer)
+ buffer = vterm_allocator_malloc(state->vt, buflen);
+
+ state->selection.callbacks = callbacks;
+ state->selection.user = user;
+ state->selection.buffer = buffer;
+ state->selection.buflen = buflen;
+}
+
+void vterm_state_send_selection(VTermState *state, VTermSelectionMask mask, VTermStringFragment frag)
+{
+ VTerm *vt = state->vt;
+
+ if(frag.initial) {
+ /* TODO: support sending more than one mask bit */
+ static char selection_chars[] = "cpqs";
+ int idx;
+ for(idx = 0; idx < 4; idx++)
+ if(mask & (1 << idx))
+ break;
+
+ vterm_push_output_sprintf_str(vt, C1_OSC, FALSE, "52;%c;", selection_chars[idx]);
+
+ state->tmp.selection.sendpartial = 0;
+ }
+
+ if(frag.len) {
+ size_t bufcur = 0;
+ char *buffer = state->selection.buffer;
+
+ uint32_t x = 0;
+ int n = 0;
+
+ if(state->tmp.selection.sendpartial) {
+ n = state->tmp.selection.sendpartial >> 24;
+ x = state->tmp.selection.sendpartial & 0xFFFFFF;
+
+ state->tmp.selection.sendpartial = 0;
+ }
+
+ while((state->selection.buflen - bufcur) >= 4 && frag.len) {
+ x = (x << 8) | frag.str[0];
+ n++;
+ frag.str++, frag.len--;
+
+ if(n == 3) {
+ buffer[0] = base64_one((x >> 18) & 0x3F);
+ buffer[1] = base64_one((x >> 12) & 0x3F);
+ buffer[2] = base64_one((x >> 6) & 0x3F);
+ buffer[3] = base64_one((x >> 0) & 0x3F);
+
+ buffer += 4, bufcur += 4;
+ x = 0;
+ n = 0;
+ }
+
+ if(!frag.len || (state->selection.buflen - bufcur) < 4) {
+ if(bufcur)
+ vterm_push_output_bytes(vt, state->selection.buffer, bufcur);
+
+ buffer = state->selection.buffer;
+ bufcur = 0;
+ }
+ }
+
+ if(n)
+ state->tmp.selection.sendpartial = (n << 24) | x;
+ }
+
+ if(frag.final) {
+ if(state->tmp.selection.sendpartial) {
+ int n = state->tmp.selection.sendpartial >> 24;
+ uint32_t x = state->tmp.selection.sendpartial & 0xFFFFFF;
+ char *buffer = state->selection.buffer;
+
+ /* n is either 1 or 2 now */
+ x <<= (n == 1) ? 16 : 8;
+
+ buffer[0] = base64_one((x >> 18) & 0x3F);
+ buffer[1] = base64_one((x >> 12) & 0x3F);
+ buffer[2] = (n == 1) ? '=' : base64_one((x >> 6) & 0x3F);
+ buffer[3] = '=';
+
+ vterm_push_output_sprintf_str(vt, 0, TRUE, "%.*s", 4, buffer);
+ }
+ else
+ vterm_push_output_sprintf_str(vt, 0, TRUE, "");
+ }
+}
diff --git a/src/libvterm/src/unicode.c b/src/libvterm/src/unicode.c
new file mode 100644
index 0000000..a6a46e1
--- /dev/null
+++ b/src/libvterm/src/unicode.c
@@ -0,0 +1,587 @@
+#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
+ */
+
+struct interval {
+ int first;
+ int last;
+};
+
+#if !defined(WCWIDTH_FUNCTION) || !defined(IS_COMBINING_FUNCTION)
+
+/* sorted list of non-overlapping intervals of non-spacing characters */
+/* generated by "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c" */
+// Replaced by the combining table from Vim.
+static const struct interval combining[] = {
+ {0X0300, 0X036F},
+ {0X0483, 0X0489},
+ {0X0591, 0X05BD},
+ {0X05BF, 0X05BF},
+ {0X05C1, 0X05C2},
+ {0X05C4, 0X05C5},
+ {0X05C7, 0X05C7},
+ {0X0610, 0X061A},
+ {0X064B, 0X065F},
+ {0X0670, 0X0670},
+ {0X06D6, 0X06DC},
+ {0X06DF, 0X06E4},
+ {0X06E7, 0X06E8},
+ {0X06EA, 0X06ED},
+ {0X0711, 0X0711},
+ {0X0730, 0X074A},
+ {0X07A6, 0X07B0},
+ {0X07EB, 0X07F3},
+ {0X07FD, 0X07FD},
+ {0X0816, 0X0819},
+ {0X081B, 0X0823},
+ {0X0825, 0X0827},
+ {0X0829, 0X082D},
+ {0X0859, 0X085B},
+ {0X08D3, 0X08E1},
+ {0X08E3, 0X0903},
+ {0X093A, 0X093C},
+ {0X093E, 0X094F},
+ {0X0951, 0X0957},
+ {0X0962, 0X0963},
+ {0X0981, 0X0983},
+ {0X09BC, 0X09BC},
+ {0X09BE, 0X09C4},
+ {0X09C7, 0X09C8},
+ {0X09CB, 0X09CD},
+ {0X09D7, 0X09D7},
+ {0X09E2, 0X09E3},
+ {0X09FE, 0X09FE},
+ {0X0A01, 0X0A03},
+ {0X0A3C, 0X0A3C},
+ {0X0A3E, 0X0A42},
+ {0X0A47, 0X0A48},
+ {0X0A4B, 0X0A4D},
+ {0X0A51, 0X0A51},
+ {0X0A70, 0X0A71},
+ {0X0A75, 0X0A75},
+ {0X0A81, 0X0A83},
+ {0X0ABC, 0X0ABC},
+ {0X0ABE, 0X0AC5},
+ {0X0AC7, 0X0AC9},
+ {0X0ACB, 0X0ACD},
+ {0X0AE2, 0X0AE3},
+ {0X0AFA, 0X0AFF},
+ {0X0B01, 0X0B03},
+ {0X0B3C, 0X0B3C},
+ {0X0B3E, 0X0B44},
+ {0X0B47, 0X0B48},
+ {0X0B4B, 0X0B4D},
+ {0X0B56, 0X0B57},
+ {0X0B62, 0X0B63},
+ {0X0B82, 0X0B82},
+ {0X0BBE, 0X0BC2},
+ {0X0BC6, 0X0BC8},
+ {0X0BCA, 0X0BCD},
+ {0X0BD7, 0X0BD7},
+ {0X0C00, 0X0C04},
+ {0X0C3E, 0X0C44},
+ {0X0C46, 0X0C48},
+ {0X0C4A, 0X0C4D},
+ {0X0C55, 0X0C56},
+ {0X0C62, 0X0C63},
+ {0X0C81, 0X0C83},
+ {0X0CBC, 0X0CBC},
+ {0X0CBE, 0X0CC4},
+ {0X0CC6, 0X0CC8},
+ {0X0CCA, 0X0CCD},
+ {0X0CD5, 0X0CD6},
+ {0X0CE2, 0X0CE3},
+ {0X0D00, 0X0D03},
+ {0X0D3B, 0X0D3C},
+ {0X0D3E, 0X0D44},
+ {0X0D46, 0X0D48},
+ {0X0D4A, 0X0D4D},
+ {0X0D57, 0X0D57},
+ {0X0D62, 0X0D63},
+ {0X0D82, 0X0D83},
+ {0X0DCA, 0X0DCA},
+ {0X0DCF, 0X0DD4},
+ {0X0DD6, 0X0DD6},
+ {0X0DD8, 0X0DDF},
+ {0X0DF2, 0X0DF3},
+ {0X0E31, 0X0E31},
+ {0X0E34, 0X0E3A},
+ {0X0E47, 0X0E4E},
+ {0X0EB1, 0X0EB1},
+ {0X0EB4, 0X0EBC},
+ {0X0EC8, 0X0ECD},
+ {0X0F18, 0X0F19},
+ {0X0F35, 0X0F35},
+ {0X0F37, 0X0F37},
+ {0X0F39, 0X0F39},
+ {0X0F3E, 0X0F3F},
+ {0X0F71, 0X0F84},
+ {0X0F86, 0X0F87},
+ {0X0F8D, 0X0F97},
+ {0X0F99, 0X0FBC},
+ {0X0FC6, 0X0FC6},
+ {0X102B, 0X103E},
+ {0X1056, 0X1059},
+ {0X105E, 0X1060},
+ {0X1062, 0X1064},
+ {0X1067, 0X106D},
+ {0X1071, 0X1074},
+ {0X1082, 0X108D},
+ {0X108F, 0X108F},
+ {0X109A, 0X109D},
+ {0X135D, 0X135F},
+ {0X1712, 0X1714},
+ {0X1732, 0X1734},
+ {0X1752, 0X1753},
+ {0X1772, 0X1773},
+ {0X17B4, 0X17D3},
+ {0X17DD, 0X17DD},
+ {0X180B, 0X180D},
+ {0X1885, 0X1886},
+ {0X18A9, 0X18A9},
+ {0X1920, 0X192B},
+ {0X1930, 0X193B},
+ {0X1A17, 0X1A1B},
+ {0X1A55, 0X1A5E},
+ {0X1A60, 0X1A7C},
+ {0X1A7F, 0X1A7F},
+ {0X1AB0, 0X1ABE},
+ {0X1B00, 0X1B04},
+ {0X1B34, 0X1B44},
+ {0X1B6B, 0X1B73},
+ {0X1B80, 0X1B82},
+ {0X1BA1, 0X1BAD},
+ {0X1BE6, 0X1BF3},
+ {0X1C24, 0X1C37},
+ {0X1CD0, 0X1CD2},
+ {0X1CD4, 0X1CE8},
+ {0X1CED, 0X1CED},
+ {0X1CF4, 0X1CF4},
+ {0X1CF7, 0X1CF9},
+ {0X1DC0, 0X1DF9},
+ {0X1DFB, 0X1DFF},
+ {0X20D0, 0X20F0},
+ {0X2CEF, 0X2CF1},
+ {0X2D7F, 0X2D7F},
+ {0X2DE0, 0X2DFF},
+ {0X302A, 0X302F},
+ {0X3099, 0X309A},
+ {0XA66F, 0XA672},
+ {0XA674, 0XA67D},
+ {0XA69E, 0XA69F},
+ {0XA6F0, 0XA6F1},
+ {0XA802, 0XA802},
+ {0XA806, 0XA806},
+ {0XA80B, 0XA80B},
+ {0XA823, 0XA827},
+ {0XA880, 0XA881},
+ {0XA8B4, 0XA8C5},
+ {0XA8E0, 0XA8F1},
+ {0XA8FF, 0XA8FF},
+ {0XA926, 0XA92D},
+ {0XA947, 0XA953},
+ {0XA980, 0XA983},
+ {0XA9B3, 0XA9C0},
+ {0XA9E5, 0XA9E5},
+ {0XAA29, 0XAA36},
+ {0XAA43, 0XAA43},
+ {0XAA4C, 0XAA4D},
+ {0XAA7B, 0XAA7D},
+ {0XAAB0, 0XAAB0},
+ {0XAAB2, 0XAAB4},
+ {0XAAB7, 0XAAB8},
+ {0XAABE, 0XAABF},
+ {0XAAC1, 0XAAC1},
+ {0XAAEB, 0XAAEF},
+ {0XAAF5, 0XAAF6},
+ {0XABE3, 0XABEA},
+ {0XABEC, 0XABED},
+ {0XFB1E, 0XFB1E},
+ {0XFE00, 0XFE0F},
+ {0XFE20, 0XFE2F},
+ {0X101FD, 0X101FD},
+ {0X102E0, 0X102E0},
+ {0X10376, 0X1037A},
+ {0X10A01, 0X10A03},
+ {0X10A05, 0X10A06},
+ {0X10A0C, 0X10A0F},
+ {0X10A38, 0X10A3A},
+ {0X10A3F, 0X10A3F},
+ {0X10AE5, 0X10AE6},
+ {0X10D24, 0X10D27},
+ {0X10F46, 0X10F50},
+ {0X11000, 0X11002},
+ {0X11038, 0X11046},
+ {0X1107F, 0X11082},
+ {0X110B0, 0X110BA},
+ {0X11100, 0X11102},
+ {0X11127, 0X11134},
+ {0X11145, 0X11146},
+ {0X11173, 0X11173},
+ {0X11180, 0X11182},
+ {0X111B3, 0X111C0},
+ {0X111C9, 0X111CC},
+ {0X1122C, 0X11237},
+ {0X1123E, 0X1123E},
+ {0X112DF, 0X112EA},
+ {0X11300, 0X11303},
+ {0X1133B, 0X1133C},
+ {0X1133E, 0X11344},
+ {0X11347, 0X11348},
+ {0X1134B, 0X1134D},
+ {0X11357, 0X11357},
+ {0X11362, 0X11363},
+ {0X11366, 0X1136C},
+ {0X11370, 0X11374},
+ {0X11435, 0X11446},
+ {0X1145E, 0X1145E},
+ {0X114B0, 0X114C3},
+ {0X115AF, 0X115B5},
+ {0X115B8, 0X115C0},
+ {0X115DC, 0X115DD},
+ {0X11630, 0X11640},
+ {0X116AB, 0X116B7},
+ {0X1171D, 0X1172B},
+ {0X1182C, 0X1183A},
+ {0X119D1, 0X119D7},
+ {0X119DA, 0X119E0},
+ {0X119E4, 0X119E4},
+ {0X11A01, 0X11A0A},
+ {0X11A33, 0X11A39},
+ {0X11A3B, 0X11A3E},
+ {0X11A47, 0X11A47},
+ {0X11A51, 0X11A5B},
+ {0X11A8A, 0X11A99},
+ {0X11C2F, 0X11C36},
+ {0X11C38, 0X11C3F},
+ {0X11C92, 0X11CA7},
+ {0X11CA9, 0X11CB6},
+ {0X11D31, 0X11D36},
+ {0X11D3A, 0X11D3A},
+ {0X11D3C, 0X11D3D},
+ {0X11D3F, 0X11D45},
+ {0X11D47, 0X11D47},
+ {0X11D8A, 0X11D8E},
+ {0X11D90, 0X11D91},
+ {0X11D93, 0X11D97},
+ {0X11EF3, 0X11EF6},
+ {0X16AF0, 0X16AF4},
+ {0X16B30, 0X16B36},
+ {0X16F4F, 0X16F4F},
+ {0X16F51, 0X16F87},
+ {0X16F8F, 0X16F92},
+ {0X1BC9D, 0X1BC9E},
+ {0X1D165, 0X1D169},
+ {0X1D16D, 0X1D172},
+ {0X1D17B, 0X1D182},
+ {0X1D185, 0X1D18B},
+ {0X1D1AA, 0X1D1AD},
+ {0X1D242, 0X1D244},
+ {0X1DA00, 0X1DA36},
+ {0X1DA3B, 0X1DA6C},
+ {0X1DA75, 0X1DA75},
+ {0X1DA84, 0X1DA84},
+ {0X1DA9B, 0X1DA9F},
+ {0X1DAA1, 0X1DAAF},
+ {0X1E000, 0X1E006},
+ {0X1E008, 0X1E018},
+ {0X1E01B, 0X1E021},
+ {0X1E023, 0X1E024},
+ {0X1E026, 0X1E02A},
+ {0X1E130, 0X1E136},
+ {0X1E2EC, 0X1E2EF},
+ {0X1E8D0, 0X1E8D6},
+ {0X1E944, 0X1E94A},
+ {0XE0100, 0XE01EF}
+};
+#endif
+
+/* 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;
+}
+
+
+/* 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
+
+/* 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 }
+};
+
+#ifdef USE_MK_WCWIDTH_CJK
+
+/*
+ * 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)
+{
+ /* binary search in table of non-spacing characters */
+ if (bisearch(ucs, ambiguous,
+ sizeof(ambiguous) / sizeof(struct interval) - 1))
+ return 2;
+
+ return mk_wcwidth(ucs);
+}
+
+#endif
+
+INTERNAL int vterm_unicode_is_ambiguous(uint32_t codepoint)
+{
+ return (bisearch(codepoint, ambiguous,
+ sizeof(ambiguous) / sizeof(struct interval) - 1)) ? 1 : 0;
+}
+
+#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
+
+#ifdef GET_SPECIAL_PTY_TYPE_FUNCTION
+int GET_SPECIAL_PTY_TYPE_FUNCTION(void);
+#else
+# define GET_SPECIAL_PTY_TYPE_FUNCTION vterm_get_special_pty_type_placeholder
+ static int
+vterm_get_special_pty_type_placeholder(void)
+{
+ return 0;
+}
+#endif
+
+// ################################
+// ### The rest added by Paul Evans
+
+static const struct interval fullwidth[] = {
+#include "fullwidth.inc"
+};
+
+INTERNAL int vterm_unicode_width(uint32_t codepoint)
+{
+ if(bisearch(codepoint, fullwidth, sizeof(fullwidth) / sizeof(fullwidth[0]) - 1))
+ return 2;
+
+ return WCWIDTH_FUNCTION(codepoint);
+}
+
+INTERNAL int vterm_unicode_is_combining(uint32_t codepoint)
+{
+ return IS_COMBINING_FUNCTION(codepoint);
+}
+
+INTERNAL int vterm_get_special_pty_type(void)
+{
+ return GET_SPECIAL_PTY_TYPE_FUNCTION();
+}
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..ed6d21b
--- /dev/null
+++ b/src/libvterm/src/vterm.c
@@ -0,0 +1,451 @@
+#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)
+{
+ struct VTermBuilder builder;
+ memset(&builder, 0, sizeof(builder));
+ builder.rows = rows;
+ builder.cols = cols;
+ return vterm_build(&builder);
+}
+
+VTerm *vterm_new_with_allocator(int rows, int cols, VTermAllocatorFunctions *funcs, void *allocdata)
+{
+ struct VTermBuilder builder;
+ memset(&builder, 0, sizeof(builder));
+ builder.rows = rows;
+ builder.cols = cols;
+ builder.allocator = funcs;
+ builder.allocdata = allocdata;
+ return vterm_build(&builder);
+}
+
+/* A handy macro for defaulting values out of builder fields */
+#define DEFAULT(v, def) ((v) ? (v) : (def))
+
+VTerm *vterm_build(const struct VTermBuilder *builder)
+{
+ const VTermAllocatorFunctions *allocator = DEFAULT(builder->allocator, &default_allocator);
+
+ /* Need to bootstrap using the allocator function directly */
+ VTerm *vt = (*allocator->malloc)(sizeof(VTerm), builder->allocdata);
+
+ vt->allocator = allocator;
+ vt->allocdata = builder->allocdata;
+
+ vt->rows = builder->rows;
+ vt->cols = builder->cols;
+
+ vt->parser.state = NORMAL;
+
+ vt->parser.callbacks = NULL;
+ vt->parser.cbdata = NULL;
+
+ vt->outfunc = NULL;
+ vt->outdata = NULL;
+
+ vt->outbuffer_len = DEFAULT(builder->outbuffer_len, 4096);
+ vt->outbuffer_cur = 0;
+ vt->outbuffer = vterm_allocator_malloc(vt, vt->outbuffer_len);
+
+ vt->tmpbuffer_len = DEFAULT(builder->tmpbuffer_len, 4096);
+ vt->tmpbuffer = vterm_allocator_malloc(vt, vt->tmpbuffer_len);
+
+ if (vt->tmpbuffer == NULL
+ || vt->outbuffer == NULL
+ || vt->tmpbuffer == NULL)
+ {
+ vterm_allocator_free(vt, vt->outbuffer);
+ vterm_allocator_free(vt, vt->tmpbuffer);
+ 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->outbuffer);
+ vterm_allocator_free(vt, vt->tmpbuffer);
+
+ 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)
+{
+ if(rows < 1 || cols < 1)
+ return;
+
+ 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;
+}
+
+void vterm_output_set_callback(VTerm *vt, VTermOutputCallback *func, void *user)
+{
+ vt->outfunc = func;
+ vt->outdata = user;
+}
+
+INTERNAL void vterm_push_output_bytes(VTerm *vt, const char *bytes, size_t len)
+{
+ if(vt->outfunc) {
+ (vt->outfunc)(bytes, len, vt->outdata);
+ return;
+ }
+
+ if(len > vt->outbuffer_len - vt->outbuffer_cur) {
+ DEBUG_LOG("vterm_push_output_bytes(): buffer overflow; dropping output\n");
+ return;
+ }
+
+ memcpy(vt->outbuffer + vt->outbuffer_cur, bytes, len);
+ vt->outbuffer_cur += len;
+}
+
+INTERNAL void vterm_push_output_vsprintf(VTerm *vt, const char *format, va_list args)
+{
+ size_t len;
+#ifndef VSNPRINTF
+ // When vsnprintf() is not available (C90) fall back to vsprintf().
+ char buffer[1024]; // 1Kbyte is enough for everybody, right?
+#endif
+
+#ifdef VSNPRINTF
+ len = VSNPRINTF(vt->tmpbuffer, vt->tmpbuffer_len, format, args);
+ vterm_push_output_bytes(vt, vt->tmpbuffer, len);
+#else
+ len = vsprintf(buffer, format, args);
+ vterm_push_output_bytes(vt, buffer, len);
+#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 cur;
+
+ if(ctrl >= 0x80 && !vt->mode.ctrl8bit)
+ cur = SNPRINTF(vt->tmpbuffer, vt->tmpbuffer_len,
+ ESC_S "%c", ctrl - 0x40);
+ else
+ cur = SNPRINTF(vt->tmpbuffer, vt->tmpbuffer_len,
+ "%c", ctrl);
+ if(cur >= vt->tmpbuffer_len)
+ return;
+ vterm_push_output_bytes(vt, vt->tmpbuffer, cur);
+
+ va_list args;
+ va_start(args, fmt);
+ vterm_push_output_vsprintf(vt, fmt, args);
+ va_end(args);
+}
+
+INTERNAL void vterm_push_output_sprintf_str(VTerm *vt, unsigned char ctrl, int term, const char *fmt, ...)
+{
+ size_t cur;
+
+ if(ctrl) {
+ if(ctrl >= 0x80 && !vt->mode.ctrl8bit)
+ cur = SNPRINTF(vt->tmpbuffer, vt->tmpbuffer_len,
+ ESC_S "%c", ctrl - 0x40);
+ else
+ cur = SNPRINTF(vt->tmpbuffer, vt->tmpbuffer_len,
+ "%c", ctrl);
+
+ if(cur >= vt->tmpbuffer_len)
+ return;
+ vterm_push_output_bytes(vt, vt->tmpbuffer, cur);
+ }
+
+ va_list args;
+ va_start(args, fmt);
+ vterm_push_output_vsprintf(vt, fmt, args);
+ va_end(args);
+
+ if(term) {
+ cur = SNPRINTF(vt->tmpbuffer, vt->tmpbuffer_len,
+ vt->mode.ctrl8bit ? "\x9C" : ESC_S "\\"); // ST
+ if(cur >= vt->tmpbuffer_len)
+ return;
+ vterm_push_output_bytes(vt, vt->tmpbuffer, 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_CONCEAL: 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_ATTR_SMALL: return VTERM_VALUETYPE_BOOL;
+ case VTERM_ATTR_BASELINE: return VTERM_VALUETYPE_INT;
+
+ 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;
+
+ 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;
+ }
+
+ VTermPos pos;
+ 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);
+ }
+}
+
+void vterm_check_version(int major, int minor)
+{
+ if(major != VTERM_VERSION_MAJOR) {
+ fprintf(stderr, "libvterm major version mismatch; %d (wants) != %d (library)\n",
+ major, VTERM_VERSION_MAJOR);
+ exit(1);
+ }
+
+ if(minor > VTERM_VERSION_MINOR) {
+ fprintf(stderr, "libvterm minor version mismatch; %d (wants) > %d (library)\n",
+ minor, VTERM_VERSION_MINOR);
+ exit(1);
+ }
+
+ // Happy
+}
diff --git a/src/libvterm/src/vterm_internal.h b/src/libvterm/src/vterm_internal.h
new file mode 100644
index 0000000..3e95611
--- /dev/null
+++ b/src/libvterm/src/vterm_internal.h
@@ -0,0 +1,329 @@
+#ifndef __VTERM_INTERNAL_H__
+#define __VTERM_INTERNAL_H__
+
+#include "vterm.h"
+
+#include <stdarg.h>
+
+#if defined(__GNUC__) && !defined(__MINGW32__)
+# define INTERNAL __attribute__((visibility("internal")))
+#else
+# define INTERNAL
+#endif
+
+#if defined(__GNUC__) || defined(__MINGW32__)
+# define UNUSED __attribute__((unused))
+#else
+# 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
+
+#define BUFIDX_PRIMARY 0
+#define BUFIDX_ALTSCREEN 1
+
+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 conceal:1;
+ unsigned int strike:1;
+ unsigned int font:4; /* To store 0-9 */
+ unsigned int small:1;
+ unsigned int baseline:2;
+};
+
+struct VTermState
+{
+ VTerm *vt;
+
+ const VTermStateCallbacks *callbacks;
+ void *cbdata;
+
+ const VTermStateFallbacks *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;
+
+ /* Primary and Altscreen; lineinfos[1] is lazily allocated as needed */
+ VTermLineInfo *lineinfos[2];
+
+ /* lineinfo will == lineinfos[0] or lineinfos[1], depending on altscreen */
+ 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;
+ unsigned int modify_other_keys:1;
+ unsigned int kitty_keyboard: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 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;
+
+ /* Temporary state for DECRQSS parsing */
+ union {
+ char decrqss[4];
+ struct {
+ uint16_t mask;
+ enum {
+ SELECTION_INITIAL,
+ SELECTION_SELECTED,
+ SELECTION_QUERY,
+ SELECTION_SET_INITIAL,
+ SELECTION_SET,
+ } state : 8;
+ uint32_t recvpartial;
+ uint32_t sendpartial;
+ } selection;
+ } tmp;
+
+ struct {
+ const VTermSelectionCallbacks *callbacks;
+ void *user;
+ char *buffer;
+ size_t buflen;
+ } selection;
+};
+
+struct VTerm
+{
+ const 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,
+ DCS_COMMAND,
+ /* below here are the "string states" */
+ OSC_COMMAND,
+ OSC,
+ DCS,
+ APC,
+ PM,
+ SOS,
+ } state;
+
+ unsigned int in_esc : 1;
+
+ int intermedlen;
+ char intermed[INTERMED_MAX];
+
+ union {
+ struct {
+ int leaderlen;
+ char leader[CSI_LEADER_MAX];
+
+ int argi;
+ long args[CSI_ARGS_MAX];
+ } csi;
+ struct {
+ int command;
+ } osc;
+ struct {
+ int commandlen;
+ char command[CSI_LEADER_MAX];
+ } dcs;
+ } v;
+
+ const VTermParserCallbacks *callbacks;
+ void *cbdata;
+
+ int string_initial;
+ } parser;
+
+ /* len == malloc()ed size; cur == number of valid bytes */
+
+ VTermOutputCallback *outfunc;
+ void *outdata;
+
+ char *outbuffer;
+ size_t outbuffer_len;
+ size_t outbuffer_cur;
+
+ char *tmpbuffer;
+ size_t tmpbuffer_len;
+
+ VTermState *state;
+ VTermScreen *screen;
+
+ int in_backspace;
+};
+
+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_str(VTerm *vt, unsigned char ctrl, int term, 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);
+int vterm_unicode_is_ambiguous(uint32_t codepoint);
+int vterm_get_special_pty_type(void);
+
+#if (defined(_XOPEN_SOURCE) && _XOPEN_SOURCE >= 500) \
+ || defined(_ISOC99_SOURCE) || defined(_BSD_SOURCE)
+# undef VSNPRINTF
+# define VSNPRINTF vsnprintf
+# undef SNPRINTF
+#else
+# ifdef VSNPRINTF
+// Use a provided vsnprintf() function.
+int VSNPRINTF(char *str, size_t str_m, const char *fmt, va_list ap);
+# endif
+# ifdef SNPRINTF
+// Use a provided snprintf() function.
+int SNPRINTF(char *str, size_t str_m, const char *fmt, ...);
+# endif
+#endif
+#ifndef SNPRINTF
+# define SNPRINTF snprintf
+#endif
+
+
+#endif
diff --git a/src/libvterm/t/02parser.test b/src/libvterm/t/02parser.test
new file mode 100644
index 0000000..2cc51dc
--- /dev/null
+++ b/src/libvterm/t/02parser.test
@@ -0,0 +1,266 @@
+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 "\x99"
+ control 0x99
+
+!C1 7bit
+PUSH "\e\x43"
+ control 0x83
+
+PUSH "\e\x59"
+ control 0x99
+
+!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"]
+
+!OSC in parts
+PUSH "\e]52;abc"
+ osc [52 "abc"
+PUSH "def"
+ osc "def"
+PUSH "ghi\e\\"
+ osc "ghi"]
+
+!OSC BEL without semicolon
+PUSH "\e]1234\x07"
+ osc [1234 ]
+
+!OSC ST without semicolon
+PUSH "\e]1234\e\\"
+ osc [1234 ]
+
+!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"
+ osc [2 ""
+ control 10
+ osc "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"]
+
+!Split write of 7bit ST
+PUSH "\ePABC\e"
+ dcs ["ABC"
+PUSH "\\"
+ dcs ]
+
+!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"
+ dcs ["By"
+ control 10
+ dcs "e"]
+
+!APC BEL
+PUSH "\e_Hello\x07"
+ apc ["Hello"]
+
+!APC ST (7bit)
+PUSH "\e_Hello\e\\"
+ apc ["Hello"]
+
+!APC ST (8bit)
+PUSH "\x{9f}Hello\x9c"
+ apc ["Hello"]
+
+!PM BEL
+PUSH "\e^Hello\x07"
+ pm ["Hello"]
+
+!PM ST (7bit)
+PUSH "\e^Hello\e\\"
+ pm ["Hello"]
+
+!PM ST (8bit)
+PUSH "\x{9e}Hello\x9c"
+ pm ["Hello"]
+
+!SOS BEL
+PUSH "\eXHello\x07"
+ sos ["Hello"]
+
+!SOS ST (7bit)
+PUSH "\eXHello\e\\"
+ sos ["Hello"]
+
+!SOS ST (8bit)
+PUSH "\x{98}Hello\x9c"
+ sos ["Hello"]
+
+!SOS can contain any C0 or C1 code
+PUSH "\eXABC\x01DEF\e\\"
+ sos ["ABC\x01DEF"]
+PUSH "\eXABC\x99DEF\e\\"
+ sos ["ABC\x{99}DEF"]
+
+!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..c82c525
--- /dev/null
+++ b/src/libvterm/t/10state_putglyph.test
@@ -0,0 +1,74 @@
+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 emoji wide char
+# U+1F600 = 0xF0 0x9F 0x98 0x80 name: GRINNING FACE
+RESET
+PUSH "\xF0\x9F\x98\x80 "
+ putglyph 0x1f600 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
+
+!Spare combining chars get truncated
+RESET
+PUSH "e" . "\xCC\x81" x 10
+ putglyph 0x65,0x301,0x301,0x301,0x301,0x301 1 0,0
+ # and nothing more
+
+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..26e447e
--- /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 Horizontal 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..c1f2791
--- /dev/null
+++ b/src/libvterm/t/12state_scroll.test
@@ -0,0 +1,156 @@
+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[9;10r"
+PUSH "\e[10H"
+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
+
+!DECSTBM resets cursor position
+PUSH "\e[5;5H"
+ ?cursor = 4,4
+PUSH "\e[r"
+ ?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..d3f3e9e
--- /dev/null
+++ b/src/libvterm/t/13state_edit.test
@@ -0,0 +1,304 @@
+INIT
+UTF8 1
+WANTSTATE seb
+
+!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
+
+!ED 3
+PUSH "\e[3J"
+ sb_clear
+
+!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..e5ba29b
--- /dev/null
+++ b/src/libvterm/t/17state_mouse.test
@@ -0,0 +1,181 @@
+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"
+
+!Mouse disabled reports nothing
+RESET
+ settermprop 1 true
+ settermprop 2 true
+ settermprop 7 1
+MOUSEMOVE 0,0 0
+MOUSEBTN d 1 0
+MOUSEBTN u 1 0
diff --git a/src/libvterm/t/18state_termprops.test b/src/libvterm/t/18state_termprops.test
new file mode 100644
index 0000000..83c333f
--- /dev/null
+++ b/src/libvterm/t/18state_termprops.test
@@ -0,0 +1,42 @@
+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"]
+
+!Title split write
+PUSH "\e]2;Here is"
+ settermprop 4 ["Here is"
+PUSH " another title\a"
+ settermprop 4 " another 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..4eb4c6a
--- /dev/null
+++ b/src/libvterm/t/25state_input.test
@@ -0,0 +1,155 @@
+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"
+
+!Unmodified F1 is SS3 P
+INKEY 0 F1
+ output "\eOP"
+
+!Modified F1 is CSI P
+INKEY S F1
+ output "\e[1;2P"
+INKEY A F1
+ output "\e[1;3P"
+INKEY C F1
+ output "\e[1;5P"
+
+!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..41e7cf8
--- /dev/null
+++ b/src/libvterm/t/26state_query.test
@@ -0,0 +1,67 @@
+INIT
+WANTSTATE
+
+!DA
+RESET
+PUSH "\e[c"
+ output "\e[?1;2c"
+
+!XTVERSION
+RESET
+PUSH "\e[>q"
+ output "\eP>|libvterm(0.3)\e\\"
+
+!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 25
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..4ab2e18
--- /dev/null
+++ b/src/libvterm/t/29state_fallback.test
@@ -0,0 +1,31 @@
+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"]
+
+!Unrecognised APC
+PUSH "\e_z123\e\\"
+ apc ["z123"]
+
+!Unrecognised PM
+PUSH "\e^z123\e\\"
+ pm ["z123"]
+
+!Unrecognised SOS
+PUSH "\eXz123\e\\"
+ sos ["z123"]
diff --git a/src/libvterm/t/30state_pen.test b/src/libvterm/t/30state_pen.test
new file mode 100644
index 0000000..92cf01d
--- /dev/null
+++ b/src/libvterm/t/30state_pen.test
@@ -0,0 +1,125 @@
+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,is_default_fg)
+ ?pen background = rgb(0,0,0,is_default_bg)
+
+!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[4:0m"
+ ?pen underline = 0
+PUSH "\e[4:1m"
+ ?pen underline = 1
+PUSH "\e[4:2m"
+ ?pen underline = 2
+PUSH "\e[4:3m"
+ ?pen underline = 3
+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 = idx(1)
+PUSH "\e[32m"
+ ?pen foreground = idx(2)
+PUSH "\e[34m"
+ ?pen foreground = idx(4)
+PUSH "\e[91m"
+ ?pen foreground = idx(9)
+PUSH "\e[38:2:10:20:30m"
+ ?pen foreground = rgb(10,20,30)
+PUSH "\e[38:5:1m"
+ ?pen foreground = idx(1)
+PUSH "\e[39m"
+ ?pen foreground = rgb(240,240,240,is_default_fg)
+
+!Background
+PUSH "\e[41m"
+ ?pen background = idx(1)
+PUSH "\e[42m"
+ ?pen background = idx(2)
+PUSH "\e[44m"
+ ?pen background = idx(4)
+PUSH "\e[101m"
+ ?pen background = idx(9)
+PUSH "\e[48:2:10:20:30m"
+ ?pen background = rgb(10,20,30)
+PUSH "\e[48:5:1m"
+ ?pen background = idx(1)
+PUSH "\e[49m"
+ ?pen background = rgb(0,0,0,is_default_bg)
+
+!Bold+ANSI colour == highbright
+PUSH "\e[m\e[1;37m"
+ ?pen bold = on
+ ?pen foreground = idx(15)
+PUSH "\e[m\e[37;1m"
+ ?pen bold = on
+ ?pen foreground = idx(15)
+
+!Super/Subscript
+PUSH "\e[73m"
+ ?pen small = on
+ ?pen baseline = raise
+PUSH "\e[74m"
+ ?pen small = on
+ ?pen baseline = lower
+PUSH "\e[75m"
+ ?pen small = off
+ ?pen baseline = normal
diff --git a/src/libvterm/t/31state_rep.test b/src/libvterm/t/31state_rep.test
new file mode 100644
index 0000000..f820e67
--- /dev/null
+++ b/src/libvterm/t/31state_rep.test
@@ -0,0 +1,128 @@
+INIT
+UTF8 1
+WANTSTATE g
+
+!REP no argument
+RESET
+PUSH "a\e[b"
+ putglyph 0x61 1 0,0
+ putglyph 0x61 1 0,1
+
+!REP zero (zero should be interpreted as one)
+RESET
+PUSH "a\e[0b"
+ putglyph 0x61 1 0,0
+ putglyph 0x61 1 0,1
+
+!REP lowercase a times two
+RESET
+PUSH "a\e[2b"
+ putglyph 0x61 1 0,0
+ putglyph 0x61 1 0,1
+ putglyph 0x61 1 0,2
+
+!REP with UTF-8 1 char
+# U+00E9 = 0xC3 0xA9 name: LATIN SMALL LETTER E WITH ACUTE
+RESET
+PUSH "\xC3\xA9\e[b"
+ putglyph 0xe9 1 0,0
+ putglyph 0xe9 1 0,1
+
+!REP with UTF-8 wide char
+# U+00E9 = 0xC3 0xA9 name: LATIN SMALL LETTER E WITH ACUTE
+RESET
+PUSH "\xEF\xBC\x90\e[b"
+ putglyph 0xff10 2 0,0
+ putglyph 0xff10 2 0,2
+
+!REP with UTF-8 combining character
+RESET
+PUSH "e\xCC\x81\e[b"
+ putglyph 0x65,0x301 1 0,0
+ putglyph 0x65,0x301 1 0,1
+
+!REP till end of line
+RESET
+PUSH "a\e[1000bb"
+ putglyph 0x61 1 0,0
+ putglyph 0x61 1 0,1
+ putglyph 0x61 1 0,2
+ putglyph 0x61 1 0,3
+ putglyph 0x61 1 0,4
+ putglyph 0x61 1 0,5
+ putglyph 0x61 1 0,6
+ putglyph 0x61 1 0,7
+ putglyph 0x61 1 0,8
+ putglyph 0x61 1 0,9
+ putglyph 0x61 1 0,10
+ putglyph 0x61 1 0,11
+ putglyph 0x61 1 0,12
+ putglyph 0x61 1 0,13
+ putglyph 0x61 1 0,14
+ putglyph 0x61 1 0,15
+ putglyph 0x61 1 0,16
+ putglyph 0x61 1 0,17
+ putglyph 0x61 1 0,18
+ putglyph 0x61 1 0,19
+ putglyph 0x61 1 0,20
+ putglyph 0x61 1 0,21
+ putglyph 0x61 1 0,22
+ putglyph 0x61 1 0,23
+ putglyph 0x61 1 0,24
+ putglyph 0x61 1 0,25
+ putglyph 0x61 1 0,26
+ putglyph 0x61 1 0,27
+ putglyph 0x61 1 0,28
+ putglyph 0x61 1 0,29
+ putglyph 0x61 1 0,30
+ putglyph 0x61 1 0,31
+ putglyph 0x61 1 0,32
+ putglyph 0x61 1 0,33
+ putglyph 0x61 1 0,34
+ putglyph 0x61 1 0,35
+ putglyph 0x61 1 0,36
+ putglyph 0x61 1 0,37
+ putglyph 0x61 1 0,38
+ putglyph 0x61 1 0,39
+ putglyph 0x61 1 0,40
+ putglyph 0x61 1 0,41
+ putglyph 0x61 1 0,42
+ putglyph 0x61 1 0,43
+ putglyph 0x61 1 0,44
+ putglyph 0x61 1 0,45
+ putglyph 0x61 1 0,46
+ putglyph 0x61 1 0,47
+ putglyph 0x61 1 0,48
+ putglyph 0x61 1 0,49
+ putglyph 0x61 1 0,50
+ putglyph 0x61 1 0,51
+ putglyph 0x61 1 0,52
+ putglyph 0x61 1 0,53
+ putglyph 0x61 1 0,54
+ putglyph 0x61 1 0,55
+ putglyph 0x61 1 0,56
+ putglyph 0x61 1 0,57
+ putglyph 0x61 1 0,58
+ putglyph 0x61 1 0,59
+ putglyph 0x61 1 0,60
+ putglyph 0x61 1 0,61
+ putglyph 0x61 1 0,62
+ putglyph 0x61 1 0,63
+ putglyph 0x61 1 0,64
+ putglyph 0x61 1 0,65
+ putglyph 0x61 1 0,66
+ putglyph 0x61 1 0,67
+ putglyph 0x61 1 0,68
+ putglyph 0x61 1 0,69
+ putglyph 0x61 1 0,70
+ putglyph 0x61 1 0,71
+ putglyph 0x61 1 0,72
+ putglyph 0x61 1 0,73
+ putglyph 0x61 1 0,74
+ putglyph 0x61 1 0,75
+ putglyph 0x61 1 0,76
+ putglyph 0x61 1 0,77
+ putglyph 0x61 1 0,78
+ putglyph 0x61 1 0,79
+ putglyph 0x62 1 1,0
+
diff --git a/src/libvterm/t/32state_flow.test b/src/libvterm/t/32state_flow.test
new file mode 100644
index 0000000..84a13df
--- /dev/null
+++ b/src/libvterm/t/32state_flow.test
@@ -0,0 +1,28 @@
+INIT
+WANTSTATE
+
+# Many of these test cases inspired by
+# https://blueprints.launchpad.net/libvterm/+spec/reflow-cases
+
+!Spillover text marks continuation on second line
+RESET
+PUSH "A"x100
+PUSH "\r\n"
+ ?lineinfo 0 =
+ ?lineinfo 1 = cont
+
+!CRLF in column 80 does not mark continuation
+RESET
+PUSH "B"x80
+PUSH "\r\n"
+PUSH "B"x20
+PUSH "\r\n"
+ ?lineinfo 0 =
+ ?lineinfo 1 =
+
+!EL cancels continuation of following line
+RESET
+PUSH "D"x100
+ ?lineinfo 1 = cont
+PUSH "\eM\e[79G\e[K"
+ ?lineinfo 1 =
diff --git a/src/libvterm/t/40state_selection.test b/src/libvterm/t/40state_selection.test
new file mode 100644
index 0000000..6ed8972
--- /dev/null
+++ b/src/libvterm/t/40state_selection.test
@@ -0,0 +1,55 @@
+INIT
+UTF8 1
+WANTSTATE
+
+!Set clipboard; final chunk len 4
+PUSH "\e]52;c;SGVsbG8s\e\\"
+ selection-set mask=0001 ["Hello,"]
+
+!Set clipboard; final chunk len 3
+PUSH "\e]52;c;SGVsbG8sIHc=\e\\"
+ selection-set mask=0001 ["Hello, w"]
+
+!Set clipboard; final chunk len 2
+PUSH "\e]52;c;SGVsbG8sIHdvcmxkCg==\e\\"
+ selection-set mask=0001 ["Hello, world\n"]
+
+!Set clipboard; split between chunks
+PUSH "\e]52;c;SGVs"
+ selection-set mask=0001 ["Hel"
+PUSH "bG8s\e\\"
+ selection-set mask=0001 "lo,"]
+
+!Set clipboard; split within chunk
+PUSH "\e]52;c;SGVsbG"
+ selection-set mask=0001 ["Hel"
+PUSH "8s\e\\"
+ selection-set mask=0001 "lo,"]
+
+!Query clipboard
+PUSH "\e]52;c;?\e\\"
+ selection-query mask=0001
+
+!Send clipboard; final chunk len 4
+SELECTION 1 ["Hello,"]
+ output "\e]52;c;SGVsbG8s\e\\"
+
+!Send clipboard; final chunk len 3
+SELECTION 1 ["Hello, w"]
+ output "\e]52;c;SGVsbG8sIHc=\e\\"
+
+!Send clipboard; final chunk len 2
+SELECTION 1 ["Hello, world\n"]
+ output "\e]52;c;SGVsbG8sIHdvcmxkCg==\e\\"
+
+!Send clipboard; split between chunks
+SELECTION 1 ["Hel"
+ output "\e]52;c;SGVs"
+SELECTION 1 "lo,"]
+ output "bG8s\e\\"
+
+!Send clipboard; split within chunk
+SELECTION 1 ["Hello"
+ output "\e]52;c;SGVs"
+SELECTION 1 ","]
+ output "bG8s\e\\"
diff --git a/src/libvterm/t/60screen_ascii.test b/src/libvterm/t/60screen_ascii.test
new file mode 100644
index 0000000..57729c5
--- /dev/null
+++ b/src/libvterm/t/60screen_ascii.test
@@ -0,0 +1,69 @@
+INIT
+WANTSCREEN ac
+
+!Get
+RESET
+PUSH "ABC"
+ movecursor 0,3
+ ?screen_chars 0,0,1,3 = "ABC"
+ ?screen_chars 0,0,1,80 = "ABC"
+ ?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_row 0 = "ABC"
+ ?screen_text 0,0,1,80 = 0x41,0x42,0x43
+PUSH "E"
+ movecursor 0,1
+ ?screen_row 0 = "EBC"
+ ?screen_text 0,0,1,80 = 0x45,0x42,0x43
+
+WANTSCREEN -c
+
+!Erase
+RESET
+PUSH "ABCDE\e[H\e[K"
+ ?screen_row 0 = ""
+ ?screen_text 0,0,1,80 =
+
+!Copycell
+RESET
+PUSH "ABC\e[H\e[@"
+PUSH "1"
+ ?screen_row 0 = "1ABC"
+
+RESET
+PUSH "ABC\e[H\e[P"
+ ?screen_chars 0,0,1,1 = "B"
+ ?screen_chars 0,1,1,2 = "C"
+ ?screen_chars 0,0,1,80 = "BC"
+
+!Space padding
+RESET
+PUSH "Hello\e[CWorld"
+ ?screen_row 0 = "Hello World"
+ ?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 = "Hello\nWorld"
+ ?screen_text 0,0,2,80 = 0x48,0x65,0x6c,0x6c,0x6f,0x0a,0x57,0x6f,0x72,0x6c,0x64
+
+!Altscreen
+RESET
+PUSH "P"
+ ?screen_row 0 = "P"
+PUSH "\e[?1049h"
+ ?screen_row 0 = ""
+PUSH "\e[2K\e[HA"
+ ?screen_row 0 = "A"
+PUSH "\e[?1049l"
+ ?screen_row 0 = "P"
diff --git a/src/libvterm/t/61screen_unicode.test b/src/libvterm/t/61screen_unicode.test
new file mode 100644
index 0000000..8bde2bd
--- /dev/null
+++ b/src/libvterm/t/61screen_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_row 0 = 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_row 0 = 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_row 0 = 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)
+
+!Outputting 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/62screen_damage.test b/src/libvterm/t/62screen_damage.test
new file mode 100644
index 0000000..3b1b238
--- /dev/null
+++ b/src/libvterm/t/62screen_damage.test
@@ -0,0 +1,155 @@
+INIT
+WANTSCREEN aDb
+
+!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_row 23 = "ABE"
diff --git a/src/libvterm/t/63screen_resize.test b/src/libvterm/t/63screen_resize.test
new file mode 100644
index 0000000..6835222
--- /dev/null
+++ b/src/libvterm/t/63screen_resize.test
@@ -0,0 +1,117 @@
+INIT
+WANTSTATE
+WANTSCREEN
+
+!Resize wider preserves cells
+RESET
+RESIZE 25,80
+PUSH "AB\r\nCD"
+ ?screen_chars 0,0,1,80 = "AB"
+ ?screen_chars 1,0,2,80 = "CD"
+RESIZE 25,100
+ ?screen_chars 0,0,1,100 = "AB"
+ ?screen_chars 1,0,2,100 = "CD"
+
+!Resize wider allows print in new area
+RESET
+RESIZE 25,80
+PUSH "AB\e[79GCD"
+ ?screen_chars 0,0,1,2 = "AB"
+ ?screen_chars 0,78,1,80 = "CD"
+RESIZE 25,100
+ ?screen_chars 0,0,1,2 = "AB"
+ ?screen_chars 0,78,1,80 = "CD"
+PUSH "E"
+ ?screen_chars 0,78,1,81 = "CDE"
+
+!Resize shorter with blanks just truncates
+RESET
+RESIZE 25,80
+PUSH "Top\e[10HLine 10"
+ ?screen_row 0 = "Top"
+ ?screen_row 9 = "Line 10"
+ ?cursor = 9,7
+RESIZE 20,80
+ ?screen_row 0 = "Top"
+ ?screen_row 9 = "Line 10"
+ ?cursor = 9,7
+
+!Resize shorter with content must scroll
+RESET
+RESIZE 25,80
+PUSH "Top\e[25HLine 25\e[15H"
+ ?screen_row 0 = "Top"
+ ?screen_row 24 = "Line 25"
+ ?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_row 0 = ""
+ ?screen_row 19 = "Line 25"
+ ?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_row 23 = "Line 25"
+ ?cursor = 24,0
+RESIZE 24,80
+ sb_pushline 80 =
+ ?screen_row 22 = "Line 25"
+ ?cursor = 23,0
+
+!Resize shorter does not send the cursor to a negative row
+# See also https://github.com/vim/vim/pull/6141
+RESET
+WANTSCREEN -b
+RESIZE 25,80
+WANTSCREEN b
+PUSH "\e[24HLine 24\r\nLine 25\e[H"
+ ?cursor = 0,0
+RESIZE 20,80
+ sb_pushline 80 =
+ sb_pushline 80 =
+ sb_pushline 80 =
+ sb_pushline 80 =
+ sb_pushline 80 =
+ ?cursor = 0,0
+
+!Resize taller attempts to pop scrollback
+RESET
+WANTSCREEN -b
+RESIZE 25,80
+PUSH "Line 1\e[25HBottom\e[15H"
+ ?screen_row 0 = "Line 1"
+ ?screen_row 24 = "Bottom"
+ ?cursor = 14,0
+WANTSCREEN b
+RESIZE 30,80
+ sb_popline 80
+ sb_popline 80
+ sb_popline 80
+ sb_popline 80
+ sb_popline 80
+ ?screen_row 0 = "ABCDE"
+ ?screen_row 5 = "Line 1"
+ ?screen_row 29 = "Bottom"
+ ?cursor = 19,0
+WANTSCREEN -b
+
+!Resize can operate on altscreen
+RESET
+WANTSCREEN a
+RESIZE 25,80
+PUSH "Main screen\e[?1049h\e[HAlt screen"
+RESIZE 30,80
+ ?screen_row 0 = "Alt screen"
+PUSH "\e[?1049l"
+ ?screen_row 0 = "Main screen"
diff --git a/src/libvterm/t/64screen_pen.test b/src/libvterm/t/64screen_pen.test
new file mode 100644
index 0000000..1cb6324
--- /dev/null
+++ b/src/libvterm/t/64screen_pen.test
@@ -0,0 +1,61 @@
+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=idx(1) 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=idx(2)
+
+!Super/subscript
+PUSH "x\e[74m0\e[73m2\e[m"
+ ?screen_cell 0,8 = {0x78} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)
+ ?screen_cell 0,9 = {0x30} width=1 attrs={S_} fg=rgb(240,240,240) bg=rgb(0,0,0)
+ ?screen_cell 0,10 = {0x32} width=1 attrs={S^} fg=rgb(240,240,240) bg=rgb(0,0,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=idx(3) bg=idx(4)
+ ?screen_cell 0,79 = {} width=1 attrs={R} fg=idx(3) bg=idx(4)
+
+!DECSCNM xors reverse for entire screen
+PUSH "\e[?5h"
+ ?screen_cell 0,0 = {} width=1 attrs={} fg=idx(3) bg=idx(4)
+ ?screen_cell 0,79 = {} width=1 attrs={} fg=idx(3) bg=idx(4)
+ ?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=idx(3) bg=idx(4)
+ ?screen_cell 0,79 = {} width=1 attrs={R} fg=idx(3) bg=idx(4)
+ ?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/65screen_protect.test b/src/libvterm/t/65screen_protect.test
new file mode 100644
index 0000000..ec412a5
--- /dev/null
+++ b/src/libvterm/t/65screen_protect.test
@@ -0,0 +1,16 @@
+INIT
+WANTSCREEN
+
+!Selective erase
+RESET
+PUSH "A\e[1\"qB\e[\"qC"
+ ?screen_row 0 = "ABC"
+PUSH "\e[G\e[?J"
+ ?screen_row 0 = " B"
+
+!Non-selective erase
+RESET
+PUSH "A\e[1\"qB\e[\"qC"
+ ?screen_row 0 = "ABC"
+PUSH "\e[G\e[J"
+ ?screen_row 0 = ""
diff --git a/src/libvterm/t/66screen_extent.test b/src/libvterm/t/66screen_extent.test
new file mode 100644
index 0000000..a126cec
--- /dev/null
+++ b/src/libvterm/t/66screen_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/67screen_dbl_wh.test b/src/libvterm/t/67screen_dbl_wh.test
new file mode 100644
index 0000000..9c81e83
--- /dev/null
+++ b/src/libvterm/t/67screen_dbl_wh.test
@@ -0,0 +1,38 @@
+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)
+
+!DWL doesn't spill over on scroll
+RESET
+PUSH "\e[25H\e#6Final\r\n"
+ ?screen_cell 23,0 = {0x46} width=1 attrs={} dwl fg=rgb(240,240,240) bg=rgb(0,0,0)
+ ?screen_cell 24,0 = {} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)
diff --git a/src/libvterm/t/68screen_termprops.test b/src/libvterm/t/68screen_termprops.test
new file mode 100644
index 0000000..bba6660
--- /dev/null
+++ b/src/libvterm/t/68screen_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/69screen_reflow.test b/src/libvterm/t/69screen_reflow.test
new file mode 100644
index 0000000..278cc5b
--- /dev/null
+++ b/src/libvterm/t/69screen_reflow.test
@@ -0,0 +1,79 @@
+INIT
+# Run these tests on a much smaller default screen, so debug output is
+# nowhere near as noisy
+RESIZE 5,10
+WANTSTATE
+WANTSCREEN r
+RESET
+
+!Resize wider reflows wide lines
+RESET
+PUSH "A"x12
+ ?screen_row 0 = "AAAAAAAAAA"
+ ?screen_row 1 = "AA"
+ ?lineinfo 1 = cont
+ ?cursor = 1,2
+RESIZE 5,15
+ ?screen_row 0 = "AAAAAAAAAAAA"
+ ?screen_row 1 =
+ ?lineinfo 1 =
+ ?cursor = 0,12
+RESIZE 5,20
+ ?screen_row 0 = "AAAAAAAAAAAA"
+ ?screen_row 1 =
+ ?lineinfo 1 =
+ ?cursor = 0,12
+
+!Resize narrower can create continuation lines
+RESET
+RESIZE 5,10
+PUSH "ABCDEFGHI"
+ ?screen_row 0 = "ABCDEFGHI"
+ ?screen_row 1 = ""
+ ?lineinfo 1 =
+ ?cursor = 0,9
+RESIZE 5,8
+ ?screen_row 0 = "ABCDEFGH"
+ ?screen_row 1 = "I"
+ ?lineinfo 1 = cont
+ ?cursor = 1,1
+RESIZE 5,6
+ ?screen_row 0 = "ABCDEF"
+ ?screen_row 1 = "GHI"
+ ?lineinfo 1 = cont
+ ?cursor = 1,3
+
+!Shell wrapped prompt behaviour
+RESET
+RESIZE 5,10
+PUSH "PROMPT GOES HERE\r\n> \r\n\r\nPROMPT GOES HERE\r\n> "
+ ?screen_row 0 = "> "
+ ?screen_row 1 = ""
+ ?screen_row 2 = "PROMPT GOE"
+ ?screen_row 3 = "S HERE"
+ ?lineinfo 3 = cont
+ ?screen_row 4 = "> "
+ ?cursor = 4,2
+RESIZE 5,11
+ ?screen_row 0 = "> "
+ ?screen_row 1 = ""
+ ?screen_row 2 = "PROMPT GOES"
+ ?screen_row 3 = " HERE"
+ ?lineinfo 3 = cont
+ ?screen_row 4 = "> "
+ ?cursor = 4,2
+RESIZE 5,12
+ ?screen_row 0 = "> "
+ ?screen_row 1 = ""
+ ?screen_row 2 = "PROMPT GOES "
+ ?screen_row 3 = "HERE"
+ ?lineinfo 3 = cont
+ ?screen_row 4 = "> "
+ ?cursor = 4,2
+RESIZE 5,16
+ ?screen_row 0 = "> "
+ ?screen_row 1 = ""
+ ?screen_row 2 = "PROMPT GOES HERE"
+ ?lineinfo 3 =
+ ?screen_row 3 = "> "
+ ?cursor = 3,2
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..d12c120
--- /dev/null
+++ b/src/libvterm/t/harness.c
@@ -0,0 +1,1233 @@
+#include "vterm.h"
+#include "../src/vterm_internal.h" // We pull in some internal bits too
+
+#include <assert.h>
+#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;
+ if(sscanf(inpos, "%2x", &ch) < 1)
+ break;
+ *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 },
+ { "F1", VTERM_KEY_FUNCTION(1) },
+ { 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 void print_color(const VTermColor *col)
+{
+ if (VTERM_COLOR_IS_RGB(col)) {
+ printf("rgb(%d,%d,%d", col->red, col->green, col->blue);
+ }
+ else if (VTERM_COLOR_IS_INDEXED(col)) {
+ printf("idx(%d", col->index);
+ }
+ else {
+ printf("invalid(%d", col->type);
+ }
+ if (VTERM_COLOR_IS_DEFAULT_FG(col)) {
+ printf(",is_default_fg");
+ }
+ if (VTERM_COLOR_IS_DEFAULT_BG(col)) {
+ printf(",is_default_bg");
+ }
+ printf(")");
+}
+
+static VTerm *vt;
+static VTermState *state;
+static VTermScreen *screen;
+
+static VTermEncodingInstance encoding;
+
+static void term_output(const char *s, size_t len, void *user UNUSED)
+{
+ size_t i;
+
+ printf("output ");
+ for(i = 0; i < len; i++)
+ printf("%x%s", (unsigned char)s[i], i < len-1 ? "," : "\n");
+}
+
+static void printhex(const char *s, size_t len)
+{
+ while(len--)
+ printf("%02x", (uint8_t)(s++)[0]);
+}
+
+static int parser_text(const char bytes[], size_t len, void *user UNUSED)
+{
+ size_t 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 UNUSED)
+{
+ printf("control %02x\n", control);
+
+ return 1;
+}
+
+static int parser_escape(const char bytes[], size_t len, void *user UNUSED)
+{
+ if(bytes[0] >= 0x20 && bytes[0] < 0x30) {
+ if(len < 2)
+ return -1;
+ len = 2;
+ }
+ else {
+ len = 1;
+ }
+
+ printf("escape ");
+ printhex(bytes, len);
+ printf("\n");
+
+ return len;
+}
+
+static int parser_csi(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user UNUSED)
+{
+ 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(int command, VTermStringFragment frag, void *user UNUSED)
+{
+
+ printf("osc ");
+
+ if(frag.initial) {
+ if(command == -1)
+ printf("[");
+ else
+ printf("[%d;", command);
+ }
+
+ printhex(frag.str, frag.len);
+
+ if(frag.final)
+ printf("]");
+
+ printf("\n");
+
+ return 1;
+}
+
+static int parser_dcs(const char *command, size_t commandlen, VTermStringFragment frag, void *user UNUSED)
+{
+ printf("dcs ");
+
+ if(frag.initial) {
+ size_t i;
+ printf("[");
+ for(i = 0; i < commandlen; i++)
+ printf("%02x", command[i]);
+ }
+
+ printhex(frag.str, frag.len);
+
+ if(frag.final)
+ printf("]");
+
+ printf("\n");
+
+ return 1;
+}
+
+static int parser_apc(VTermStringFragment frag, void *user UNUSED)
+{
+ printf("apc ");
+
+ if(frag.initial)
+ printf("[");
+
+ printhex(frag.str, frag.len);
+
+ if(frag.final)
+ printf("]");
+
+ printf("\n");
+
+ return 1;
+}
+
+static int parser_pm(VTermStringFragment frag, void *user UNUSED)
+{
+ printf("pm ");
+
+ if(frag.initial)
+ printf("[");
+
+ printhex(frag.str, frag.len);
+
+ if(frag.final)
+ printf("]");
+
+ printf("\n");
+
+ return 1;
+}
+
+static int parser_sos(VTermStringFragment frag, void *user UNUSED)
+{
+ printf("sos ");
+
+ if(frag.initial)
+ printf("[");
+
+ printhex(frag.str, frag.len);
+
+ if(frag.final)
+ printf("]");
+
+ 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
+ parser_apc, // apc
+ parser_pm, // pm
+ parser_sos, // sos
+ NULL // resize
+};
+
+static VTermStateFallbacks fallbacks = {
+ parser_control, // control
+ parser_csi, // csi
+ parser_osc, // osc
+ parser_dcs, // dcs
+ parser_apc, // dcs
+ parser_pm, // pm
+ parser_sos // sos
+};
+
+/* These callbacks are shared by State and Screen */
+
+static int want_movecursor = 0;
+static VTermPos state_pos;
+static int movecursor(VTermPos pos, VTermPos oldpos UNUSED, int visible UNUSED, void *user UNUSED)
+{
+ 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 UNUSED)
+{
+ 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 UNUSED)
+{
+ 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 UNUSED)
+{
+ 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\"%.*s\"%s\n", prop,
+ val->string.initial ? "[" : "", val->string.len, val->string.str, val->string.final ? "]" : "");
+ return 1;
+ case VTERM_VALUETYPE_COLOR:
+ printf("settermprop %d ", prop);
+ print_color(&val->color);
+ printf("\n");
+ 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 UNUSED)
+{
+ int i;
+ if(!want_state_putglyph)
+ return 1;
+
+ printf("putglyph ");
+ for(i = 0; i < VTERM_MAX_CHARS_PER_CELL && 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 UNUSED)
+{
+ 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 conceal;
+ int strike;
+ int font;
+ int small;
+ int baseline;
+ VTermColor foreground;
+ VTermColor background;
+} state_pen;
+static int state_setpenattr(VTermAttr attr, VTermValue *val, void *user UNUSED)
+{
+ 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_CONCEAL:
+ state_pen.conceal = 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_SMALL:
+ state_pen.small = val->boolean;
+ break;
+ case VTERM_ATTR_BASELINE:
+ state_pen.baseline = 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 UNUSED, const VTermLineInfo *newinfo UNUSED, const VTermLineInfo *oldinfo UNUSED, void *user UNUSED)
+{
+ return 1;
+}
+
+static int want_state_scrollback = 0;
+static int state_sb_clear(void *user UNUSED) {
+ if(!want_state_scrollback)
+ return 1;
+
+ printf("sb_clear\n");
+ return 0;
+}
+
+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
+ state_sb_clear, // sb_clear
+};
+
+static int selection_set(VTermSelectionMask mask, VTermStringFragment frag, void *user UNUSED)
+{
+ printf("selection-set mask=%04X ", mask);
+ if(frag.initial)
+ printf("[");
+ printhex(frag.str, frag.len);
+ if(frag.final)
+ printf("]");
+ printf("\n");
+
+ return 1;
+}
+
+static int selection_query(VTermSelectionMask mask, void *user UNUSED)
+{
+ printf("selection-query mask=%04X\n", mask);
+
+ return 1;
+}
+
+VTermSelectionCallbacks selection_cbs = {
+ .set = selection_set,
+ .query = selection_query,
+};
+
+static int want_screen_damage = 0;
+static int want_screen_damage_cells = 0;
+static int screen_damage(VTermRect rect, void *user UNUSED)
+{
+ 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 UNUSED)
+{
+ 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 UNUSED)
+{
+ 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;
+}
+
+static int screen_sb_clear(void *user UNUSED)
+{
+ if(!want_screen_scrollback)
+ return 1;
+
+ printf("sb_clear\n");
+ return 0;
+}
+
+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
+ screen_sb_clear, // sb_clear
+};
+
+int main(int argc UNUSED, char **argv UNUSED)
+{
+ 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);
+
+ // Somehow this makes tests fail
+ // vterm_output_set_callback(vt, term_output, NULL);
+ }
+
+ else if(streq(line, "WANTPARSER")) {
+ assert(vt);
+ vterm_parser_set_callbacks(vt, &parser_cbs, NULL);
+ }
+
+ else if(strstartswith(line, "WANTSTATE") && (line[9] == '\0' || line[9] == ' ')) {
+ int i = 9;
+ int sense = 1;
+ assert(vt);
+ if(!state) {
+ state = vterm_obtain_state(vt);
+ vterm_state_set_callbacks(state, &state_cbs, NULL);
+ vterm_state_set_selection_callbacks(state, &selection_cbs, NULL, NULL, 1024);
+ 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 ? &fallbacks : NULL, NULL);
+ break;
+ case 'b':
+ want_state_scrollback = sense;
+ 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;
+ assert(vt);
+ if(!screen)
+ screen = vterm_obtain_screen(vt);
+ vterm_screen_set_callbacks(screen, &screen_cbs, NULL);
+
+ while(line[i] == ' ')
+ i++;
+ for( ; line[i]; i++)
+ switch(line[i]) {
+ case '-':
+ sense = 0;
+ break;
+ case 'a':
+ vterm_screen_enable_altscreen(screen, 1);
+ 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;
+ case 'r':
+ vterm_screen_enable_reflow(screen, 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);
+ assert(len);
+
+ 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);
+ assert(len);
+
+ 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 ")) {
+ assert(state);
+ 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, "SELECTION ")) {
+ char *linep = line + 10;
+ unsigned int mask;
+ int len;
+ VTermStringFragment frag = { 0 };
+ sscanf(linep, "%x%n", &mask, &len);
+ linep += len;
+ while(linep[0] == ' ')
+ linep++;
+ if(linep[0] == '[') {
+ frag.initial = TRUE;
+ linep++;
+ while(linep[0] == ' ')
+ linep++;
+ }
+ frag.len = inplace_hex2bytes(linep);
+ frag.str = linep;
+ assert(frag.len);
+
+ linep += frag.len * 2;
+ while(linep[0] == ' ')
+ linep++;
+ if(linep[0] == ']') {
+ frag.final = TRUE;
+ }
+ vterm_state_send_selection(state, mask, frag);
+ }
+
+ else if(strstartswith(line, "DAMAGEMERGE ")) {
+ assert(screen);
+ 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")) {
+ assert(screen);
+ vterm_screen_flush_damage(screen);
+ }
+
+ else if(line[0] == '?') {
+ if(streq(line, "?cursor")) {
+ assert(state);
+ 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 ")) {
+ assert(state);
+ 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, "small")) {
+ vterm_state_get_penattr(state, VTERM_ATTR_SMALL, &val);
+ if(val.boolean != state_pen.small)
+ printf("! pen small mismatch; state=%s, event=%s\n",
+ BOOLSTR(val.boolean), BOOLSTR(state_pen.small));
+ else
+ printf("%s\n", BOOLSTR(state_pen.small));
+ }
+ else if(streq(linep, "baseline")) {
+ vterm_state_get_penattr(state, VTERM_ATTR_BASELINE, &val);
+ if(val.number != state_pen.baseline)
+ printf("! pen baseline mismatch: state=%d, event=%d\n",
+ val.number, state_pen.baseline);
+ else
+ printf("%s\n", state_pen.baseline == VTERM_BASELINE_RAISE ? "raise"
+ : state_pen.baseline == VTERM_BASELINE_LOWER ? "lower"
+ : "normal");
+ }
+ else if(streq(linep, "foreground")) {
+ print_color(&state_pen.foreground);
+ printf("\n");
+ }
+ else if(streq(linep, "background")) {
+ print_color(&state_pen.background);
+ printf("\n");
+ }
+ else
+ printf("?\n");
+ }
+ else if(strstartswith(line, "?lineinfo ")) {
+ assert(state);
+ char *linep = line + 10;
+ int row;
+ const VTermLineInfo *info;
+ while(linep[0] == ' ')
+ linep++;
+ if(sscanf(linep, "%d", &row) < 1) {
+ printf("! lineinfo unrecognised input\n");
+ goto abort_line;
+ }
+ info = vterm_state_get_lineinfo(state, row);
+ if(info->doublewidth)
+ printf("dwl ");
+ if(info->doubleheight)
+ printf("dhl ");
+ if(info->continuation)
+ printf("cont ");
+ printf("\n");
+ }
+ else if(strstartswith(line, "?screen_chars ")) {
+ assert(screen);
+ 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)
+ ; // fine
+ else if(sscanf(linep, "%d", &rect.start_row) == 1) {
+ rect.end_row = rect.start_row + 1;
+ rect.start_col = 0;
+ vterm_get_size(vt, NULL, &rect.end_col);
+ }
+ else {
+ 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 ")) {
+ assert(screen);
+ 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 ")) {
+ assert(screen);
+ 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);
+ if(cell.attrs.small) printf("S");
+ if(cell.attrs.baseline) printf(
+ cell.attrs.baseline == VTERM_BASELINE_RAISE ? "^" :
+ "_");
+ printf("} ");
+ if(cell.attrs.dwl) printf("dwl ");
+ if(cell.attrs.dhl) printf("dhl-%s ", cell.attrs.dhl == 2 ? "bottom" : "top");
+ printf("fg=");
+ vterm_screen_convert_color_to_rgb(screen, &cell.fg);
+ print_color(&cell.fg);
+ printf(" bg=");
+ vterm_screen_convert_color_to_rgb(screen, &cell.bg);
+ print_color(&cell.bg);
+ printf("\n");
+ }
+ else if(strstartswith(line, "?screen_eol ")) {
+ assert(screen);
+ 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 ")) {
+ assert(screen);
+ 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) {
+ char outbuff[1024];
+ vterm_output_read(vt, outbuff, outlen);
+
+ term_output(outbuff, outlen, NULL);
+ }
+
+ 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..3440465
--- /dev/null
+++ b/src/libvterm/t/run-test.pl
@@ -0,0 +1,233 @@
+#!/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;
+my $EXECUTABLE = "t/harness";
+GetOptions(
+ 'valgrind|v+' => \$VALGRIND,
+ 'executable|e=s' => \$EXECUTABLE,
+ 'fail-early|F' => \(my $FAIL_EARLY),
+) or exit 1;
+
+my ( $hin, $hout, $hpid );
+{
+ my @command = $EXECUTABLE;
+ 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;
+
+my $linenum = 0;
+
+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 "# line $linenum: 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 "# line $linenum: Test failed\n" unless $fail_printed++;
+ print "# Expected: $expectation\n" .
+ "# Actual: $outline\n";
+ }
+
+ if( @expect ) {
+ print "# line $linenum: Test failed\n" unless $fail_printed++;
+ print "# Expected: $_\n" .
+ "# didn't happen\n" for @expect;
+ }
+
+ $exitcode = 1 if $fail_printed;
+ exit $exitcode if $exitcode and $FAIL_EARLY;
+}
+
+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;
+ }
+ elsif( $line =~ m/^(SELECTION \d+) +(\[?)(.*?)(\]?)$/ ) {
+ # we're evil
+ my $string = eval($3);
+ $line = "$1 $2 " . unpack( "H*", $string ) . " $4";
+ }
+
+ 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/^(osc) (\[\d+)? *(.*?)(\]?)$/ ) {
+ my ( $cmd, $initial, $data, $final ) = ( $1, $2, $3, $4 );
+ $initial //= "";
+ $initial .= ";" if $initial =~ m/\d+/;
+
+ $line = "$cmd $initial" . join( "", map sprintf("%02x", $_), unpack "C*", length $data ? eval($data) : "" ) . "$final";
+ }
+ elsif( $line =~ m/^(escape|dcs|apc|pm|sos) (\[?)(.*?)(\]?)$/ ) {
+ $line = "$1 $2" . join( "", map sprintf("%02x", $_), unpack "C*", length $3 ? eval($3) : "" ) . "$4";
+ }
+ 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|sb_clear|settermprop|setmousefunc|selection-query) ?/ ) {
+ # no conversion
+ }
+ elsif( $line =~ m/^(selection-set) (.*?) (\[?)(.*?)(\]?)$/ ) {
+ $line = "$1 $2 $3" . join( "", map sprintf("%02x", $_), unpack "C*", eval($4) ) . "$5";
+ }
+ 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 $want;
+
+ if( $line =~ m/^"/ ) {
+ $want = eval($line);
+ }
+ else {
+ # Turn 0xDD,0xDD,... directly into bytes
+ $want = pack "C*", map { hex } split m/,/, $line;
+ }
+
+ do_onetest if defined $command;
+
+ $hin->print( "\?screen_chars $row\n" );
+ my $response = <$hout>;
+ chomp $response;
+
+ $response = pack "C*", map { hex } split m/,/, $response;
+ if( $response ne $want ) {
+ print "# line $linenum: Assert ?screen_row $row failed:\n" .
+ "# Expected: $want\n" .
+ "# Actual: $response\n";
+ $exitcode = 1;
+ exit $exitcode if $exitcode and $FAIL_EARLY;
+ }
+ }
+ # Assertions start with '?'
+ elsif( $line =~ s/^\?([a-z]+.*?=)\s*// ) {
+ do_onetest if defined $command;
+
+ my ( $assertion ) = $1 =~ m/^(.*)\s+=/;
+ my $expectation = $line;
+
+ $hin->print( "\?$assertion\n" );
+ my $response = <$hout>; defined $response or wait, die "Test harness failed - $?\n";
+ chomp $response; $response =~ s/^\s+|\s+$//g;
+
+ # Some convenience formatting
+ if( $assertion =~ m/^screen_chars/ and $expectation =~ m/^"/ ) {
+ $expectation = join ",", map sprintf("0x%02x", ord $_), split m//, eval($expectation);
+ }
+
+ if( $response ne $expectation ) {
+ print "# line $linenum: Assert $assertion failed:\n" .
+ "# Expected: $expectation\n" .
+ "# Actual: $response\n";
+ $exitcode = 1;
+ exit $exitcode if $exitcode and $FAIL_EARLY;
+ }
+ }
+ # 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> ) {
+ $linenum++;
+ $line =~ s/^\s+//;
+ chomp $line;
+
+ next if $line =~ m/^(?:#|$)/;
+ last if $line eq "__END__";
+
+ 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..8d7e59a
--- /dev/null
+++ b/src/libvterm/vterm.pc.in
@@ -0,0 +1,8 @@
+libdir=@LIBDIR@
+includedir=@INCDIR@
+
+Name: vterm
+Description: Abstract VT220/Xterm/ECMA-48 emulation library
+Version: @VERSION@
+Libs: -L${libdir} -lvterm
+Cflags: -I${includedir}