diff options
Diffstat (limited to 'src/libvterm')
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} |