[
  {
    "path": ".bzrignore",
    "content": ".libs\n*.lo\n*.la\n\nbin/*\n!bin/*.c\n\npangoterm\nt/test\nt/suites.h\nt/externs.h\nt/harness\nsrc/encoding/*.inc\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"daily\"\n    commit-message:\n      prefix: \"ci\"\n"
  },
  {
    "path": ".github/workflows/sync.yml",
    "content": "name: sync\non:\n  schedule:\n    - cron: '30 1 * * *' # Run every day at 01:30\n  workflow_dispatch:\n\npermissions:\n  contents: write\n\njobs:\n  mirror:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n          ref: mirror\n      - run: sudo apt-get install brz\n\n      - name: Set up git config\n        run: |\n          git config --global user.name 'marvim'\n          git config --global user.email 'marvim@users.noreply.github.com'\n\n      - run: git pull bzr::http://bazaar.leonerd.org.uk/c/libvterm --tags --rebase\n      - run: |\n          git push --force-with-lease\n          git push --tags\n\n  nvim:\n    runs-on: ubuntu-latest\n    needs: mirror\n    steps:\n      - uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n\n      - name: Set up git config\n        run: |\n          git config --global user.name 'marvim'\n          git config --global user.email 'marvim@users.noreply.github.com'\n\n      - run: git merge origin/mirror -m \"Merge mirror\"\n      - run: git push\n"
  },
  {
    "path": "CODE-MAP",
    "content": "bin/\n - contains some standalone programs\n\nbin/unterm.c\n - an example program using libvterm to reconstruct the final state of a\n   terminal after replaying captured input\n\nbin/vterm-ctrl.c\n - a helper program that emits sequences to control a running libvterm\n   terminal\n\nbin/vterm-dump.c\n - an example program using the parser layer of libvterm to interpret captured\n   input\n\nCODE-MAP\n - high-level list and description of files in the repository\n\nCONTRIBUTING\n - documentation explaining how developers can contribute fixes and features\n\ndoc/\n - contains documentation\n\ndoc/seqs.txt\n - documents the sequences recognised by the library\n\ninclude/vterm.h\n - main include file\n\ninclude/vterm_keycodes.h\n - include file containing the keyboard input keycode enumerations\n\nLICENSE\n - legalese\n\nMakefile\n - main build file\n\nsrc/\n - contains the source code for the library\n\nsrc/encoding.c\n - handles mapping ISO/IEC 2022 alternate character sets into Unicode\n   codepoints\n\nsrc/keyboard.c\n - handles sending reported keyboard events to the output stream\n\nsrc/mouse.c\n - handles sending reported mouse events to the output stream\n\nsrc/parser.c\n - parses bytes from the input stream into parser-level events\n\nsrc/pen.c\n - interprets SGR sequences and maintains current rendering attributes\n\nsrc/screen.c\n - uses state-level events to maintain a buffer of current screen contents\n\nsrc/state.c\n - follows parser-level events to keep track of the overall terminal state\n\nsrc/unicode.c\n - utility functions for Unicode and UTF-8 handling\n\nsrc/vterm.c\n - toplevel object state and miscellaneous functions\n\nsrc/vterm_internal.h\n - include file for definitions private to the library's internals\n\nt/\n - contains unit tests\n\nt/harness.c\n - standalone program to embed the library into for unit-test purposes\n\nt/run-test.pl\n - invokes the test harness to run a single unit test script\n"
  },
  {
    "path": "CONTRIBUTING",
    "content": "How to Contribute\n-----------------\n\nThe main resources for this library are:\n\n  Launchpad\n    https://launchpad.net/libvterm\n\n  IRC:\n    ##tty or #tickit on irc.libera.chat\n\n  Email:\n    Paul \"LeoNerd\" Evans <leonerd@leonerd.org.uk>\n\n\nBug reports and feature requests can be sent to any of the above resources.\n\nNew features, bug patches, etc.. should in the first instance be discussed via\nany of the resources listed above, before starting work on the actual code.\nThere may be future plans or development already in-progress that could be\naffected so it is better to discuss the ideas first before starting work\nactually writing any code.\n"
  },
  {
    "path": "LICENSE",
    "content": "\n\nThe MIT License\n\nCopyright (c) 2008 Paul Evans <leonerd@leonerd.org.uk>\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "Makefile",
    "content": "ifeq ($(shell uname),Darwin)\n  LIBTOOL ?= glibtool\nelse\n  LIBTOOL ?= libtool\nendif\n\nifneq ($(VERBOSE),1)\n  LIBTOOL +=--quiet\nendif\n\noverride CFLAGS +=-Wall -Iinclude -std=c99 -Wpedantic\n\nifeq ($(shell uname),SunOS)\n  override CFLAGS +=-D__EXTENSIONS__ -D_XPG6 -D__XOPEN_OR_POSIX\nendif\n\nifeq ($(DEBUG),1)\n  override CFLAGS +=-ggdb -DDEBUG\nendif\n\nifeq ($(PROFILE),1)\n  override CFLAGS +=-pg\n  override LDFLAGS+=-pg\nendif\n\nCFILES=$(sort $(wildcard src/*.c))\nHFILES=$(sort $(wildcard include/*.h))\nOBJECTS=$(CFILES:.c=.lo)\nLIBRARY=libvterm.la\n\nBINFILES_SRC=$(sort $(wildcard bin/*.c))\nBINFILES=$(BINFILES_SRC:.c=)\n\nTBLFILES=$(sort $(wildcard src/encoding/*.tbl))\nINCFILES=$(TBLFILES:.tbl=.inc)\n\nHFILES_INT=$(sort $(wildcard src/*.h)) $(HFILES)\n\nVERSION_CURRENT=0\nVERSION_REVISION=0\nVERSION_AGE=0\n\nVERSION=0.3.3\n\nPREFIX=/usr/local\nBINDIR=$(PREFIX)/bin\nLIBDIR=$(PREFIX)/lib\nINCDIR=$(PREFIX)/include\nMANDIR=$(PREFIX)/share/man\nMAN3DIR=$(MANDIR)/man3\n\nall: $(LIBRARY) $(BINFILES)\n\n$(LIBRARY): $(OBJECTS)\n\t@echo LINK $@\n\t@$(LIBTOOL) --mode=link --tag=CC $(CC) -rpath $(LIBDIR) -version-info $(VERSION_CURRENT):$(VERSION_REVISION):$(VERSION_AGE) -o $@ $^ $(LDFLAGS)\n\nsrc/%.lo: src/%.c $(HFILES_INT)\n\t@echo CC $<\n\t@$(LIBTOOL) --mode=compile --tag=CC $(CC) $(CFLAGS) -o $@ -c $<\n\nsrc/encoding/%.inc: src/encoding/%.tbl\n\t@echo TBL $<\n\t@perl -CSD tbl2inc_c.pl $< >$@\n\nsrc/fullwidth.inc:\n\t@perl find-wide-chars.pl >$@\n\nsrc/encoding.lo: $(INCFILES)\n\nbin/%: bin/%.c $(LIBRARY)\n\t@echo CC $<\n\t@$(LIBTOOL) --mode=link --tag=CC $(CC) $(CFLAGS) -o $@ $< -lvterm $(LDFLAGS)\n\nt/harness.lo: t/harness.c $(HFILES)\n\t@echo CC $<\n\t@$(LIBTOOL) --mode=compile --tag=CC $(CC) $(CFLAGS) -o $@ -c $<\n\nt/harness: t/harness.lo $(LIBRARY)\n\t@echo LINK $@\n\t@$(LIBTOOL) --mode=link --tag=CC $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)\n\n.PHONY: test\ntest: $(LIBRARY) t/harness\n\tfor T in `ls t/[0-9]*.test`; do echo \"** $$T **\"; perl t/run-test.pl $$T $(if $(VALGRIND),--valgrind) || exit 1; done\n\n.PHONY: clean\nclean:\n\t$(LIBTOOL) --mode=clean rm -f $(OBJECTS) $(INCFILES)\n\t$(LIBTOOL) --mode=clean rm -f t/harness.lo t/harness\n\t$(LIBTOOL) --mode=clean rm -f $(LIBRARY) $(BINFILES)\n\n.PHONY: install\ninstall: install-inc install-lib install-bin\n\ninstall-inc:\n\tinstall -d $(DESTDIR)$(INCDIR)\n\tinstall -m644 $(HFILES) $(DESTDIR)$(INCDIR)\n\tinstall -d $(DESTDIR)$(LIBDIR)/pkgconfig\n\tsed -e \"s,@INCDIR@,$(INCDIR),\" -e \"s,@LIBDIR@,$(LIBDIR),\" -e \"s,@VERSION@,$(VERSION),\" <vterm.pc.in >$(DESTDIR)$(LIBDIR)/pkgconfig/vterm.pc\n\ninstall-lib: $(LIBRARY)\n\tinstall -d $(DESTDIR)$(LIBDIR)\n\t$(LIBTOOL) --mode=install install $(LIBRARY) $(DESTDIR)$(LIBDIR)/$(LIBRARY)\n\t$(LIBTOOL) --mode=finish $(DESTDIR)$(LIBDIR)\n\ninstall-bin: $(BINFILES)\n\tinstall -d $(DESTDIR)$(BINDIR)\n\t$(LIBTOOL) --mode=install install $(BINFILES) $(DESTDIR)$(BINDIR)/\n\n# DIST CUT\n\nDISTDIR=libvterm-$(VERSION)\n\ndistdir: $(INCFILES)\n\tmkdir __distdir\n\tcp LICENSE CONTRIBUTING __distdir\n\tmkdir __distdir/src\n\tcp src/*.c src/*.h src/*.inc __distdir/src\n\tmkdir __distdir/src/encoding\n\tcp src/encoding/*.inc __distdir/src/encoding\n\tmkdir __distdir/include\n\tcp include/*.h __distdir/include\n\tmkdir __distdir/bin\n\tcp bin/*.c __distdir/bin\n\tmkdir __distdir/t\n\tcp t/*.test t/harness.c t/run-test.pl __distdir/t\n\tsed \"s,@VERSION@,$(VERSION),\" <vterm.pc.in >__distdir/vterm.pc.in\n\tsed \"/^# DIST CUT/Q\" <Makefile >__distdir/Makefile\n\tmv __distdir $(DISTDIR)\n\nTARBALL=$(DISTDIR).tar.gz\n\ndist: distdir\n\ttar -czf $(TARBALL) $(DISTDIR)\n\trm -rf $(DISTDIR)\n\ndist+bzr:\n\t$(MAKE) dist VERSION=$(VERSION)+bzr`bzr revno`\n\ndistdir+bzr:\n\t$(MAKE) distdir VERSION=$(VERSION)+bzr`bzr revno`\n"
  },
  {
    "path": "README.md",
    "content": "# libvterm fork\n\n**Important**: This is currently not used by neovim. Vterm has instead been\nbundled into the neovim repo itself:\nhttps://github.com/neovim/neovim/tree/master/src/nvim/vterm. All pull requests\nshould be sent to neovim instead.\n\nThis is a fork of https://www.leonerd.org.uk/code/libvterm. An exact mirror\nof upstream is in the branch `mirror`.\n"
  },
  {
    "path": "bin/unterm.c",
    "content": "#include <stdio.h>\n#include <string.h>\n\n#include <errno.h>\n#include <fcntl.h>\n#include <getopt.h>\n#include <unistd.h>\n\n#include \"vterm.h\"\n\n#include \"../src/utf8.h\" // fill_utf8\n\n/*\n * unterm [OPTIONS] SCRIPTFILE\n *\n * Interprets terminal sequences in SCRIPTFILE and outputs the final state of\n * the terminal buffer at the end.\n *\n * OPTIONS:\n *   -f FORMAT  -- set the output format: [\"plain\" | \"sgr\"]\n *   -l LINES,\n *   -c COLS    -- set the size of the emulated terminal\n */\n\n#define streq(a,b) (!strcmp(a,b))\n\nstatic VTerm *vt;\nstatic VTermScreen *vts;\n\nstatic int cols;\nstatic int rows;\n\nstatic enum {\n  FORMAT_PLAIN,\n  FORMAT_SGR,\n} format = FORMAT_PLAIN;\n\nstatic int dump_cell_color(const VTermColor *col, int sgri, int sgr[], int fg)\n{\n    /* Reset the color if the given color is the default color */\n    if (fg && VTERM_COLOR_IS_DEFAULT_FG(col)) {\n        sgr[sgri++] = 39;\n        return sgri;\n    }\n    if (!fg && VTERM_COLOR_IS_DEFAULT_BG(col)) {\n        sgr[sgri++] = 49;\n        return sgri;\n    }\n\n    /* Decide whether to send an indexed color or an RGB color */\n    if (VTERM_COLOR_IS_INDEXED(col)) {\n        const uint8_t idx = col->indexed.idx;\n        if (idx < 8) {\n            sgr[sgri++] = (idx + (fg ? 30 : 40));\n        }\n        else if (idx < 16) {\n            sgr[sgri++] = (idx - 8 + (fg ? 90 : 100));\n        }\n        else {\n            sgr[sgri++] = (fg ? 38 : 48);\n            sgr[sgri++] = 5;\n            sgr[sgri++] = idx;\n        }\n    }\n    else if (VTERM_COLOR_IS_RGB(col)) {\n        sgr[sgri++] = (fg ? 38 : 48);\n        sgr[sgri++] = 2;\n        sgr[sgri++] = col->rgb.red;\n        sgr[sgri++] = col->rgb.green;\n        sgr[sgri++] = col->rgb.blue;\n    }\n    return sgri;\n}\n\nstatic void dump_cell(const VTermScreenCell *cell, const VTermScreenCell *prevcell)\n{\n  switch(format) {\n    case FORMAT_PLAIN:\n      break;\n    case FORMAT_SGR:\n      {\n        // If all 7 attributes change, that means 7 SGRs max\n        // Each colour could consume up to 5 entries\n        int sgr[7 + 2*5]; int sgri = 0;\n\n        if(!prevcell->attrs.bold && cell->attrs.bold)\n          sgr[sgri++] = 1;\n        if(prevcell->attrs.bold && !cell->attrs.bold)\n          sgr[sgri++] = 22;\n\n        if(!prevcell->attrs.underline && cell->attrs.underline)\n          sgr[sgri++] = 4;\n        if(prevcell->attrs.underline && !cell->attrs.underline)\n          sgr[sgri++] = 24;\n\n        if(!prevcell->attrs.italic && cell->attrs.italic)\n          sgr[sgri++] = 3;\n        if(prevcell->attrs.italic && !cell->attrs.italic)\n          sgr[sgri++] = 23;\n\n        if(!prevcell->attrs.blink && cell->attrs.blink)\n          sgr[sgri++] = 5;\n        if(prevcell->attrs.blink && !cell->attrs.blink)\n          sgr[sgri++] = 25;\n\n        if(!prevcell->attrs.reverse && cell->attrs.reverse)\n          sgr[sgri++] = 7;\n        if(prevcell->attrs.reverse && !cell->attrs.reverse)\n          sgr[sgri++] = 27;\n\n        if(!prevcell->attrs.conceal && cell->attrs.conceal)\n          sgr[sgri++] = 8;\n        if(prevcell->attrs.conceal && !cell->attrs.conceal)\n          sgr[sgri++] = 28;\n\n        if(!prevcell->attrs.strike && cell->attrs.strike)\n          sgr[sgri++] = 9;\n        if(prevcell->attrs.strike && !cell->attrs.strike)\n          sgr[sgri++] = 29;\n\n        if(!prevcell->attrs.font && cell->attrs.font)\n          sgr[sgri++] = 10 + cell->attrs.font;\n        if(prevcell->attrs.font && !cell->attrs.font)\n          sgr[sgri++] = 10;\n\n        if(!vterm_color_is_equal(&prevcell->fg, &cell->fg)) {\n          sgri = dump_cell_color(&cell->fg, sgri, sgr, 1);\n        }\n\n        if(!vterm_color_is_equal(&prevcell->bg, &cell->bg)) {\n          sgri = dump_cell_color(&cell->bg, sgri, sgr, 0);\n        }\n\n        if(!sgri)\n          break;\n\n        printf(\"\\x1b[\");\n        for(int i = 0; i < sgri; i++)\n          printf(!i               ? \"%d\" :\n              CSI_ARG_HAS_MORE(sgr[i]) ? \":%d\" :\n              \";%d\",\n              CSI_ARG(sgr[i]));\n        printf(\"m\");\n      }\n      break;\n  }\n\n  for(int i = 0; i < VTERM_MAX_CHARS_PER_CELL && cell->chars[i]; i++) {\n    char bytes[6];\n    bytes[fill_utf8(cell->chars[i], bytes)] = 0;\n    printf(\"%s\", bytes);\n  }\n}\n\nstatic void dump_eol(const VTermScreenCell *prevcell)\n{\n  switch(format) {\n    case FORMAT_PLAIN:\n      break;\n    case FORMAT_SGR:\n      if(prevcell->attrs.bold || prevcell->attrs.underline || prevcell->attrs.italic ||\n         prevcell->attrs.blink || prevcell->attrs.reverse || prevcell->attrs.strike ||\n         prevcell->attrs.conceal || prevcell->attrs.font)\n        printf(\"\\x1b[m\");\n      break;\n  }\n\n  printf(\"\\n\");\n}\n\nvoid dump_row(int row)\n{\n  VTermPos pos = { .row = row, .col = 0 };\n  VTermScreenCell prevcell = { 0 };\n  vterm_state_get_default_colors(vterm_obtain_state(vt), &prevcell.fg, &prevcell.bg);\n\n  while(pos.col < cols) {\n    VTermScreenCell cell;\n    vterm_screen_get_cell(vts, pos, &cell);\n\n    dump_cell(&cell, &prevcell);\n\n    pos.col += cell.width;\n    prevcell = cell;\n  }\n\n  dump_eol(&prevcell);\n}\n\nstatic int screen_sb_pushline(int cols, const VTermScreenCell *cells, void *user)\n{\n  VTermScreenCell prevcell = { 0 };\n  vterm_state_get_default_colors(vterm_obtain_state(vt), &prevcell.fg, &prevcell.bg);\n\n  for(int col = 0; col < cols; col++) {\n    dump_cell(cells + col, &prevcell);\n    prevcell = cells[col];\n  }\n\n  dump_eol(&prevcell);\n\n  return 1;\n}\n\nstatic int screen_resize(int new_rows, int new_cols, void *user)\n{\n  rows = new_rows;\n  cols = new_cols;\n  return 1;\n}\n\nstatic VTermScreenCallbacks cb_screen = {\n  .sb_pushline = &screen_sb_pushline,\n  .resize      = &screen_resize,\n};\n\nint main(int argc, char *argv[])\n{\n  rows = 25;\n  cols = 80;\n\n  int opt;\n  while((opt = getopt(argc, argv, \"f:l:c:\")) != -1) {\n    switch(opt) {\n      case 'f':\n        if(streq(optarg, \"plain\"))\n          format = FORMAT_PLAIN;\n        else if(streq(optarg, \"sgr\"))\n          format = FORMAT_SGR;\n        else {\n          fprintf(stderr, \"Unrecognised format '%s'\\n\", optarg);\n          exit(1);\n        }\n        break;\n\n      case 'l':\n        rows = atoi(optarg);\n        if(!rows)\n          rows = 25;\n        break;\n\n      case 'c':\n        cols = atoi(optarg);\n        if(!cols)\n          cols = 80;\n        break;\n    }\n  }\n\n  const char *file = argv[optind++];\n  int fd = open(file, O_RDONLY);\n  if(fd == -1) {\n    fprintf(stderr, \"Cannot open %s - %s\\n\", file, strerror(errno));\n    exit(1);\n  }\n\n  vt = vterm_new(rows, cols);\n  vterm_set_utf8(vt, true);\n\n  vts = vterm_obtain_screen(vt);\n  vterm_screen_set_callbacks(vts, &cb_screen, NULL);\n\n  vterm_screen_reset(vts, 1);\n\n  int len;\n  char buffer[1024];\n  while((len = read(fd, buffer, sizeof(buffer))) > 0) {\n    vterm_input_write(vt, buffer, len);\n  }\n\n  for(int row = 0; row < rows; row++) {\n    dump_row(row);\n  }\n\n  close(fd);\n\n  vterm_free(vt);\n\n  return 0;\n}\n"
  },
  {
    "path": "bin/vterm-ctrl.c",
    "content": "#define _XOPEN_SOURCE 600  /* strdup */\n\n#include <stdbool.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#define streq(a,b) (strcmp(a,b)==0)\n\n#include <termios.h>\n\nstatic char *getvalue(int *argip, int argc, char *argv[])\n{\n  if(*argip >= argc) {\n    fprintf(stderr, \"Expected an option value\\n\");\n    exit(1);\n  }\n\n  return argv[(*argip)++];\n}\n\nstatic int getchoice(int *argip, int argc, char *argv[], const char *options[])\n{\n  const char *arg = getvalue(argip, argc, argv);\n\n  int value = -1;\n  while(options[++value])\n    if(streq(arg, options[value]))\n      return value;\n\n  fprintf(stderr, \"Unrecognised option value %s\\n\", arg);\n  exit(1);\n}\n\ntypedef enum {\n  OFF,\n  ON,\n  QUERY,\n} BoolQuery;\n\nstatic BoolQuery getboolq(int *argip, int argc, char *argv[])\n{\n  return getchoice(argip, argc, argv, (const char *[]){\"off\", \"on\", \"query\", NULL});\n}\n\nstatic char *helptext[] = {\n  \"reset\",\n  \"s8c1t [off|on]\",\n  \"keypad [app|num]\",\n  \"screen [off|on|query]\",\n  \"cursor [off|on|query]\",\n  \"curblink [off|on|query]\",\n  \"curshape [block|under|bar|query]\",\n  \"mouse [off|click|clickdrag|motion]\",\n  \"reportfocus [off|on|query]\",\n  \"altscreen [off|on|query]\",\n  \"bracketpaste [off|on|query]\",\n  \"icontitle [STR]\",\n  \"icon [STR]\",\n  \"title [STR]\",\n  NULL\n};\n\nstatic bool seticanon(bool icanon, bool echo)\n{\n  struct termios termios;\n\n  tcgetattr(0, &termios);\n\n  bool ret = (termios.c_lflag & ICANON);\n\n  if(icanon) termios.c_lflag |=  ICANON;\n  else       termios.c_lflag &= ~ICANON;\n\n  if(echo) termios.c_lflag |=  ECHO;\n  else     termios.c_lflag &= ~ECHO;\n\n  tcsetattr(0, TCSANOW, &termios);\n\n  return ret;\n}\n\nstatic void await_c1(unsigned char c1)\n{\n  unsigned char c;\n\n  /* await CSI - 8bit or 2byte 7bit form */\n  bool in_esc = false;\n  while((c = getchar())) {\n    if(c == c1)\n      break;\n    if(in_esc && c == (char)(c1 - 0x40))\n      break;\n    if(!in_esc && c == 0x1b)\n      in_esc = true;\n    else\n      in_esc = false;\n  }\n}\n\nstatic char *read_csi(void)\n{\n  await_c1(0x9B); // CSI\n\n  /* TODO: This really should be a more robust CSI parser\n   */\n  char csi[32];\n  int i = 0;\n  for(; i < sizeof(csi)-1; i++) {\n    char c = csi[i] = getchar();\n    if(c >= 0x40 && c <= 0x7e)\n      break;\n  }\n  csi[++i] = 0;\n\n  // TODO: returns longer than 32?\n\n  return strdup(csi);\n}\n\nstatic char *read_dcs(void)\n{\n  await_c1(0x90);\n\n  char dcs[32];\n  bool in_esc = false;\n  int i = 0;\n  for(; i < sizeof(dcs)-1; ) {\n    unsigned char c = getchar();\n    if(c == 0x9c) // ST\n      break;\n    if(in_esc && c == 0x5c)\n      break;\n    if(!in_esc && c == 0x1b)\n      in_esc = true;\n    else {\n      dcs[i++] = c;\n      in_esc = false;\n    }\n  }\n  dcs[++i] = 0;\n\n  return strdup(dcs);\n}\n\nstatic void usage(int exitcode)\n{\n  fprintf(stderr, \"Control a libvterm-based terminal\\n\"\n      \"\\n\"\n      \"Options:\\n\");\n\n  for(char **p = helptext; *p; p++)\n    fprintf(stderr, \"  %s\\n\", *p);\n\n  exit(exitcode);\n}\n\nstatic bool query_dec_mode(int mode)\n{\n  printf(\"\\x1b[?%d$p\", mode);\n\n  char *s = NULL;\n  do {\n    if(s)\n      free(s);\n    s = read_csi();\n\n    /* expect \"?\" mode \";\" value \"$y\" */\n\n    int reply_mode, reply_value;\n    char reply_cmd;\n    /* If the sscanf format string ends in a literal, we can't tell from\n     * its return value if it matches. Hence we'll %c the cmd and check it\n     * explicitly\n     */\n    if(sscanf(s, \"?%d;%d$%c\", &reply_mode, &reply_value, &reply_cmd) < 3)\n      continue;\n    if(reply_cmd != 'y')\n      continue;\n\n    if(reply_mode != mode)\n      continue;\n\n    free(s);\n\n    if(reply_value == 1 || reply_value == 3)\n      return true;\n    if(reply_value == 2 || reply_value == 4)\n      return false;\n\n    printf(\"Unrecognised reply to DECRQM: %d\\n\", reply_value);\n    return false;\n  } while(1);\n}\n\nstatic void do_dec_mode(int mode, BoolQuery val, const char *name)\n{\n  switch(val) {\n    case OFF:\n    case ON:\n      printf(\"\\x1b[?%d%c\", mode, val == ON ? 'h' : 'l');\n      break;\n\n    case QUERY:\n      if(query_dec_mode(mode))\n        printf(\"%s on\\n\", name);\n      else\n        printf(\"%s off\\n\", name);\n      break;\n  }\n}\n\nstatic int query_rqss_numeric(char *cmd)\n{\n  printf(\"\\x1bP$q%s\\x1b\\\\\", cmd);\n\n  char *s = NULL;\n  do {\n    if(s)\n      free(s);\n    s = read_dcs();\n\n    if(!s)\n      return -1;\n    if(strlen(s) < strlen(cmd))\n      return -1;\n    if(strcmp(s + strlen(s) - strlen(cmd), cmd) != 0) {\n      printf(\"No match\\n\");\n      continue;\n    }\n\n    if(s[0] != '1' || s[1] != '$' || s[2] != 'r')\n      return -1;\n\n    int num;\n    if(sscanf(s + 3, \"%d\", &num) != 1)\n      return -1;\n\n    return num;\n  } while(1);\n}\n\nbool wasicanon;\n\nvoid restoreicanon(void)\n{\n  seticanon(wasicanon, true);\n}\n\nint main(int argc, char *argv[])\n{\n  int argi = 1;\n\n  if(argc == 1)\n    usage(0);\n\n  wasicanon = seticanon(false, false);\n  atexit(restoreicanon);\n\n  while(argi < argc) {\n    const char *arg = argv[argi++];\n\n    if(streq(arg, \"reset\")) {\n      printf(\"\\x1b\" \"c\");\n    }\n    else if(streq(arg, \"s8c1t\")) {\n      switch(getchoice(&argi, argc, argv, (const char *[]){\"off\", \"on\", NULL})) {\n      case 0:\n        printf(\"\\x1b F\"); break;\n      case 1:\n        printf(\"\\x1b G\"); break;\n      }\n    }\n    else if(streq(arg, \"keypad\")) {\n      switch(getchoice(&argi, argc, argv, (const char *[]){\"app\", \"num\", NULL})) {\n      case 0:\n        printf(\"\\x1b=\"); break;\n      case 1:\n        printf(\"\\x1b>\"); break;\n      }\n    }\n    else if(streq(arg, \"screen\")) {\n      do_dec_mode(5, getboolq(&argi, argc, argv), \"screen\");\n    }\n    else if(streq(arg, \"cursor\")) {\n      do_dec_mode(25, getboolq(&argi, argc, argv), \"cursor\");\n    }\n    else if(streq(arg, \"curblink\")) {\n      do_dec_mode(12, getboolq(&argi, argc, argv), \"curblink\");\n    }\n    else if(streq(arg, \"curshape\")) {\n      // TODO: This ought to query the current value of DECSCUSR because it\n      //   may need blinking on or off\n      int shape = getchoice(&argi, argc, argv, (const char *[]){\"block\", \"under\", \"bar\", \"query\", NULL});\n      switch(shape) {\n        case 3: // query\n          shape = query_rqss_numeric(\" q\");\n          switch(shape) {\n            case 1: case 2:\n              printf(\"curshape block\\n\");\n              break;\n            case 3: case 4:\n              printf(\"curshape under\\n\");\n              break;\n            case 5: case 6:\n              printf(\"curshape bar\\n\");\n              break;\n          }\n          break;\n\n        case 0:\n        case 1:\n        case 2:\n          printf(\"\\x1b[%d q\", 1 + (shape * 2));\n          break;\n      }\n    }\n    else if(streq(arg, \"mouse\")) {\n      switch(getchoice(&argi, argc, argv, (const char *[]){\"off\", \"click\", \"clickdrag\", \"motion\", NULL})) {\n      case 0:\n        printf(\"\\x1b[?1000l\"); break;\n      case 1:\n        printf(\"\\x1b[?1000h\"); break;\n      case 2:\n        printf(\"\\x1b[?1002h\"); break;\n      case 3:\n        printf(\"\\x1b[?1003h\"); break;\n      }\n    }\n    else if(streq(arg, \"reportfocus\")) {\n      do_dec_mode(1004, getboolq(&argi, argc, argv), \"reportfocus\");\n    }\n    else if(streq(arg, \"altscreen\")) {\n      do_dec_mode(1049, getboolq(&argi, argc, argv), \"altscreen\");\n    }\n    else if(streq(arg, \"bracketpaste\")) {\n      do_dec_mode(2004, getboolq(&argi, argc, argv), \"bracketpaste\");\n    }\n    else if(streq(arg, \"icontitle\")) {\n      printf(\"\\x1b]0;%s\\a\", getvalue(&argi, argc, argv));\n    }\n    else if(streq(arg, \"icon\")) {\n      printf(\"\\x1b]1;%s\\a\", getvalue(&argi, argc, argv));\n    }\n    else if(streq(arg, \"title\")) {\n      printf(\"\\x1b]2;%s\\a\", getvalue(&argi, argc, argv));\n    }\n    else {\n      fprintf(stderr, \"Unrecognised command %s\\n\", arg);\n      exit(1);\n    }\n  }\n\n  return 0;\n}\n"
  },
  {
    "path": "bin/vterm-dump.c",
    "content": "// Require getopt(3)\n#define _XOPEN_SOURCE\n\n#include <stdio.h>\n#include <string.h>\n#define streq(a,b) (strcmp(a,b)==0)\n\n#include <errno.h>\n#include <fcntl.h>\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <unistd.h>\n\n#include \"vterm.h\"\n\nstatic const char *special_begin = \"{\";\nstatic const char *special_end   = \"}\";\n\nstatic int parser_text(const char bytes[], size_t len, void *user)\n{\n  unsigned char *b = (unsigned char *)bytes;\n\n  int i;\n  for(i = 0; i < len; /* none */) {\n    if(b[i] < 0x20)        // C0\n      break;\n    else if(b[i] < 0x80)   // ASCII\n      i++;\n    else if(b[i] < 0xa0)   // C1\n      break;\n    else if(b[i] < 0xc0)   // UTF-8 continuation\n      break;\n    else if(b[i] < 0xe0) { // UTF-8 2-byte\n      // 2-byte UTF-8\n      if(len < i+2) break;\n      i += 2;\n    }\n    else if(b[i] < 0xf0) { // UTF-8 3-byte\n      if(len < i+3) break;\n      i += 3;\n    }\n    else if(b[i] < 0xf8) { // UTF-8 4-byte\n      if(len < i+4) break;\n      i += 4;\n    }\n    else                   // otherwise invalid\n      break;\n  }\n\n  printf(\"%.*s\", i, b);\n  return i;\n}\n\n/* 0     1      2      3       4     5      6      7      8      9      A      B      C      D      E      F    */\nstatic const char *name_c0[] = {\n  \"NUL\", \"SOH\", \"STX\", \"ETX\", \"EOT\", \"ENQ\", \"ACK\", \"BEL\", \"BS\",  \"HT\",  \"LF\",  \"VT\",  \"FF\",  \"CR\",  \"LS0\", \"LS1\",\n  \"DLE\", \"DC1\", \"DC2\", \"DC3\", \"DC4\", \"NAK\", \"SYN\", \"ETB\", \"CAN\", \"EM\",  \"SUB\", \"ESC\", \"FS\",  \"GS\",  \"RS\",  \"US\",\n};\nstatic const char *name_c1[] = {\n  NULL,  NULL,  \"BPH\", \"NBH\", NULL,  \"NEL\", \"SSA\", \"ESA\", \"HTS\", \"HTJ\", \"VTS\", \"PLD\", \"PLU\", \"RI\",  \"SS2\", \"SS3\",\n  \"DCS\", \"PU1\", \"PU2\", \"STS\", \"CCH\", \"MW\",  \"SPA\", \"EPA\", \"SOS\", NULL,  \"SCI\", \"CSI\", \"ST\",  \"OSC\", \"PM\",  \"APC\",\n};\n\nstatic int parser_control(unsigned char control, void *user)\n{\n  if(control < 0x20)\n    printf(\"%s%s%s\", special_begin, name_c0[control], special_end);\n  else if(control == 0x7f)\n    printf(\"%s%s%s\", special_begin, \"DEL\", special_end);\n  else if(control >= 0x80 && control < 0xa0 && name_c1[control - 0x80])\n    printf(\"%s%s%s\", special_begin, name_c1[control - 0x80], special_end);\n  else\n    printf(\"%sCONTROL 0x%02x%s\", special_begin, control, special_end);\n\n  if(control == 0x0a)\n    printf(\"\\n\");\n  return 1;\n}\n\nstatic int parser_escape(const char bytes[], size_t len, void *user)\n{\n  if(bytes[0] >= 0x20 && bytes[0] < 0x30) {\n    if(len < 2)\n      return -1;\n    len = 2;\n  }\n  else {\n    len = 1;\n  }\n\n  printf(\"%sESC %.*s%s\", special_begin, (int)len, bytes, special_end);\n\n  return len;\n}\n\n/* 0     1      2      3       4     5      6      7      8      9      A      B      C      D      E      F    */\nstatic const char *name_csi_plain[] = {\n  \"ICH\", \"CUU\", \"CUD\", \"CUF\", \"CUB\", \"CNL\", \"CPL\", \"CHA\", \"CUP\", \"CHT\", \"ED\",  \"EL\",  \"IL\",  \"DL\",  \"EF\",  \"EA\",\n  \"DCH\", \"SSE\", \"CPR\", \"SU\",  \"SD\",  \"NP\",  \"PP\",  \"CTC\", \"ECH\", \"CVT\", \"CBT\", \"SRS\", \"PTX\", \"SDS\", \"SIMD\",NULL,\n  \"HPA\", \"HPR\", \"REP\", \"DA\",  \"VPA\", \"VPR\", \"HVP\", \"TBC\", \"SM\",  \"MC\",  \"HPB\", \"VPB\", \"RM\",  \"SGR\", \"DSR\", \"DAQ\",\n};\n\n/*0           4           8           B         */\nstatic const int newline_csi_plain[] = {\n  0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0,\n  0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,\n  0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0,\n};\n\nstatic int parser_csi(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user)\n{\n  const char *name = NULL;\n  if(!leader && !intermed && command < 0x70)\n    name = name_csi_plain[command - 0x40];\n  else if(leader && streq(leader, \"?\") && !intermed) {\n    /* DEC */\n    switch(command) {\n      case 'h': name = \"DECSM\"; break;\n      case 'l': name = \"DECRM\"; break;\n    }\n    if(name)\n      leader = NULL;\n  }\n\n  if(!leader && !intermed && command < 0x70 && newline_csi_plain[command - 0x40])\n    printf(\"\\n\");\n\n  if(name)\n    printf(\"%s%s\", special_begin, name);\n  else\n    printf(\"%sCSI\", special_begin);\n\n  if(leader && leader[0])\n    printf(\" %s\", leader);\n\n  for(int i = 0; i < argcount; i++) {\n    printf(i ? \",\" : \" \");\n\n    if(args[i] == CSI_ARG_MISSING)\n      printf(\"*\");\n    else {\n      while(CSI_ARG_HAS_MORE(args[i]))\n        printf(\"%ld+\", CSI_ARG(args[i++]));\n      printf(\"%ld\", CSI_ARG(args[i]));\n    }\n  }\n\n  if(intermed && intermed[0])\n    printf(\" %s\", intermed);\n\n  if(name)\n    printf(\"%s\", special_end);\n  else\n    printf(\" %c%s\", command, special_end);\n\n  return 1;\n}\n\nstatic int parser_osc(int command, VTermStringFragment frag, void *user)\n{\n  if(frag.initial) {\n    if(command == -1)\n      printf(\"%sOSC \", special_begin);\n    else\n      printf(\"%sOSC %d;\", special_begin, command);\n  }\n\n  printf(\"%.*s\", (int)frag.len, frag.str);\n\n  if(frag.final)\n    printf(\"%s\", special_end);\n\n  return 1;\n}\n\nstatic int parser_dcs(const char *command, size_t commandlen, VTermStringFragment frag, void *user)\n{\n  if(frag.initial)\n    printf(\"%sDCS %.*s\", special_begin, (int)commandlen, command);\n\n  printf(\"%.*s\", (int)frag.len, frag.str);\n\n  if(frag.final)\n    printf(\"%s\", special_end);\n\n  return 1;\n}\n\nstatic VTermParserCallbacks parser_cbs = {\n  .text    = &parser_text,\n  .control = &parser_control,\n  .escape  = &parser_escape,\n  .csi     = &parser_csi,\n  .osc     = &parser_osc,\n  .dcs     = &parser_dcs,\n};\n\nint main(int argc, char *argv[])\n{\n  int use_colour = isatty(1);\n\n  int opt;\n  while((opt = getopt(argc, argv, \"c\")) != -1) {\n    switch(opt) {\n      case 'c': use_colour = 1; break;\n    }\n  }\n\n  const char *file = argv[optind++];\n\n  int fd;\n  if(!file || streq(file, \"-\"))\n    fd = 0; // stdin\n  else {\n    fd = open(file, O_RDONLY);\n    if(fd == -1) {\n      fprintf(stderr, \"Cannot open %s - %s\\n\", file, strerror(errno));\n      exit(1);\n    }\n  }\n\n  if(use_colour) {\n    special_begin = \"\\x1b[7m{\";\n    special_end   = \"}\\x1b[m\";\n  }\n\n  /* Size matters not for the parser */\n  VTerm *vt = vterm_new(25, 80);\n  vterm_set_utf8(vt, 1);\n  vterm_parser_set_callbacks(vt, &parser_cbs, NULL);\n  vterm_parser_set_emit_nul(vt, true);\n\n  int len;\n  char buffer[1024];\n  while((len = read(fd, buffer, sizeof(buffer))) > 0) {\n    vterm_input_write(vt, buffer, len);\n  }\n\n  printf(\"\\n\");\n\n  close(fd);\n  vterm_free(vt);\n\n  return 0;\n}\n"
  },
  {
    "path": "doc/URLs",
    "content": "ECMA-48:\n  http://www.ecma-international.org/publications/standards/Ecma-048.htm\n\nXterm Control Sequences:\n  http://invisible-island.net/xterm/ctlseqs/ctlseqs.html\n\nDigital VT100 User Guide:\n  http://vt100.net/docs/vt100-ug/\n\nDigital VT220 Programmer Reference Manual\n  http://vt100.net/docs/vt220-rm/\n\nSummary of ANSI standards for ASCII terminals\n  http://www.inwap.com/pdp10/ansicode.txt\n"
  },
  {
    "path": "doc/seqs.txt",
    "content": "Sequences documented in parens are implicit ones from parser.c, which move\nbetween states.\n\n1 = VT100\n2 = VT220\n3 = VT320\nx = xterm\n\n    C0 controls\n\n123    0x00             = NUL\n123x   0x07             = BEL\n123x   0x08             = BS\n123x   0x09             = HT\n123x   0x0A             = LF\n123x   0x0B             = VT\n123x   0x0C             = FF\n123x   0x0D             = CR\n123x   0x0E             = LS1\n123x   0x0F             = LS0\n      (0x18             = CAN)\n      (0x1A             = SUB)\n      (0x1B             = ESC)\n\n123    0x7f             = DEL (ignored)\n\n    C1 controls\n\n123x   0x84             = IND\n123x   0x85             = NEL\n123x   0x88             = HTS\n123x   0x8D             = RI\n 23x   0x8E             = SS2\n 23x   0x8F             = SS3\n      (0x90             = DCS)\n      (0x98             = SOS)\n      (0x9B             = CSI)\n      (0x9C             = ST)\n      (0x9D             = OSC)\n      (0x9E             = PM)\n      (0x9F             = APC)\n\n    Escape sequences\n     - excluding sequences that are C1 aliases\n\n123x   ESC (            = SCS, select character set G0\n123x   ESC )            = SCS, select character set G1\n 23x   ESC *            = SCS, select character set G2\n 23x   ESC +            = SCS, select character set G3\n123x   ESC 7            = DECSC - save cursor\n123x   ESC 8            = DECRC - restore cursor\n123x   ESC # 3          = DECDHL, double-height line (top half)\n123x   ESC # 4          = DECDHL, double-height line (bottom half)\n123x   ESC # 5          = DECSWL, single-width single-height line\n123x   ESC # 6          = DECDWL, double-width single-height line\n123x   ESC # 8          = DECALN\n123    ESC <            = Ignored (used by VT100 to exit VT52 mode)\n123x   ESC =            = DECKPAM, keypad application mode\n123x   ESC >            = DECKPNM, keypad numeric mode\n 23x   ESC Sp F         = S7C1T\n 23x   ESC Sp G         = S8C1T\n      (ESC P            = DCS)\n      (ESC X            = SOS)\n      (ESC [            = CSI)\n      (ESC \\            = ST)\n      (ESC ]            = OSC)\n      (ESC ^            = PM)\n      (ESC _            = APC)\n123x   ESC c            = RIS, reset initial state\n  3x   ESC n            = LS2\n  3x   ESC o            = LS3\n  3x   ESC |            = LS3R\n  3x   ESC }            = LS2R\n  3x   ESC ~            = LS1R\n\n    DCSes\n\n  3x   DCS $ q      ST  = DECRQSS\n  3x           m        =   Request SGR\n   x           Sp q     =   Request DECSCUSR\n  3x           \" q      =   Request DECSCA\n  3x           r        =   Request DECSTBM\n   x           s        =   Request DECSLRM\n\n    CSIs\n 23x   CSI @            = ICH\n123x   CSI A            = CUU\n123x   CSI B            = CUD\n123x   CSI C            = CUF\n123x   CSI D            = CUB\n   x   CSI E            = CNL\n   x   CSI F            = CPL\n   x   CSI G            = CHA\n123x   CSI H            = CUP\n   x   CSI I            = CHT\n123x   CSI J            = ED\n 23x   CSI ? J          = DECSED, selective erase in display\n123x   CSI K            = EL\n 23x   CSI ? K          = DECSEL, selective erase in line\n 23x   CSI L            = IL\n 23x   CSI M            = DL\n 23x   CSI P            = DCH\n   x   CSI S            = SU\n   x   CSI T            = SD\n 23x   CSI X            = ECH\n   x   CSI Z            = CBT\n   x   CSI `            = HPA\n   x   CSI a            = HPR\n   x   CSI b            = REP\n123x   CSI   c          = DA, device attributes\n123        0            =   DA\n 23x   CSI >   c        = DECSDA\n 23          0          =   SDA\n   x   CSI d            = VPA\n   x   CSI e            = VPR\n123x   CSI f            = HVP\n123x   CSI g            = TBC\n123x   CSI h            = SM, Set mode\n123x   CSI ? h          = DECSM, DEC set mode\n       CSI j            = HPB\n       CSI k            = VPB\n123x   CSI l            = RM, Reset mode\n123x   CSI ? l          = DECRM, DEC reset mode\n123x   CSI m            = SGR, Set Graphic Rendition\n       CSI ? m          = DECSGR, private Set Graphic Rendition\n123x   CSI   n          = DSR, Device Status Report\n 23x       5            =   operating status\n 23x       6            =   CPR = cursor position\n 23x   CSI ? n          = DECDSR; behaves as DSR but uses CSI ? instead of CSI to respond\n 23x   CSI ! p          = DECSTR, soft terminal reset\n  3x   CSI ? $ p        = DECRQM, request private mode\n   x   CSI   Sp q       = DECSCUSR (odd numbers blink, even numbers solid)\n           1 or 2       =   block\n           3 or 4       =   underline\n           5 or 6       =   I-beam to left\n   x   CSI > q          = XTVERSION, request version string\n 23x   CSI \" q          = DECSCA, select character attributes\n123x   CSI r            = DECSTBM\n   x   CSI s            = DECSLRM\n   x   CSI ' }          = DECIC\n   x   CSI ' ~          = DECDC\n\n    OSCs\n\n   x   OSC 0;           = Set icon name and title\n   x   OSC 1;           = Set icon name\n   x   OSC 2;           = Set title\n   x   OSC 52;          = Selection management\n\n    Standard modes\n\n 23x   SM 4             = IRM\n123x   SM 20            = NLM, linefeed/newline\n\n    DEC modes\n\n123x   DECSM 1          = DECCKM, cursor keys\n123x   DECSM 5          = DECSCNM, screen\n123x   DECSM 6          = DECOM, origin\n123x   DECSM 7          = DECAWM, autowrap\n   x   DECSM 12         = Cursor blink\n 23x   DECSM 25         = DECTCEM, text cursor enable\n   x   DECSM 69         = DECVSSM, vertical screen split\n   x   DECSM 1000       = Mouse click/release tracking\n   x   DECSM 1002       = Mouse click/release/drag tracking\n   x   DECSM 1003       = Mouse all movements tracking\n   x   DECSM 1004       = Focus in/out reporting\n   x   DECSM 1005       = Mouse protocol extended (UTF-8) - not recommended\n   x   DECSM 1006       = Mouse protocol SGR\n   x   DECSM 1015       = Mouse protocol rxvt\n   x   DECSM 1047       = Altscreen\n   x   DECSM 1048       = Save cursor\n   x   DECSM 1049       = 1047 + 1048\n   x   DECSM 2004       = Bracketed paste\n\n    Graphic Renditions\n\n123x   SGR 0            = Reset\n123x   SGR 1            = Bold on\n   x   SGR 3            = Italic on\n123x   SGR 4            = Underline single\n       SGR 4:x          = Underline style\n123x   SGR 5            = Blink on\n123x   SGR 7            = Reverse on\n   x   SGR 8            = Conceal on\n   x   SGR 9            = Strikethrough on\n       SGR 10-19        = Select font\n   x   SGR 21           = Underline double\n 23x   SGR 22           = Bold off\n   x   SGR 23           = Italic off\n 23x   SGR 24           = Underline off\n 23x   SGR 25           = Blink off\n 23x   SGR 27           = Reverse off\n   x   SGR 28           = Conceal off\n   x   SGR 29           = Strikethrough off\n   x   SGR 30-37        = Foreground ANSI\n   x   SGR 38           = Foreground alternative palette\n   x   SGR 39           = Foreground default\n   x   SGR 40-47        = Background ANSI\n   x   SGR 48           = Background alternative palette\n   x   SGR 49           = Background default\n       SGR 73           = Superscript on\n       SGR 74           = Subscript on\n       SGR 75           = Superscript/subscript off\n   x   SGR 90-97        = Foreground ANSI high-intensity\n   x   SGR 100-107      = Background ANSI high-intensity\n\nThe state storage used by ESC 7 and DECSM 1048/1049 is shared.\n\n    Unimplemented sequences:\n\nThe following sequences are not recognised by libvterm.\n\n123x   0x05             = ENQ\n  3    0x11             = DC1 (XON)\n  3    0x13             = DC3 (XOFF)\n   x   ESC % @          = Select default character set\n   x   ESC % G          = Select UTF-8 character set\n   x   ESC 6            = DECBI, Back Index\n12     ESC Z            = DECID, identify terminal\n   x   DCS + Q          = XTGETXRES, Request resource values\n       DCS $ q          = [DECRQSS]\n  3x           \" p      =   Request DECSCL\n   x           t        =   Request DECSLPP\n   x           $ |      =   Request DECSCPP\n   x           * |      =   Request DECSLNS\n  3            $ }      =   Request DECSASD\n  3            $ ~      =   Request DECSSDT\n   x   DCS + p          = XTSETTCAP, set termcap/terminfo data\n   x   DCS + q          = XTGETTCAP, request termcap/terminfo\n 23    DCS {            = DECDLD, down-line-loadable character set\n 23x   DCS |            = DECUDK, user-defined key\n   x   CSI Sp @         = Shift left columns\n   x   CSI Sp A         = Shift right columns\n   x   CSI # P          = XTPUSHCOLORS, push current dynamic colours to stack\n   x   CSI # Q          = XTPOPCOLORS, pop dynamic colours from stack\n   x   CSI # R          = XTREPORTCOLORS, report current entry on palette stack\n   x   CSI ? S          = XTSMGRAPHICS, set/request graphics attribute\n   x   CSI > T          = XTRMTITLE, reset title mode features\n 23x   CSI i            = DEC printer control\n   x   CSI > m          = XTMODKEYS, set key modifier options\n   x   CSI > n          = (XTMODKEYS), reset key modifier options\n   x   CSI $ p          = DECRQM, request ANSI mode\n 23x   CSI \" p          = DECSCL, set compatibility level\n   x   CSI > p          = XTSMPOINTER, set resource value pointer mode\n1  x   CSI q            = DECLL, load LEDs\n   x   CSI ? r          = XTRESTORE, restore DEC private mode values\n   x   CSI $ r          = DECCARA, change attributes in rectangular area\n   x   CSI > s          = XTSHIFTESCAPE, set/reset shift-escape options\n   x   CSI ? s          = XTSAVE, save DEC private mode values\n   x   CSI t            = XTWINOPS, window operations\n   x   CSI > t          = XTSMTITLE, set title mode features\n   x   CSI $ t          = DECRARA, reset attributes in rectangular area\n  3    CSI   $ u        = DECRQTSR, request terminal state report\n  3        1            =   terminal state report\n  3    CSI & u          = DECRQUPSS, request user-preferred supplemental set\n   x   CSI $ v          = DECCRA, copy rectangular area\n  3x   CSI   $ w        = DECRQPSR, request presentation state report\n  3x       1            =   cursor information report\n  3x       2            =   tab stop report\n   x   CSI ' w          = DECEFR, enable filter rectangle\n1  x   CSI x            = DECREQTPARM, request terminal parameters\n   x   CSI * x          = DECSACE, select attribute change extent\n   x   CSI $ x          = DECFRA, fill rectangular area\n123    CSI y            = DECTST, invoke confidence test\n   x   CSI $ z          = DECERA, erase rectangular area\n   x   CSI # {          = XTPUSHSGR, push video attributes onto stack\n   x   CSI $ {          = DECSERA, selective erase in rectangular area\n   x   CSI # |          = XTREPORTSGR, report selected graphic rendition\n   x   CSI $ |          = DECSCPP, select columns per page\n   x   CSI # }          = XTPOPSGR, pop video attributes from stack\n  3    CSI $ }          = DECSASD, select active status display\n  3    CSI $ ~          = DECSSDT, select status line type\n 23    SM 2             = KAM, keyboard action\n123    SM 12            = SRM, send/receive\n123    DECSM 2          = DECANM, ANSI/VT52\n123    DECSM 3          = DECCOLM, 132 column\n123    DECSM 4          = DECSCLM, scrolling\n123    DECSM 8          = DECARM, auto-repeat\n12     DECSM 9          = DECINLM, interlace\n 23    DECSM 18         = DECPFF, print form feed\n 23    DECSM 19         = DECPEX, print extent\n 23    DECSM 42         = DECNRCM, national/multinational character\n"
  },
  {
    "path": "find-wide-chars.pl",
    "content": "#!/usr/bin/perl\n\nuse strict;\nuse warnings;\n\nSTDOUT->autoflush(1);\n\nsub iswide\n{\n   my ( $cp ) = @_;\n   return chr($cp) =~ m/\\p{East_Asian_Width=Wide}|\\p{East_Asian_Width=Fullwidth}/;\n}\n\nmy ( $start, $end );\nforeach my $cp ( 0 .. 0x1FFFF ) {\n   iswide($cp) or next;\n\n   if( defined $end and $end == $cp-1 ) {\n      # extend the range\n      $end = $cp;\n      next;\n   }\n\n   # start a new range\n   printf \"  { %#04x, %#04x },\\n\", $start, $end if defined $start;\n\n   $start = $end = $cp;\n}\n\nprintf \"  { %#04x, %#04x },\\n\", $start, $end if defined $start;\n"
  },
  {
    "path": "include/vterm.h",
    "content": "#ifndef __VTERM_H__\n#define __VTERM_H__\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n#include <stdint.h>\n#include <stdlib.h>\n#include <stdbool.h>\n\n#include \"vterm_keycodes.h\"\n\n#define VTERM_VERSION_MAJOR 0\n#define VTERM_VERSION_MINOR 3\n#define VTERM_VERSION_PATCH 3\n\n#define VTERM_CHECK_VERSION \\\n        vterm_check_version(VTERM_VERSION_MAJOR, VTERM_VERSION_MINOR)\n\n/* Any cell can contain at most one basic printing character and 5 combining\n * characters. This number could be changed but will be ABI-incompatible if\n * you do */\n#define VTERM_MAX_CHARS_PER_CELL 6\n\ntypedef struct VTerm VTerm;\ntypedef struct VTermState VTermState;\ntypedef struct VTermScreen VTermScreen;\n\ntypedef struct {\n  int row;\n  int col;\n} VTermPos;\n\n/* some small utility functions; we can just keep these static here */\n\n/* order points by on-screen flow order */\nstatic inline int vterm_pos_cmp(VTermPos a, VTermPos b)\n{\n  return (a.row == b.row) ? a.col - b.col : a.row - b.row;\n}\n\ntypedef struct {\n  int start_row;\n  int end_row;\n  int start_col;\n  int end_col;\n} VTermRect;\n\n/* true if the rect contains the point */\nstatic inline int vterm_rect_contains(VTermRect r, VTermPos p)\n{\n  return p.row >= r.start_row && p.row < r.end_row &&\n         p.col >= r.start_col && p.col < r.end_col;\n}\n\n/* move a rect */\nstatic inline void vterm_rect_move(VTermRect *rect, int row_delta, int col_delta)\n{\n  rect->start_row += row_delta; rect->end_row += row_delta;\n  rect->start_col += col_delta; rect->end_col += col_delta;\n}\n\n/**\n * Bit-field describing the content of the tagged union `VTermColor`.\n */\ntypedef enum {\n  /**\n   * If the lower bit of `type` is not set, the colour is 24-bit RGB.\n   */\n  VTERM_COLOR_RGB = 0x00,\n\n  /**\n   * The colour is an index into a palette of 256 colours.\n   */\n  VTERM_COLOR_INDEXED = 0x01,\n\n  /**\n   * Mask that can be used to extract the RGB/Indexed bit.\n   */\n  VTERM_COLOR_TYPE_MASK = 0x01,\n\n  /**\n   * If set, indicates that this colour should be the default foreground\n   * color, i.e. there was no SGR request for another colour. When\n   * rendering this colour it is possible to ignore \"idx\" and just use a\n   * colour that is not in the palette.\n   */\n  VTERM_COLOR_DEFAULT_FG = 0x02,\n\n  /**\n   * If set, indicates that this colour should be the default background\n   * color, i.e. there was no SGR request for another colour. A common\n   * option when rendering this colour is to not render a background at\n   * all, for example by rendering the window transparently at this spot.\n   */\n  VTERM_COLOR_DEFAULT_BG = 0x04,\n\n  /**\n   * Mask that can be used to extract the default foreground/background bit.\n   */\n  VTERM_COLOR_DEFAULT_MASK = 0x06\n} VTermColorType;\n\n/**\n * Returns true if the VTERM_COLOR_RGB `type` flag is set, indicating that the\n * given VTermColor instance is an indexed colour.\n */\n#define VTERM_COLOR_IS_INDEXED(col) \\\n  (((col)->type & VTERM_COLOR_TYPE_MASK) == VTERM_COLOR_INDEXED)\n\n/**\n * Returns true if the VTERM_COLOR_INDEXED `type` flag is set, indicating that\n * the given VTermColor instance is an rgb colour.\n */\n#define VTERM_COLOR_IS_RGB(col) \\\n  (((col)->type & VTERM_COLOR_TYPE_MASK) == VTERM_COLOR_RGB)\n\n/**\n * Returns true if the VTERM_COLOR_DEFAULT_FG `type` flag is set, indicating\n * that the given VTermColor instance corresponds to the default foreground\n * color.\n */\n#define VTERM_COLOR_IS_DEFAULT_FG(col) \\\n  (!!((col)->type & VTERM_COLOR_DEFAULT_FG))\n\n/**\n * Returns true if the VTERM_COLOR_DEFAULT_BG `type` flag is set, indicating\n * that the given VTermColor instance corresponds to the default background\n * color.\n */\n#define VTERM_COLOR_IS_DEFAULT_BG(col) \\\n  (!!((col)->type & VTERM_COLOR_DEFAULT_BG))\n\n/**\n * Tagged union storing either an RGB color or an index into a colour palette.\n * In order to convert indexed colours to RGB, you may use the\n * vterm_state_convert_color_to_rgb() or vterm_screen_convert_color_to_rgb()\n * functions which lookup the RGB colour from the palette maintained by a\n * VTermState or VTermScreen instance.\n */\ntypedef union {\n  /**\n   * Tag indicating which union member is actually valid. This variable\n   * coincides with the `type` member of the `rgb` and the `indexed` struct\n   * in memory. Please use the `VTERM_COLOR_IS_*` test macros to check whether\n   * a particular type flag is set.\n   */\n  uint8_t type;\n\n  /**\n   * Valid if `VTERM_COLOR_IS_RGB(type)` is true. Holds the RGB colour values.\n   */\n  struct {\n    /**\n     * Same as the top-level `type` member stored in VTermColor.\n     */\n    uint8_t type;\n\n    /**\n     * The actual 8-bit red, green, blue colour values.\n     */\n    uint8_t red, green, blue;\n  } rgb;\n\n  /**\n   * If `VTERM_COLOR_IS_INDEXED(type)` is true, this member holds the index into\n   * the colour palette.\n   */\n  struct {\n    /**\n     * Same as the top-level `type` member stored in VTermColor.\n     */\n    uint8_t type;\n\n    /**\n     * Index into the colour map.\n     */\n    uint8_t idx;\n  } indexed;\n} VTermColor;\n\n/**\n * Constructs a new VTermColor instance representing the given RGB values.\n */\nstatic inline void vterm_color_rgb(VTermColor *col, uint8_t red, uint8_t green,\n                                   uint8_t blue)\n{\n  col->type = VTERM_COLOR_RGB;\n  col->rgb.red   = red;\n  col->rgb.green = green;\n  col->rgb.blue  = blue;\n}\n\n/**\n * Construct a new VTermColor instance representing an indexed color with the\n * given index.\n */\nstatic inline void vterm_color_indexed(VTermColor *col, uint8_t idx)\n{\n  col->type = VTERM_COLOR_INDEXED;\n  col->indexed.idx = idx;\n}\n\n/**\n * Compares two colours. Returns true if the colors are equal, false otherwise.\n */\nint vterm_color_is_equal(const VTermColor *a, const VTermColor *b);\n\ntypedef enum {\n  /* VTERM_VALUETYPE_NONE = 0 */\n  VTERM_VALUETYPE_BOOL = 1,\n  VTERM_VALUETYPE_INT,\n  VTERM_VALUETYPE_STRING,\n  VTERM_VALUETYPE_COLOR,\n\n  VTERM_N_VALUETYPES\n} VTermValueType;\n\ntypedef struct {\n  const char *str;\n  size_t      len : 30;\n  bool        initial : 1;\n  bool        final : 1;\n} VTermStringFragment;\n\ntypedef union {\n  int boolean;\n  int number;\n  VTermStringFragment string;\n  VTermColor color;\n} VTermValue;\n\ntypedef enum {\n  /* VTERM_ATTR_NONE = 0 */\n  VTERM_ATTR_BOLD = 1,   // bool:   1, 22\n  VTERM_ATTR_UNDERLINE,  // number: 4, 21, 24\n  VTERM_ATTR_ITALIC,     // bool:   3, 23\n  VTERM_ATTR_BLINK,      // bool:   5, 25\n  VTERM_ATTR_REVERSE,    // bool:   7, 27\n  VTERM_ATTR_CONCEAL,    // bool:   8, 28\n  VTERM_ATTR_STRIKE,     // bool:   9, 29\n  VTERM_ATTR_FONT,       // number: 10-19\n  VTERM_ATTR_FOREGROUND, // color:  30-39 90-97\n  VTERM_ATTR_BACKGROUND, // color:  40-49 100-107\n  VTERM_ATTR_SMALL,      // bool:   73, 74, 75\n  VTERM_ATTR_BASELINE,   // number: 73, 74, 75\n\n  VTERM_N_ATTRS\n} VTermAttr;\n\ntypedef enum {\n  /* VTERM_PROP_NONE = 0 */\n  VTERM_PROP_CURSORVISIBLE = 1, // bool\n  VTERM_PROP_CURSORBLINK,       // bool\n  VTERM_PROP_ALTSCREEN,         // bool\n  VTERM_PROP_TITLE,             // string\n  VTERM_PROP_ICONNAME,          // string\n  VTERM_PROP_REVERSE,           // bool\n  VTERM_PROP_CURSORSHAPE,       // number\n  VTERM_PROP_MOUSE,             // number\n  VTERM_PROP_FOCUSREPORT,       // bool\n\n  VTERM_N_PROPS\n} VTermProp;\n\nenum {\n  VTERM_PROP_CURSORSHAPE_BLOCK = 1,\n  VTERM_PROP_CURSORSHAPE_UNDERLINE,\n  VTERM_PROP_CURSORSHAPE_BAR_LEFT,\n\n  VTERM_N_PROP_CURSORSHAPES\n};\n\nenum {\n  VTERM_PROP_MOUSE_NONE = 0,\n  VTERM_PROP_MOUSE_CLICK,\n  VTERM_PROP_MOUSE_DRAG,\n  VTERM_PROP_MOUSE_MOVE,\n\n  VTERM_N_PROP_MOUSES\n};\n\ntypedef enum {\n  VTERM_SELECTION_CLIPBOARD = (1<<0),\n  VTERM_SELECTION_PRIMARY   = (1<<1),\n  VTERM_SELECTION_SECONDARY = (1<<2),\n  VTERM_SELECTION_SELECT    = (1<<3),\n  VTERM_SELECTION_CUT0      = (1<<4), /* also CUT1 .. CUT7 by bitshifting */\n} VTermSelectionMask;\n\ntypedef struct {\n  const uint32_t *chars;\n  int             width;\n  unsigned int    protected_cell:1;  /* DECSCA-protected against DECSEL/DECSED */\n  unsigned int    dwl:1;             /* DECDWL or DECDHL double-width line */\n  unsigned int    dhl:2;             /* DECDHL double-height line (1=top 2=bottom) */\n} VTermGlyphInfo;\n\ntypedef struct {\n  unsigned int    doublewidth:1;     /* DECDWL or DECDHL line */\n  unsigned int    doubleheight:2;    /* DECDHL line (1=top 2=bottom) */\n  unsigned int    continuation:1;    /* Line is a flow continuation of the previous */\n} VTermLineInfo;\n\n/* Copies of VTermState fields that the 'resize' callback might have reason to\n * edit. 'resize' callback gets total control of these fields and may\n * free-and-reallocate them if required. They will be copied back from the\n * struct after the callback has returned.\n */\ntypedef struct {\n  VTermPos pos;                /* current cursor position */\n  VTermLineInfo *lineinfos[2]; /* [1] may be NULL */\n} VTermStateFields;\n\ntypedef struct {\n  /* libvterm relies on this memory to be zeroed out before it is returned\n   * by the allocator. */\n  void *(*malloc)(size_t size, void *allocdata);\n  void  (*free)(void *ptr, void *allocdata);\n} VTermAllocatorFunctions;\n\nvoid vterm_check_version(int major, int minor);\n\nstruct VTermBuilder {\n  int ver; /* currently unused but reserved for some sort of ABI version flag */\n\n  int rows, cols;\n\n  const VTermAllocatorFunctions *allocator;\n  void *allocdata;\n\n  /* Override default sizes for various structures */\n  size_t outbuffer_len;  /* default: 4096 */\n  size_t tmpbuffer_len;  /* default: 4096 */\n};\n\nVTerm *vterm_build(const struct VTermBuilder *builder);\n\n/* A convenient shortcut for default cases */\nVTerm *vterm_new(int rows, int cols);\n/* This shortcuts are generally discouraged in favour of just using vterm_build() */\nVTerm *vterm_new_with_allocator(int rows, int cols, VTermAllocatorFunctions *funcs, void *allocdata);\n\nvoid   vterm_free(VTerm* vt);\n\nvoid vterm_get_size(const VTerm *vt, int *rowsp, int *colsp);\nvoid vterm_set_size(VTerm *vt, int rows, int cols);\n\nint  vterm_get_utf8(const VTerm *vt);\nvoid vterm_set_utf8(VTerm *vt, int is_utf8);\n\nsize_t vterm_input_write(VTerm *vt, const char *bytes, size_t len);\n\n/* Setting output callback will override the buffer logic */\ntypedef void VTermOutputCallback(const char *s, size_t len, void *user);\nvoid vterm_output_set_callback(VTerm *vt, VTermOutputCallback *func, void *user);\n\n/* These buffer functions only work if output callback is NOT set\n * These are deprecated and will be removed in a later version */\nsize_t vterm_output_get_buffer_size(const VTerm *vt);\nsize_t vterm_output_get_buffer_current(const VTerm *vt);\nsize_t vterm_output_get_buffer_remaining(const VTerm *vt);\n\n/* This too */\nsize_t vterm_output_read(VTerm *vt, char *buffer, size_t len);\n\nvoid vterm_keyboard_unichar(VTerm *vt, uint32_t c, VTermModifier mod);\nvoid vterm_keyboard_key(VTerm *vt, VTermKey key, VTermModifier mod);\n\nvoid vterm_keyboard_start_paste(VTerm *vt);\nvoid vterm_keyboard_end_paste(VTerm *vt);\n\nvoid vterm_mouse_move(VTerm *vt, int row, int col, VTermModifier mod);\nvoid vterm_mouse_button(VTerm *vt, int button, bool pressed, VTermModifier mod);\n\n// ------------\n// Parser layer\n// ------------\n\n/* Flag to indicate non-final subparameters in a single CSI parameter.\n * Consider\n *   CSI 1;2:3:4;5a\n * 1 4 and 5 are final.\n * 2 and 3 are non-final and will have this bit set\n *\n * Don't confuse this with the final byte of the CSI escape; 'a' in this case.\n */\n#define CSI_ARG_FLAG_MORE (1U<<31)\n#define CSI_ARG_MASK      (~(1U<<31))\n\n#define CSI_ARG_HAS_MORE(a) ((a) & CSI_ARG_FLAG_MORE)\n#define CSI_ARG(a)          ((a) & CSI_ARG_MASK)\n\n/* Can't use -1 to indicate a missing argument; use this instead */\n#define CSI_ARG_MISSING ((1UL<<31)-1)\n\n#define CSI_ARG_IS_MISSING(a) (CSI_ARG(a) == CSI_ARG_MISSING)\n#define CSI_ARG_OR(a,def)     (CSI_ARG(a) == CSI_ARG_MISSING ? (def) : CSI_ARG(a))\n#define CSI_ARG_COUNT(a)      (CSI_ARG(a) == CSI_ARG_MISSING || CSI_ARG(a) == 0 ? 1 : CSI_ARG(a))\n\ntypedef struct {\n  int (*text)(const char *bytes, size_t len, void *user);\n  int (*control)(unsigned char control, void *user);\n  int (*escape)(const char *bytes, size_t len, void *user);\n  int (*csi)(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user);\n  int (*osc)(int command, VTermStringFragment frag, void *user);\n  int (*dcs)(const char *command, size_t commandlen, VTermStringFragment frag, void *user);\n  int (*apc)(VTermStringFragment frag, void *user);\n  int (*pm)(VTermStringFragment frag, void *user);\n  int (*sos)(VTermStringFragment frag, void *user);\n  int (*resize)(int rows, int cols, void *user);\n} VTermParserCallbacks;\n\nvoid  vterm_parser_set_callbacks(VTerm *vt, const VTermParserCallbacks *callbacks, void *user);\nvoid *vterm_parser_get_cbdata(VTerm *vt);\n\n/* Normally NUL, CAN, SUB and DEL are ignored. Setting this true causes them\n * to be emitted by the 'control' callback\n */\nvoid vterm_parser_set_emit_nul(VTerm *vt, bool emit);\n\n// -----------\n// State layer\n// -----------\n\ntypedef struct {\n  int (*putglyph)(VTermGlyphInfo *info, VTermPos pos, void *user);\n  int (*movecursor)(VTermPos pos, VTermPos oldpos, int visible, void *user);\n  int (*scrollrect)(VTermRect rect, int downward, int rightward, void *user);\n  int (*moverect)(VTermRect dest, VTermRect src, void *user);\n  int (*erase)(VTermRect rect, int selective, void *user);\n  int (*initpen)(void *user);\n  int (*setpenattr)(VTermAttr attr, VTermValue *val, void *user);\n  int (*settermprop)(VTermProp prop, VTermValue *val, void *user);\n  int (*bell)(void *user);\n  int (*resize)(int rows, int cols, VTermStateFields *fields, void *user);\n  int (*setlineinfo)(int row, const VTermLineInfo *newinfo, const VTermLineInfo *oldinfo, void *user);\n  int (*sb_clear)(void *user);\n  // ABI-compat only enabled if vterm_state_callbacks_has_premove() is invoked\n  int (*premove)(VTermRect dest, void *user);\n} VTermStateCallbacks;\n\ntypedef struct {\n  int (*control)(unsigned char control, void *user);\n  int (*csi)(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user);\n  int (*osc)(int command, VTermStringFragment frag, void *user);\n  int (*dcs)(const char *command, size_t commandlen, VTermStringFragment frag, void *user);\n  int (*apc)(VTermStringFragment frag, void *user);\n  int (*pm)(VTermStringFragment frag, void *user);\n  int (*sos)(VTermStringFragment frag, void *user);\n} VTermStateFallbacks;\n\ntypedef struct {\n  int (*set)(VTermSelectionMask mask, VTermStringFragment frag, void *user);\n  int (*query)(VTermSelectionMask mask, void *user);\n} VTermSelectionCallbacks;\n\nVTermState *vterm_obtain_state(VTerm *vt);\n\nvoid  vterm_state_set_callbacks(VTermState *state, const VTermStateCallbacks *callbacks, void *user);\nvoid *vterm_state_get_cbdata(VTermState *state);\n\nvoid vterm_state_callbacks_has_premove(VTermState *state);\n\nvoid  vterm_state_set_unrecognised_fallbacks(VTermState *state, const VTermStateFallbacks *fallbacks, void *user);\nvoid *vterm_state_get_unrecognised_fbdata(VTermState *state);\n\nvoid vterm_state_reset(VTermState *state, int hard);\nvoid vterm_state_get_cursorpos(const VTermState *state, VTermPos *cursorpos);\nvoid vterm_state_get_default_colors(const VTermState *state, VTermColor *default_fg, VTermColor *default_bg);\nvoid vterm_state_get_palette_color(const VTermState *state, int index, VTermColor *col);\nvoid vterm_state_set_default_colors(VTermState *state, const VTermColor *default_fg, const VTermColor *default_bg);\nvoid vterm_state_set_palette_color(VTermState *state, int index, const VTermColor *col);\nvoid vterm_state_set_bold_highbright(VTermState *state, int bold_is_highbright);\nint  vterm_state_get_penattr(const VTermState *state, VTermAttr attr, VTermValue *val);\nint  vterm_state_set_termprop(VTermState *state, VTermProp prop, VTermValue *val);\nvoid vterm_state_focus_in(VTermState *state);\nvoid vterm_state_focus_out(VTermState *state);\nconst VTermLineInfo *vterm_state_get_lineinfo(const VTermState *state, int row);\n\n/**\n * Makes sure that the given color `col` is indeed an RGB colour. After this\n * function returns, VTERM_COLOR_IS_RGB(col) will return true, while all other\n * flags stored in `col->type` will have been reset.\n *\n * @param state is the VTermState instance from which the colour palette should\n * be extracted.\n * @param col is a pointer at the VTermColor instance that should be converted\n * to an RGB colour.\n */\nvoid vterm_state_convert_color_to_rgb(const VTermState *state, VTermColor *col);\n\nvoid vterm_state_set_selection_callbacks(VTermState *state, const VTermSelectionCallbacks *callbacks, void *user,\n    char *buffer, size_t buflen);\n\nvoid vterm_state_send_selection(VTermState *state, VTermSelectionMask mask, VTermStringFragment frag);\n\n// ------------\n// Screen layer\n// ------------\n\ntypedef struct {\n    unsigned int bold      : 1;\n    unsigned int underline : 2;\n    unsigned int italic    : 1;\n    unsigned int blink     : 1;\n    unsigned int reverse   : 1;\n    unsigned int conceal   : 1;\n    unsigned int strike    : 1;\n    unsigned int font      : 4; /* 0 to 9 */\n    unsigned int dwl       : 1; /* On a DECDWL or DECDHL line */\n    unsigned int dhl       : 2; /* On a DECDHL line (1=top 2=bottom) */\n    unsigned int small     : 1;\n    unsigned int baseline  : 2;\n} VTermScreenCellAttrs;\n\nenum {\n  VTERM_UNDERLINE_OFF,\n  VTERM_UNDERLINE_SINGLE,\n  VTERM_UNDERLINE_DOUBLE,\n  VTERM_UNDERLINE_CURLY,\n};\n\nenum {\n  VTERM_BASELINE_NORMAL,\n  VTERM_BASELINE_RAISE,\n  VTERM_BASELINE_LOWER,\n};\n\ntypedef struct {\n  uint32_t chars[VTERM_MAX_CHARS_PER_CELL];\n  char     width;\n  VTermScreenCellAttrs attrs;\n  VTermColor fg, bg;\n} VTermScreenCell;\n\ntypedef struct {\n  int (*damage)(VTermRect rect, void *user);\n  int (*moverect)(VTermRect dest, VTermRect src, void *user);\n  int (*movecursor)(VTermPos pos, VTermPos oldpos, int visible, void *user);\n  int (*settermprop)(VTermProp prop, VTermValue *val, void *user);\n  int (*bell)(void *user);\n  int (*resize)(int rows, int cols, void *user);\n  int (*sb_pushline)(int cols, const VTermScreenCell *cells, void *user);\n  int (*sb_popline)(int cols, VTermScreenCell *cells, void *user);\n  int (*sb_clear)(void* user);\n  /* ABI-compat this is only used if vterm_screen_callbacks_has_pushline4() is called */\n  int (*sb_pushline4)(int cols, const VTermScreenCell *cells, bool continuation, void *user);\n} VTermScreenCallbacks;\n\nVTermScreen *vterm_obtain_screen(VTerm *vt);\n\nvoid  vterm_screen_set_callbacks(VTermScreen *screen, const VTermScreenCallbacks *callbacks, void *user);\nvoid *vterm_screen_get_cbdata(VTermScreen *screen);\n\nvoid vterm_screen_callbacks_has_pushline4(VTermScreen *screen);\n\nvoid  vterm_screen_set_unrecognised_fallbacks(VTermScreen *screen, const VTermStateFallbacks *fallbacks, void *user);\nvoid *vterm_screen_get_unrecognised_fbdata(VTermScreen *screen);\n\nvoid vterm_screen_enable_reflow(VTermScreen *screen, bool reflow);\n\n// Back-compat alias for the brief time it was in 0.3-RC1\n#define vterm_screen_set_reflow  vterm_screen_enable_reflow\n\nvoid vterm_screen_enable_altscreen(VTermScreen *screen, int altscreen);\n\ntypedef enum {\n  VTERM_DAMAGE_CELL,    /* every cell */\n  VTERM_DAMAGE_ROW,     /* entire rows */\n  VTERM_DAMAGE_SCREEN,  /* entire screen */\n  VTERM_DAMAGE_SCROLL,  /* entire screen + scrollrect */\n\n  VTERM_N_DAMAGES\n} VTermDamageSize;\n\nvoid vterm_screen_flush_damage(VTermScreen *screen);\nvoid vterm_screen_set_damage_merge(VTermScreen *screen, VTermDamageSize size);\n\nvoid   vterm_screen_reset(VTermScreen *screen, int hard);\n\n/* Neither of these functions NUL-terminate the buffer */\nsize_t vterm_screen_get_chars(const VTermScreen *screen, uint32_t *chars, size_t len, const VTermRect rect);\nsize_t vterm_screen_get_text(const VTermScreen *screen, char *str, size_t len, const VTermRect rect);\n\ntypedef enum {\n  VTERM_ATTR_BOLD_MASK       = 1 << 0,\n  VTERM_ATTR_UNDERLINE_MASK  = 1 << 1,\n  VTERM_ATTR_ITALIC_MASK     = 1 << 2,\n  VTERM_ATTR_BLINK_MASK      = 1 << 3,\n  VTERM_ATTR_REVERSE_MASK    = 1 << 4,\n  VTERM_ATTR_STRIKE_MASK     = 1 << 5,\n  VTERM_ATTR_FONT_MASK       = 1 << 6,\n  VTERM_ATTR_FOREGROUND_MASK = 1 << 7,\n  VTERM_ATTR_BACKGROUND_MASK = 1 << 8,\n  VTERM_ATTR_CONCEAL_MASK    = 1 << 9,\n  VTERM_ATTR_SMALL_MASK      = 1 << 10,\n  VTERM_ATTR_BASELINE_MASK   = 1 << 11,\n\n  VTERM_ALL_ATTRS_MASK = (1 << 12) - 1\n} VTermAttrMask;\n\nint vterm_screen_get_attrs_extent(const VTermScreen *screen, VTermRect *extent, VTermPos pos, VTermAttrMask attrs);\n\nint vterm_screen_get_cell(const VTermScreen *screen, VTermPos pos, VTermScreenCell *cell);\n\nint vterm_screen_is_eol(const VTermScreen *screen, VTermPos pos);\n\n/**\n * Same as vterm_state_convert_color_to_rgb(), but takes a `screen` instead of a `state`\n * instance.\n */\nvoid vterm_screen_convert_color_to_rgb(const VTermScreen *screen, VTermColor *col);\n\n/**\n * Similar to vterm_state_set_default_colors(), but also resets colours in the\n * screen buffer(s)\n */\nvoid vterm_screen_set_default_colors(VTermScreen *screen, const VTermColor *default_fg, const VTermColor *default_bg);\n\n// ---------\n// Utilities\n// ---------\n\nVTermValueType vterm_get_attr_type(VTermAttr attr);\nVTermValueType vterm_get_prop_type(VTermProp prop);\n\nvoid vterm_scroll_rect(VTermRect rect,\n                       int downward,\n                       int rightward,\n                       int (*moverect)(VTermRect src, VTermRect dest, void *user),\n                       int (*eraserect)(VTermRect rect, int selective, void *user),\n                       void *user);\n\nvoid vterm_copy_cells(VTermRect dest,\n                      VTermRect src,\n                      void (*copycell)(VTermPos dest, VTermPos src, void *user),\n                      void *user);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif\n"
  },
  {
    "path": "include/vterm_keycodes.h",
    "content": "#ifndef __VTERM_INPUT_H__\n#define __VTERM_INPUT_H__\n\ntypedef enum {\n  VTERM_MOD_NONE  = 0x00,\n  VTERM_MOD_SHIFT = 0x01,\n  VTERM_MOD_ALT   = 0x02,\n  VTERM_MOD_CTRL  = 0x04,\n\n  VTERM_ALL_MODS_MASK = 0x07 \n} VTermModifier;\n\ntypedef enum {\n  VTERM_KEY_NONE,\n\n  VTERM_KEY_ENTER,\n  VTERM_KEY_TAB,\n  VTERM_KEY_BACKSPACE,\n  VTERM_KEY_ESCAPE,\n\n  VTERM_KEY_UP,\n  VTERM_KEY_DOWN,\n  VTERM_KEY_LEFT,\n  VTERM_KEY_RIGHT,\n\n  VTERM_KEY_INS,\n  VTERM_KEY_DEL,\n  VTERM_KEY_HOME,\n  VTERM_KEY_END,\n  VTERM_KEY_PAGEUP,\n  VTERM_KEY_PAGEDOWN,\n\n  VTERM_KEY_FUNCTION_0   = 256,\n  VTERM_KEY_FUNCTION_MAX = VTERM_KEY_FUNCTION_0 + 255,\n\n  VTERM_KEY_KP_0,\n  VTERM_KEY_KP_1,\n  VTERM_KEY_KP_2,\n  VTERM_KEY_KP_3,\n  VTERM_KEY_KP_4,\n  VTERM_KEY_KP_5,\n  VTERM_KEY_KP_6,\n  VTERM_KEY_KP_7,\n  VTERM_KEY_KP_8,\n  VTERM_KEY_KP_9,\n  VTERM_KEY_KP_MULT,\n  VTERM_KEY_KP_PLUS,\n  VTERM_KEY_KP_COMMA,\n  VTERM_KEY_KP_MINUS,\n  VTERM_KEY_KP_PERIOD,\n  VTERM_KEY_KP_DIVIDE,\n  VTERM_KEY_KP_ENTER,\n  VTERM_KEY_KP_EQUAL,\n\n  VTERM_KEY_MAX, // Must be last\n  VTERM_N_KEYS = VTERM_KEY_MAX\n} VTermKey;\n\n#define VTERM_KEY_FUNCTION(n) (VTERM_KEY_FUNCTION_0+(n))\n\n#endif\n"
  },
  {
    "path": "src/encoding/DECdrawing.inc",
    "content": "static const struct StaticTableEncoding encoding_DECdrawing = {\n  { .decode = &decode_table },\n  {\n    [0x60] = 0x25C6,\n    [0x61] = 0x2592,\n    [0x62] = 0x2409,\n    [0x63] = 0x240C,\n    [0x64] = 0x240D,\n    [0x65] = 0x240A,\n    [0x66] = 0x00B0,\n    [0x67] = 0x00B1,\n    [0x68] = 0x2424,\n    [0x69] = 0x240B,\n    [0x6a] = 0x2518,\n    [0x6b] = 0x2510,\n    [0x6c] = 0x250C,\n    [0x6d] = 0x2514,\n    [0x6e] = 0x253C,\n    [0x6f] = 0x23BA,\n    [0x70] = 0x23BB,\n    [0x71] = 0x2500,\n    [0x72] = 0x23BC,\n    [0x73] = 0x23BD,\n    [0x74] = 0x251C,\n    [0x75] = 0x2524,\n    [0x76] = 0x2534,\n    [0x77] = 0x252C,\n    [0x78] = 0x2502,\n    [0x79] = 0x2A7D,\n    [0x7a] = 0x2A7E,\n    [0x7b] = 0x03C0,\n    [0x7c] = 0x2260,\n    [0x7d] = 0x00A3,\n    [0x7e] = 0x00B7,\n  }\n};\n"
  },
  {
    "path": "src/encoding/DECdrawing.tbl",
    "content": "6/0 = U+25C6 # BLACK DIAMOND\n6/1 = U+2592 # MEDIUM SHADE (checkerboard)\n6/2 = U+2409 # SYMBOL FOR HORIZONTAL TAB\n6/3 = U+240C # SYMBOL FOR FORM FEED\n6/4 = U+240D # SYMBOL FOR CARRIAGE RETURN\n6/5 = U+240A # SYMBOL FOR LINE FEED\n6/6 = U+00B0 # DEGREE SIGN\n6/7 = U+00B1 # PLUS-MINUS SIGN (plus or minus)\n6/8 = U+2424 # SYMBOL FOR NEW LINE\n6/9 = U+240B # SYMBOL FOR VERTICAL TAB\n6/10 = U+2518 # BOX DRAWINGS LIGHT UP AND LEFT (bottom-right corner)\n6/11 = U+2510 # BOX DRAWINGS LIGHT DOWN AND LEFT (top-right corner)\n6/12 = U+250C # BOX DRAWINGS LIGHT DOWN AND RIGHT (top-left corner)\n6/13 = U+2514 # BOX DRAWINGS LIGHT UP AND RIGHT (bottom-left corner)\n6/14 = U+253C # BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL (crossing lines)\n6/15 = U+23BA # HORIZONTAL SCAN LINE-1\n7/0 = U+23BB # HORIZONTAL SCAN LINE-3\n7/1 = U+2500 # BOX DRAWINGS LIGHT HORIZONTAL\n7/2 = U+23BC # HORIZONTAL SCAN LINE-7\n7/3 = U+23BD # HORIZONTAL SCAN LINE-9\n7/4 = U+251C # BOX DRAWINGS LIGHT VERTICAL AND RIGHT\n7/5 = U+2524 # BOX DRAWINGS LIGHT VERTICAL AND LEFT\n7/6 = U+2534 # BOX DRAWINGS LIGHT UP AND HORIZONTAL\n7/7 = U+252C # BOX DRAWINGS LIGHT DOWN AND HORIZONTAL\n7/8 = U+2502 # BOX DRAWINGS LIGHT VERTICAL\n7/9 = U+2A7D # LESS-THAN OR SLANTED EQUAL-TO\n7/10 = U+2A7E # GREATER-THAN OR SLANTED EQUAL-TO\n7/11 = U+03C0 # GREEK SMALL LETTER PI\n7/12 = U+2260 # NOT EQUAL TO\n7/13 = U+00A3 # POUND SIGN\n7/14 = U+00B7 # MIDDLE DOT\n"
  },
  {
    "path": "src/encoding/uk.inc",
    "content": "static const struct StaticTableEncoding encoding_uk = {\n  { .decode = &decode_table },\n  {\n    [0x23] = 0x00a3,\n  }\n};\n"
  },
  {
    "path": "src/encoding/uk.tbl",
    "content": "2/3 = \"£\"\n"
  },
  {
    "path": "src/encoding.c",
    "content": "#include \"vterm_internal.h\"\n\n#define UNICODE_INVALID 0xFFFD\n\n#if defined(DEBUG) && DEBUG > 1\n# define DEBUG_PRINT_UTF8\n#endif\n\nstruct UTF8DecoderData {\n  // number of bytes remaining in this codepoint\n  int bytes_remaining;\n\n  // number of bytes total in this codepoint once it's finished\n  // (for detecting overlongs)\n  int bytes_total;\n\n  int this_cp;\n};\n\nstatic void init_utf8(VTermEncoding *enc, void *data_)\n{\n  struct UTF8DecoderData *data = data_;\n\n  data->bytes_remaining = 0;\n  data->bytes_total     = 0;\n}\n\nstatic void decode_utf8(VTermEncoding *enc, void *data_,\n                        uint32_t cp[], int *cpi, int cplen,\n                        const char bytes[], size_t *pos, size_t bytelen)\n{\n  struct UTF8DecoderData *data = data_;\n\n#ifdef DEBUG_PRINT_UTF8\n  printf(\"BEGIN UTF-8\\n\");\n#endif\n\n  for(; *pos < bytelen && *cpi < cplen; (*pos)++) {\n    unsigned char c = bytes[*pos];\n\n#ifdef DEBUG_PRINT_UTF8\n    printf(\" pos=%zd c=%02x rem=%d\\n\", *pos, c, data->bytes_remaining);\n#endif\n\n    if(c < 0x20) // C0\n      return;\n\n    else if(c >= 0x20 && c < 0x7f) {\n      if(data->bytes_remaining)\n        cp[(*cpi)++] = UNICODE_INVALID;\n\n      cp[(*cpi)++] = c;\n#ifdef DEBUG_PRINT_UTF8\n      printf(\" UTF-8 char: U+%04x\\n\", c);\n#endif\n      data->bytes_remaining = 0;\n    }\n\n    else if(c == 0x7f) // DEL\n      return;\n\n    else if(c >= 0x80 && c < 0xc0) {\n      if(!data->bytes_remaining) {\n        cp[(*cpi)++] = UNICODE_INVALID;\n        continue;\n      }\n\n      data->this_cp <<= 6;\n      data->this_cp |= c & 0x3f;\n      data->bytes_remaining--;\n\n      if(!data->bytes_remaining) {\n#ifdef DEBUG_PRINT_UTF8\n        printf(\" UTF-8 raw char U+%04x bytelen=%d \", data->this_cp, data->bytes_total);\n#endif\n        // Check for overlong sequences\n        switch(data->bytes_total) {\n        case 2:\n          if(data->this_cp <  0x0080) data->this_cp = UNICODE_INVALID;\n          break;\n        case 3:\n          if(data->this_cp <  0x0800) data->this_cp = UNICODE_INVALID;\n          break;\n        case 4:\n          if(data->this_cp < 0x10000) data->this_cp = UNICODE_INVALID;\n          break;\n        case 5:\n          if(data->this_cp < 0x200000) data->this_cp = UNICODE_INVALID;\n          break;\n        case 6:\n          if(data->this_cp < 0x4000000) data->this_cp = UNICODE_INVALID;\n          break;\n        }\n        // Now look for plain invalid ones\n        if((data->this_cp >= 0xD800 && data->this_cp <= 0xDFFF) ||\n           data->this_cp == 0xFFFE ||\n           data->this_cp == 0xFFFF)\n          data->this_cp = UNICODE_INVALID;\n#ifdef DEBUG_PRINT_UTF8\n        printf(\" char: U+%04x\\n\", data->this_cp);\n#endif\n        cp[(*cpi)++] = data->this_cp;\n      }\n    }\n\n    else if(c >= 0xc0 && c < 0xe0) {\n      if(data->bytes_remaining)\n        cp[(*cpi)++] = UNICODE_INVALID;\n\n      data->this_cp = c & 0x1f;\n      data->bytes_total = 2;\n      data->bytes_remaining = 1;\n    }\n\n    else if(c >= 0xe0 && c < 0xf0) {\n      if(data->bytes_remaining)\n        cp[(*cpi)++] = UNICODE_INVALID;\n\n      data->this_cp = c & 0x0f;\n      data->bytes_total = 3;\n      data->bytes_remaining = 2;\n    }\n\n    else if(c >= 0xf0 && c < 0xf8) {\n      if(data->bytes_remaining)\n        cp[(*cpi)++] = UNICODE_INVALID;\n\n      data->this_cp = c & 0x07;\n      data->bytes_total = 4;\n      data->bytes_remaining = 3;\n    }\n\n    else if(c >= 0xf8 && c < 0xfc) {\n      if(data->bytes_remaining)\n        cp[(*cpi)++] = UNICODE_INVALID;\n\n      data->this_cp = c & 0x03;\n      data->bytes_total = 5;\n      data->bytes_remaining = 4;\n    }\n\n    else if(c >= 0xfc && c < 0xfe) {\n      if(data->bytes_remaining)\n        cp[(*cpi)++] = UNICODE_INVALID;\n\n      data->this_cp = c & 0x01;\n      data->bytes_total = 6;\n      data->bytes_remaining = 5;\n    }\n\n    else {\n      cp[(*cpi)++] = UNICODE_INVALID;\n    }\n  }\n}\n\nstatic VTermEncoding encoding_utf8 = {\n  .init   = &init_utf8,\n  .decode = &decode_utf8,\n};\n\nstatic void decode_usascii(VTermEncoding *enc, void *data,\n                           uint32_t cp[], int *cpi, int cplen,\n                           const char bytes[], size_t *pos, size_t bytelen)\n{\n  int is_gr = bytes[*pos] & 0x80;\n\n  for(; *pos < bytelen && *cpi < cplen; (*pos)++) {\n    unsigned char c = bytes[*pos] ^ is_gr;\n\n    if(c < 0x20 || c == 0x7f || c >= 0x80)\n      return;\n\n    cp[(*cpi)++] = c;\n  }\n}\n\nstatic VTermEncoding encoding_usascii = {\n  .decode = &decode_usascii,\n};\n\nstruct StaticTableEncoding {\n  const VTermEncoding enc;\n  const uint32_t chars[128];\n};\n\nstatic void decode_table(VTermEncoding *enc, void *data,\n                         uint32_t cp[], int *cpi, int cplen,\n                         const char bytes[], size_t *pos, size_t bytelen)\n{\n  struct StaticTableEncoding *table = (struct StaticTableEncoding *)enc;\n  int is_gr = bytes[*pos] & 0x80;\n\n  for(; *pos < bytelen && *cpi < cplen; (*pos)++) {\n    unsigned char c = bytes[*pos] ^ is_gr;\n\n    if(c < 0x20 || c == 0x7f || c >= 0x80)\n      return;\n\n    if(table->chars[c])\n      cp[(*cpi)++] = table->chars[c];\n    else\n      cp[(*cpi)++] = c;\n  }\n}\n\n#include \"encoding/DECdrawing.inc\"\n#include \"encoding/uk.inc\"\n\nstatic struct {\n  VTermEncodingType type;\n  char designation;\n  VTermEncoding *enc;\n}\nencodings[] = {\n  { ENC_UTF8,      'u', &encoding_utf8 },\n  { ENC_SINGLE_94, '0', (VTermEncoding*)&encoding_DECdrawing },\n  { ENC_SINGLE_94, 'A', (VTermEncoding*)&encoding_uk },\n  { ENC_SINGLE_94, 'B', &encoding_usascii },\n  { 0 },\n};\n\n/* This ought to be INTERNAL but isn't because it's used by unit testing */\nVTermEncoding *vterm_lookup_encoding(VTermEncodingType type, char designation)\n{\n  for(int i = 0; encodings[i].designation; i++)\n    if(encodings[i].type == type && encodings[i].designation == designation)\n      return encodings[i].enc;\n  return NULL;\n}\n"
  },
  {
    "path": "src/fullwidth.inc",
    "content": "  { 0x1100, 0x115f },\n  { 0x231a, 0x231b },\n  { 0x2329, 0x232a },\n  { 0x23e9, 0x23ec },\n  { 0x23f0, 0x23f0 },\n  { 0x23f3, 0x23f3 },\n  { 0x25fd, 0x25fe },\n  { 0x2614, 0x2615 },\n  { 0x2648, 0x2653 },\n  { 0x267f, 0x267f },\n  { 0x2693, 0x2693 },\n  { 0x26a1, 0x26a1 },\n  { 0x26aa, 0x26ab },\n  { 0x26bd, 0x26be },\n  { 0x26c4, 0x26c5 },\n  { 0x26ce, 0x26ce },\n  { 0x26d4, 0x26d4 },\n  { 0x26ea, 0x26ea },\n  { 0x26f2, 0x26f3 },\n  { 0x26f5, 0x26f5 },\n  { 0x26fa, 0x26fa },\n  { 0x26fd, 0x26fd },\n  { 0x2705, 0x2705 },\n  { 0x270a, 0x270b },\n  { 0x2728, 0x2728 },\n  { 0x274c, 0x274c },\n  { 0x274e, 0x274e },\n  { 0x2753, 0x2755 },\n  { 0x2757, 0x2757 },\n  { 0x2795, 0x2797 },\n  { 0x27b0, 0x27b0 },\n  { 0x27bf, 0x27bf },\n  { 0x2b1b, 0x2b1c },\n  { 0x2b50, 0x2b50 },\n  { 0x2b55, 0x2b55 },\n  { 0x2e80, 0x2e99 },\n  { 0x2e9b, 0x2ef3 },\n  { 0x2f00, 0x2fd5 },\n  { 0x2ff0, 0x2ffb },\n  { 0x3000, 0x303e },\n  { 0x3041, 0x3096 },\n  { 0x3099, 0x30ff },\n  { 0x3105, 0x312f },\n  { 0x3131, 0x318e },\n  { 0x3190, 0x31ba },\n  { 0x31c0, 0x31e3 },\n  { 0x31f0, 0x321e },\n  { 0x3220, 0x3247 },\n  { 0x3250, 0x4dbf },\n  { 0x4e00, 0xa48c },\n  { 0xa490, 0xa4c6 },\n  { 0xa960, 0xa97c },\n  { 0xac00, 0xd7a3 },\n  { 0xf900, 0xfaff },\n  { 0xfe10, 0xfe19 },\n  { 0xfe30, 0xfe52 },\n  { 0xfe54, 0xfe66 },\n  { 0xfe68, 0xfe6b },\n  { 0xff01, 0xff60 },\n  { 0xffe0, 0xffe6 },\n  { 0x16fe0, 0x16fe3 },\n  { 0x17000, 0x187f7 },\n  { 0x18800, 0x18af2 },\n  { 0x1b000, 0x1b11e },\n  { 0x1b150, 0x1b152 },\n  { 0x1b164, 0x1b167 },\n  { 0x1b170, 0x1b2fb },\n  { 0x1f004, 0x1f004 },\n  { 0x1f0cf, 0x1f0cf },\n  { 0x1f18e, 0x1f18e },\n  { 0x1f191, 0x1f19a },\n  { 0x1f200, 0x1f202 },\n  { 0x1f210, 0x1f23b },\n  { 0x1f240, 0x1f248 },\n  { 0x1f250, 0x1f251 },\n  { 0x1f260, 0x1f265 },\n  { 0x1f300, 0x1f320 },\n  { 0x1f32d, 0x1f335 },\n  { 0x1f337, 0x1f37c },\n  { 0x1f37e, 0x1f393 },\n  { 0x1f3a0, 0x1f3ca },\n  { 0x1f3cf, 0x1f3d3 },\n  { 0x1f3e0, 0x1f3f0 },\n  { 0x1f3f4, 0x1f3f4 },\n  { 0x1f3f8, 0x1f43e },\n  { 0x1f440, 0x1f440 },\n  { 0x1f442, 0x1f4fc },\n  { 0x1f4ff, 0x1f53d },\n  { 0x1f54b, 0x1f54e },\n  { 0x1f550, 0x1f567 },\n  { 0x1f57a, 0x1f57a },\n  { 0x1f595, 0x1f596 },\n  { 0x1f5a4, 0x1f5a4 },\n  { 0x1f5fb, 0x1f64f },\n  { 0x1f680, 0x1f6c5 },\n  { 0x1f6cc, 0x1f6cc },\n  { 0x1f6d0, 0x1f6d2 },\n  { 0x1f6d5, 0x1f6d5 },\n  { 0x1f6eb, 0x1f6ec },\n  { 0x1f6f4, 0x1f6fa },\n  { 0x1f7e0, 0x1f7eb },\n  { 0x1f90d, 0x1f971 },\n  { 0x1f973, 0x1f976 },\n  { 0x1f97a, 0x1f9a2 },\n  { 0x1f9a5, 0x1f9aa },\n  { 0x1f9ae, 0x1f9ca },\n  { 0x1f9cd, 0x1f9ff },\n  { 0x1fa70, 0x1fa73 },\n  { 0x1fa78, 0x1fa7a },\n  { 0x1fa80, 0x1fa82 },\n  { 0x1fa90, 0x1fa95 },\n"
  },
  {
    "path": "src/keyboard.c",
    "content": "#include \"vterm_internal.h\"\n\n#include <stdio.h>\n\n#include \"utf8.h\"\n\nvoid vterm_keyboard_unichar(VTerm *vt, uint32_t c, VTermModifier mod)\n{\n  /* The shift modifier is never important for Unicode characters\n   * apart from Space\n   */\n  if(c != ' ')\n    mod &= ~VTERM_MOD_SHIFT;\n\n  if(mod == 0) {\n    // Normal text - ignore just shift\n    char str[6];\n    int seqlen = fill_utf8(c, str);\n    vterm_push_output_bytes(vt, str, seqlen);\n    return;\n  }\n\n  int needs_CSIu;\n  switch(c) {\n    /* Special Ctrl- letters that can't be represented elsewise */\n    case 'i': case 'j': case 'm': case '[':\n      needs_CSIu = 1;\n      break;\n    /* Ctrl-\\ ] ^ _ don't need CSUu */\n    case '\\\\': case ']': case '^': case '_':\n      needs_CSIu = 0;\n      break;\n    /* Shift-space needs CSIu */\n    case ' ':\n      needs_CSIu = !!(mod & VTERM_MOD_SHIFT);\n      break;\n    /* All other characters needs CSIu except for letters a-z */\n    default:\n      needs_CSIu = (c < 'a' || c > 'z');\n  }\n\n  /* ALT we can just prefix with ESC; anything else requires CSI u */\n  if(needs_CSIu && (mod & ~VTERM_MOD_ALT)) {\n    vterm_push_output_sprintf_ctrl(vt, C1_CSI, \"%d;%du\", c, mod+1);\n    return;\n  }\n\n  if(mod & VTERM_MOD_CTRL)\n    c &= 0x1f;\n\n  vterm_push_output_sprintf(vt, \"%s%c\", mod & VTERM_MOD_ALT ? ESC_S : \"\", c);\n}\n\ntypedef struct {\n  enum {\n    KEYCODE_NONE,\n    KEYCODE_LITERAL,\n    KEYCODE_TAB,\n    KEYCODE_ENTER,\n    KEYCODE_SS3,\n    KEYCODE_CSI,\n    KEYCODE_CSI_CURSOR,\n    KEYCODE_CSINUM,\n    KEYCODE_KEYPAD,\n  } type;\n  char literal;\n  int csinum;\n} keycodes_s;\n\nstatic keycodes_s keycodes[] = {\n  { KEYCODE_NONE }, // NONE\n\n  { KEYCODE_ENTER,   '\\r'   }, // ENTER\n  { KEYCODE_TAB,     '\\t'   }, // TAB\n  { KEYCODE_LITERAL, '\\x7f' }, // BACKSPACE == ASCII DEL\n  { KEYCODE_LITERAL, '\\x1b' }, // ESCAPE\n\n  { KEYCODE_CSI_CURSOR, 'A' }, // UP\n  { KEYCODE_CSI_CURSOR, 'B' }, // DOWN\n  { KEYCODE_CSI_CURSOR, 'D' }, // LEFT\n  { KEYCODE_CSI_CURSOR, 'C' }, // RIGHT\n\n  { KEYCODE_CSINUM, '~', 2 },  // INS\n  { KEYCODE_CSINUM, '~', 3 },  // DEL\n  { KEYCODE_CSI_CURSOR, 'H' }, // HOME\n  { KEYCODE_CSI_CURSOR, 'F' }, // END\n  { KEYCODE_CSINUM, '~', 5 },  // PAGEUP\n  { KEYCODE_CSINUM, '~', 6 },  // PAGEDOWN\n};\n\nstatic keycodes_s keycodes_fn[] = {\n  { KEYCODE_NONE },            // F0 - shouldn't happen\n  { KEYCODE_SS3,    'P' },     // F1\n  { KEYCODE_SS3,    'Q' },     // F2\n  { KEYCODE_SS3,    'R' },     // F3\n  { KEYCODE_SS3,    'S' },     // F4\n  { KEYCODE_CSINUM, '~', 15 }, // F5\n  { KEYCODE_CSINUM, '~', 17 }, // F6\n  { KEYCODE_CSINUM, '~', 18 }, // F7\n  { KEYCODE_CSINUM, '~', 19 }, // F8\n  { KEYCODE_CSINUM, '~', 20 }, // F9\n  { KEYCODE_CSINUM, '~', 21 }, // F10\n  { KEYCODE_CSINUM, '~', 23 }, // F11\n  { KEYCODE_CSINUM, '~', 24 }, // F12\n};\n\nstatic keycodes_s keycodes_kp[] = {\n  { KEYCODE_KEYPAD, '0', 'p' }, // KP_0\n  { KEYCODE_KEYPAD, '1', 'q' }, // KP_1\n  { KEYCODE_KEYPAD, '2', 'r' }, // KP_2\n  { KEYCODE_KEYPAD, '3', 's' }, // KP_3\n  { KEYCODE_KEYPAD, '4', 't' }, // KP_4\n  { KEYCODE_KEYPAD, '5', 'u' }, // KP_5\n  { KEYCODE_KEYPAD, '6', 'v' }, // KP_6\n  { KEYCODE_KEYPAD, '7', 'w' }, // KP_7\n  { KEYCODE_KEYPAD, '8', 'x' }, // KP_8\n  { KEYCODE_KEYPAD, '9', 'y' }, // KP_9\n  { KEYCODE_KEYPAD, '*', 'j' }, // KP_MULT\n  { KEYCODE_KEYPAD, '+', 'k' }, // KP_PLUS\n  { KEYCODE_KEYPAD, ',', 'l' }, // KP_COMMA\n  { KEYCODE_KEYPAD, '-', 'm' }, // KP_MINUS\n  { KEYCODE_KEYPAD, '.', 'n' }, // KP_PERIOD\n  { KEYCODE_KEYPAD, '/', 'o' }, // KP_DIVIDE\n  { KEYCODE_KEYPAD, '\\n', 'M' }, // KP_ENTER\n  { KEYCODE_KEYPAD, '=', 'X' }, // KP_EQUAL\n};\n\nvoid vterm_keyboard_key(VTerm *vt, VTermKey key, VTermModifier mod)\n{\n  if(key == VTERM_KEY_NONE)\n    return;\n\n  keycodes_s k;\n  if(key < VTERM_KEY_FUNCTION_0) {\n    if(key >= sizeof(keycodes)/sizeof(keycodes[0]))\n      return;\n    k = keycodes[key];\n  }\n  else if(key >= VTERM_KEY_FUNCTION_0 && key <= VTERM_KEY_FUNCTION_MAX) {\n    if((key - VTERM_KEY_FUNCTION_0) >= sizeof(keycodes_fn)/sizeof(keycodes_fn[0]))\n      return;\n    k = keycodes_fn[key - VTERM_KEY_FUNCTION_0];\n  }\n  else if(key >= VTERM_KEY_KP_0) {\n    if((key - VTERM_KEY_KP_0) >= sizeof(keycodes_kp)/sizeof(keycodes_kp[0]))\n      return;\n    k = keycodes_kp[key - VTERM_KEY_KP_0];\n  }\n\n  switch(k.type) {\n  case KEYCODE_NONE:\n    break;\n\n  case KEYCODE_TAB:\n    /* Shift-Tab is CSI Z but plain Tab is 0x09 */\n    if(mod == VTERM_MOD_SHIFT)\n      vterm_push_output_sprintf_ctrl(vt, C1_CSI, \"Z\");\n    else if(mod & VTERM_MOD_SHIFT)\n      vterm_push_output_sprintf_ctrl(vt, C1_CSI, \"1;%dZ\", mod+1);\n    else\n      goto case_LITERAL;\n    break;\n\n  case KEYCODE_ENTER:\n    /* Enter is CRLF in newline mode, but just LF in linefeed */\n    if(vt->state->mode.newline)\n      vterm_push_output_sprintf(vt, \"\\r\\n\");\n    else\n      goto case_LITERAL;\n    break;\n\n  case KEYCODE_LITERAL: case_LITERAL:\n    if(mod & (VTERM_MOD_SHIFT|VTERM_MOD_CTRL))\n      vterm_push_output_sprintf_ctrl(vt, C1_CSI, \"%d;%du\", k.literal, mod+1);\n    else\n      vterm_push_output_sprintf(vt, mod & VTERM_MOD_ALT ? ESC_S \"%c\" : \"%c\", k.literal);\n    break;\n\n  case KEYCODE_SS3: case_SS3:\n    if(mod == 0)\n      vterm_push_output_sprintf_ctrl(vt, C1_SS3, \"%c\", k.literal);\n    else\n      goto case_CSI;\n    break;\n\n  case KEYCODE_CSI: case_CSI:\n    if(mod == 0)\n      vterm_push_output_sprintf_ctrl(vt, C1_CSI, \"%c\", k.literal);\n    else\n      vterm_push_output_sprintf_ctrl(vt, C1_CSI, \"1;%d%c\", mod + 1, k.literal);\n    break;\n\n  case KEYCODE_CSINUM:\n    if(mod == 0)\n      vterm_push_output_sprintf_ctrl(vt, C1_CSI, \"%d%c\", k.csinum, k.literal);\n    else\n      vterm_push_output_sprintf_ctrl(vt, C1_CSI, \"%d;%d%c\", k.csinum, mod + 1, k.literal);\n    break;\n\n  case KEYCODE_CSI_CURSOR:\n    if(vt->state->mode.cursor)\n      goto case_SS3;\n    else\n      goto case_CSI;\n\n  case KEYCODE_KEYPAD:\n    if(vt->state->mode.keypad) {\n      k.literal = k.csinum;\n      goto case_SS3;\n    }\n    else\n      goto case_LITERAL;\n  }\n}\n\nvoid vterm_keyboard_start_paste(VTerm *vt)\n{\n  if(vt->state->mode.bracketpaste)\n    vterm_push_output_sprintf_ctrl(vt, C1_CSI, \"200~\");\n}\n\nvoid vterm_keyboard_end_paste(VTerm *vt)\n{\n  if(vt->state->mode.bracketpaste)\n    vterm_push_output_sprintf_ctrl(vt, C1_CSI, \"201~\");\n}\n"
  },
  {
    "path": "src/mouse.c",
    "content": "#include \"vterm_internal.h\"\n\n#include \"utf8.h\"\n\nstatic void output_mouse(VTermState *state, int code, int pressed, int modifiers, int col, int row)\n{\n  modifiers <<= 2;\n\n  switch(state->mouse_protocol) {\n  case MOUSE_X10:\n    if(col + 0x21 > 0xff)\n      col = 0xff - 0x21;\n    if(row + 0x21 > 0xff)\n      row = 0xff - 0x21;\n\n    if(!pressed)\n      code = 3;\n\n    vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, \"M%c%c%c\",\n        (code | modifiers) + 0x20, col + 0x21, row + 0x21);\n    break;\n\n  case MOUSE_UTF8:\n    {\n      char utf8[18]; size_t len = 0;\n\n      if(!pressed)\n        code = 3;\n\n      len += fill_utf8((code | modifiers) + 0x20, utf8 + len);\n      len += fill_utf8(col + 0x21, utf8 + len);\n      len += fill_utf8(row + 0x21, utf8 + len);\n      utf8[len] = 0;\n\n      vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, \"M%s\", utf8);\n    }\n    break;\n\n  case MOUSE_SGR:\n    vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, \"<%d;%d;%d%c\",\n        code | modifiers, col + 1, row + 1, pressed ? 'M' : 'm');\n    break;\n\n  case MOUSE_RXVT:\n    if(!pressed)\n      code = 3;\n\n    vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, \"%d;%d;%dM\",\n        code | modifiers, col + 1, row + 1);\n    break;\n  }\n}\n\nvoid vterm_mouse_move(VTerm *vt, int row, int col, VTermModifier mod)\n{\n  VTermState *state = vt->state;\n\n  if(col == state->mouse_col && row == state->mouse_row)\n    return;\n\n  state->mouse_col = col;\n  state->mouse_row = row;\n\n  if((state->mouse_flags & MOUSE_WANT_DRAG && state->mouse_buttons) ||\n     (state->mouse_flags & MOUSE_WANT_MOVE)) {\n    int button = state->mouse_buttons & 0x01 ? 1 :\n                 state->mouse_buttons & 0x02 ? 2 :\n                 state->mouse_buttons & 0x04 ? 3 : 4;\n    output_mouse(state, button-1 + 0x20, 1, mod, col, row);\n  }\n}\n\nvoid vterm_mouse_button(VTerm *vt, int button, bool pressed, VTermModifier mod)\n{\n  VTermState *state = vt->state;\n\n  int old_buttons = state->mouse_buttons;\n\n  if(button > 0 && button <= 3) {\n    if(pressed)\n      state->mouse_buttons |= (1 << (button-1));\n    else\n      state->mouse_buttons &= ~(1 << (button-1));\n  }\n\n  /* Most of the time we don't get button releases from 4/5 */\n  if(state->mouse_buttons == old_buttons && button < 4)\n    return;\n\n  if(!state->mouse_flags)\n    return;\n\n  if(button < 4) {\n    output_mouse(state, button-1, pressed, mod, state->mouse_col, state->mouse_row);\n  }\n  else if(button < 8) {\n    output_mouse(state, button-4 + 0x40, pressed, mod, state->mouse_col, state->mouse_row);\n  }\n}\n"
  },
  {
    "path": "src/parser.c",
    "content": "#include \"vterm_internal.h\"\n\n#include <stdio.h>\n#include <string.h>\n\n#undef DEBUG_PARSER\n\nstatic bool is_intermed(unsigned char c)\n{\n  return c >= 0x20 && c <= 0x2f;\n}\n\nstatic void do_control(VTerm *vt, unsigned char control)\n{\n  if(vt->parser.callbacks && vt->parser.callbacks->control)\n    if((*vt->parser.callbacks->control)(control, vt->parser.cbdata))\n      return;\n\n  DEBUG_LOG(\"libvterm: Unhandled control 0x%02x\\n\", control);\n}\n\nstatic void do_csi(VTerm *vt, char command)\n{\n#ifdef DEBUG_PARSER\n  printf(\"Parsed CSI args as:\\n\", arglen, args);\n  printf(\" leader: %s\\n\", vt->parser.v.csi.leader);\n  for(int argi = 0; argi < vt->parser.v.csi.argi; argi++) {\n    printf(\" %lu\", CSI_ARG(vt->parser.v.csi.args[argi]));\n    if(!CSI_ARG_HAS_MORE(vt->parser.v.csi.args[argi]))\n      printf(\"\\n\");\n  printf(\" intermed: %s\\n\", vt->parser.intermed);\n  }\n#endif\n\n  if(vt->parser.callbacks && vt->parser.callbacks->csi)\n    if((*vt->parser.callbacks->csi)(\n          vt->parser.v.csi.leaderlen ? vt->parser.v.csi.leader : NULL, \n          vt->parser.v.csi.args,\n          vt->parser.v.csi.argi,\n          vt->parser.intermedlen ? vt->parser.intermed : NULL,\n          command,\n          vt->parser.cbdata))\n      return;\n\n  DEBUG_LOG(\"libvterm: Unhandled CSI %c\\n\", command);\n}\n\nstatic void do_escape(VTerm *vt, char command)\n{\n  char seq[INTERMED_MAX+1];\n\n  size_t len = vt->parser.intermedlen;\n  strncpy(seq, vt->parser.intermed, len);\n  seq[len++] = command;\n  seq[len]   = 0;\n\n  if(vt->parser.callbacks && vt->parser.callbacks->escape)\n    if((*vt->parser.callbacks->escape)(seq, len, vt->parser.cbdata))\n      return;\n\n  DEBUG_LOG(\"libvterm: Unhandled escape ESC 0x%02x\\n\", command);\n}\n\nstatic void string_fragment(VTerm *vt, const char *str, size_t len, bool final)\n{\n  VTermStringFragment frag = {\n    .str     = str,\n    .len     = len,\n    .initial = vt->parser.string_initial,\n    .final   = final,\n  };\n\n  switch(vt->parser.state) {\n    case OSC:\n      if(vt->parser.callbacks && vt->parser.callbacks->osc)\n        (*vt->parser.callbacks->osc)(vt->parser.v.osc.command, frag, vt->parser.cbdata);\n      break;\n\n    case DCS:\n      if(vt->parser.callbacks && vt->parser.callbacks->dcs)\n        (*vt->parser.callbacks->dcs)(vt->parser.v.dcs.command, vt->parser.v.dcs.commandlen, frag, vt->parser.cbdata);\n      break;\n\n    case APC:\n      if(vt->parser.callbacks && vt->parser.callbacks->apc)\n        (*vt->parser.callbacks->apc)(frag, vt->parser.cbdata);\n      break;\n\n    case PM:\n      if(vt->parser.callbacks && vt->parser.callbacks->pm)\n        (*vt->parser.callbacks->pm)(frag, vt->parser.cbdata);\n      break;\n\n    case SOS:\n      if(vt->parser.callbacks && vt->parser.callbacks->sos)\n        (*vt->parser.callbacks->sos)(frag, vt->parser.cbdata);\n      break;\n\n    case NORMAL:\n    case CSI_LEADER:\n    case CSI_ARGS:\n    case CSI_INTERMED:\n    case OSC_COMMAND:\n    case DCS_COMMAND:\n      break;\n  }\n\n  vt->parser.string_initial = false;\n}\n\nsize_t vterm_input_write(VTerm *vt, const char *bytes, size_t len)\n{\n  size_t pos = 0;\n  const char *string_start;\n\n  switch(vt->parser.state) {\n  case NORMAL:\n  case CSI_LEADER:\n  case CSI_ARGS:\n  case CSI_INTERMED:\n  case OSC_COMMAND:\n  case DCS_COMMAND:\n    string_start = NULL;\n    break;\n  case OSC:\n  case DCS:\n  case APC:\n  case PM:\n  case SOS:\n    string_start = bytes;\n    break;\n  }\n\n#define ENTER_STATE(st)        do { vt->parser.state = st; string_start = NULL; } while(0)\n#define ENTER_NORMAL_STATE()   ENTER_STATE(NORMAL)\n\n#define IS_STRING_STATE()      (vt->parser.state >= OSC_COMMAND)\n\n  for( ; pos < len; pos++) {\n    unsigned char c = bytes[pos];\n    bool c1_allowed = !vt->mode.utf8;\n\n    if(c == 0x00 || c == 0x7f) { // NUL, DEL\n      if(IS_STRING_STATE()) {\n        string_fragment(vt, string_start, bytes + pos - string_start, false);\n        string_start = bytes + pos + 1;\n      }\n      if(vt->parser.emit_nul)\n        do_control(vt, c);\n      continue;\n    }\n    if(c == 0x18 || c == 0x1a) { // CAN, SUB\n      vt->parser.in_esc = false;\n      ENTER_NORMAL_STATE();\n      if(vt->parser.emit_nul)\n        do_control(vt, c);\n      continue;\n    }\n    else if(c == 0x1b) { // ESC\n      vt->parser.intermedlen = 0;\n      if(!IS_STRING_STATE())\n        vt->parser.state = NORMAL;\n      vt->parser.in_esc = true;\n      continue;\n    }\n    else if(c == 0x07 &&  // BEL, can stand for ST in OSC or DCS state\n            IS_STRING_STATE()) {\n      // fallthrough\n    }\n    else if(c < 0x20) { // other C0\n      if(vt->parser.state == SOS)\n        continue; // All other C0s permitted in SOS\n\n      if(IS_STRING_STATE())\n        string_fragment(vt, string_start, bytes + pos - string_start, false);\n      do_control(vt, c);\n      if(IS_STRING_STATE())\n        string_start = bytes + pos + 1;\n      continue;\n    }\n    // else fallthrough\n\n    size_t string_len = bytes + pos - string_start;\n\n    if(vt->parser.in_esc) {\n      // Hoist an ESC letter into a C1 if we're not in a string mode\n      // Always accept ESC \\ == ST even in string mode\n      if(!vt->parser.intermedlen &&\n          c >= 0x40 && c < 0x60 &&\n          ((!IS_STRING_STATE() || c == 0x5c))) {\n        c += 0x40;\n        c1_allowed = true;\n        if(string_len)\n          string_len -= 1;\n        vt->parser.in_esc = false;\n      }\n      else {\n        string_start = NULL;\n        vt->parser.state = NORMAL;\n      }\n    }\n\n    switch(vt->parser.state) {\n    case CSI_LEADER:\n      /* Extract leader bytes 0x3c to 0x3f */\n      if(c >= 0x3c && c <= 0x3f) {\n        if(vt->parser.v.csi.leaderlen < CSI_LEADER_MAX-1)\n          vt->parser.v.csi.leader[vt->parser.v.csi.leaderlen++] = c;\n        break;\n      }\n\n      /* else fallthrough */\n      vt->parser.v.csi.leader[vt->parser.v.csi.leaderlen] = 0;\n\n      vt->parser.v.csi.argi = 0;\n      vt->parser.v.csi.args[0] = CSI_ARG_MISSING;\n      vt->parser.state = CSI_ARGS;\n\n      /* fallthrough */\n    case CSI_ARGS:\n      /* Numerical value of argument */\n      if(c >= '0' && c <= '9') {\n        if(vt->parser.v.csi.args[vt->parser.v.csi.argi] == CSI_ARG_MISSING)\n          vt->parser.v.csi.args[vt->parser.v.csi.argi] = 0;\n        vt->parser.v.csi.args[vt->parser.v.csi.argi] *= 10;\n        vt->parser.v.csi.args[vt->parser.v.csi.argi] += c - '0';\n        break;\n      }\n      if(c == ':') {\n        vt->parser.v.csi.args[vt->parser.v.csi.argi] |= CSI_ARG_FLAG_MORE;\n        c = ';';\n      }\n      if(c == ';') {\n        vt->parser.v.csi.argi++;\n        vt->parser.v.csi.args[vt->parser.v.csi.argi] = CSI_ARG_MISSING;\n        break;\n      }\n\n      /* else fallthrough */\n      vt->parser.v.csi.argi++;\n      vt->parser.intermedlen = 0;\n      vt->parser.state = CSI_INTERMED;\n    case CSI_INTERMED:\n      if(is_intermed(c)) {\n        if(vt->parser.intermedlen < INTERMED_MAX-1)\n          vt->parser.intermed[vt->parser.intermedlen++] = c;\n        break;\n      }\n      else if(c == 0x1b) {\n        /* ESC in CSI cancels */\n      }\n      else if(c >= 0x40 && c <= 0x7e) {\n        vt->parser.intermed[vt->parser.intermedlen] = 0;\n        do_csi(vt, c);\n      }\n      /* else was invalid CSI */\n\n      ENTER_NORMAL_STATE();\n      break;\n\n    case OSC_COMMAND:\n      /* Numerical value of command */\n      if(c >= '0' && c <= '9') {\n        if(vt->parser.v.osc.command == -1)\n          vt->parser.v.osc.command = 0;\n        else\n          vt->parser.v.osc.command *= 10;\n        vt->parser.v.osc.command += c - '0';\n        break;\n      }\n      if(c == ';') {\n        vt->parser.state = OSC;\n        string_start = bytes + pos + 1;\n        break;\n      }\n\n      /* else fallthrough */\n      string_start = bytes + pos;\n      string_len   = 0;\n      vt->parser.state = OSC;\n      goto string_state;\n\n    case DCS_COMMAND:\n      if(vt->parser.v.dcs.commandlen < CSI_LEADER_MAX)\n        vt->parser.v.dcs.command[vt->parser.v.dcs.commandlen++] = c;\n\n      if(c >= 0x40 && c<= 0x7e) {\n        string_start = bytes + pos + 1;\n        vt->parser.state = DCS;\n      }\n      break;\n\nstring_state:\n    case OSC:\n    case DCS:\n    case APC:\n    case PM:\n    case SOS:\n      if(c == 0x07 || (c1_allowed && c == 0x9c)) {\n        string_fragment(vt, string_start, string_len, true);\n        ENTER_NORMAL_STATE();\n      }\n      break;\n\n    case NORMAL:\n      if(vt->parser.in_esc) {\n        if(is_intermed(c)) {\n          if(vt->parser.intermedlen < INTERMED_MAX-1)\n            vt->parser.intermed[vt->parser.intermedlen++] = c;\n        }\n        else if(c >= 0x30 && c < 0x7f) {\n          do_escape(vt, c);\n          vt->parser.in_esc = 0;\n          ENTER_NORMAL_STATE();\n        }\n        else {\n          DEBUG_LOG(\"TODO: Unhandled byte %02x in Escape\\n\", c);\n        }\n        break;\n      }\n      if(c1_allowed && c >= 0x80 && c < 0xa0) {\n        switch(c) {\n        case 0x90: // DCS\n          vt->parser.string_initial = true;\n          vt->parser.v.dcs.commandlen = 0;\n          ENTER_STATE(DCS_COMMAND);\n          break;\n        case 0x98: // SOS\n          vt->parser.string_initial = true;\n          ENTER_STATE(SOS);\n          string_start = bytes + pos + 1;\n          string_len = 0;\n          break;\n        case 0x9b: // CSI\n          vt->parser.v.csi.leaderlen = 0;\n          ENTER_STATE(CSI_LEADER);\n          break;\n        case 0x9d: // OSC\n          vt->parser.v.osc.command = -1;\n          vt->parser.string_initial = true;\n          string_start = bytes + pos + 1;\n          ENTER_STATE(OSC_COMMAND);\n          break;\n        case 0x9e: // PM\n          vt->parser.string_initial = true;\n          ENTER_STATE(PM);\n          string_start = bytes + pos + 1;\n          string_len = 0;\n          break;\n        case 0x9f: // APC\n          vt->parser.string_initial = true;\n          ENTER_STATE(APC);\n          string_start = bytes + pos + 1;\n          string_len = 0;\n          break;\n        default:\n          do_control(vt, c);\n          break;\n        }\n      }\n      else {\n        size_t eaten = 0;\n        if(vt->parser.callbacks && vt->parser.callbacks->text)\n          eaten = (*vt->parser.callbacks->text)(bytes + pos, len - pos, vt->parser.cbdata);\n\n        if(!eaten) {\n          DEBUG_LOG(\"libvterm: Text callback did not consume any input\\n\");\n          /* force it to make progress */\n          eaten = 1;\n        }\n\n        pos += (eaten - 1); // we'll ++ it again in a moment\n      }\n      break;\n    }\n  }\n\n  if(string_start) {\n    size_t string_len = bytes + pos - string_start;\n    if(vt->parser.in_esc)\n      string_len -= 1;\n    string_fragment(vt, string_start, string_len, false);\n  }\n\n  return len;\n}\n\nvoid vterm_parser_set_callbacks(VTerm *vt, const VTermParserCallbacks *callbacks, void *user)\n{\n  vt->parser.callbacks = callbacks;\n  vt->parser.cbdata = user;\n}\n\nvoid *vterm_parser_get_cbdata(VTerm *vt)\n{\n  return vt->parser.cbdata;\n}\n\nvoid vterm_parser_set_emit_nul(VTerm *vt, bool emit)\n{\n  vt->parser.emit_nul = emit;\n}\n"
  },
  {
    "path": "src/pen.c",
    "content": "#include \"vterm_internal.h\"\n\n#include <stdio.h>\n\n/**\n * Structure used to store RGB triples without the additional metadata stored in\n * VTermColor.\n */\ntypedef struct {\n  uint8_t red, green, blue;\n} VTermRGB;\n\nstatic const VTermRGB ansi_colors[] = {\n  /* R    G    B */\n  {   0,   0,   0 }, // black\n  { 224,   0,   0 }, // red\n  {   0, 224,   0 }, // green\n  { 224, 224,   0 }, // yellow\n  {   0,   0, 224 }, // blue\n  { 224,   0, 224 }, // magenta\n  {   0, 224, 224 }, // cyan\n  { 224, 224, 224 }, // white == light grey\n\n  // high intensity\n  { 128, 128, 128 }, // black\n  { 255,  64,  64 }, // red\n  {  64, 255,  64 }, // green\n  { 255, 255,  64 }, // yellow\n  {  64,  64, 255 }, // blue\n  { 255,  64, 255 }, // magenta\n  {  64, 255, 255 }, // cyan\n  { 255, 255, 255 }, // white for real\n};\n\nstatic int ramp6[] = {\n  0x00, 0x33, 0x66, 0x99, 0xCC, 0xFF,\n};\n\nstatic int ramp24[] = {\n  0x00, 0x0B, 0x16, 0x21, 0x2C, 0x37, 0x42, 0x4D, 0x58, 0x63, 0x6E, 0x79,\n  0x85, 0x90, 0x9B, 0xA6, 0xB1, 0xBC, 0xC7, 0xD2, 0xDD, 0xE8, 0xF3, 0xFF,\n};\n\nstatic void lookup_default_colour_ansi(long idx, VTermColor *col)\n{\n  if (idx >= 0 && idx < 16) {\n    vterm_color_rgb(\n        col,\n        ansi_colors[idx].red, ansi_colors[idx].green, ansi_colors[idx].blue);\n  }\n}\n\nstatic bool lookup_colour_ansi(const VTermState *state, long index, VTermColor *col)\n{\n  if(index >= 0 && index < 16) {\n    *col = state->colors[index];\n    return true;\n  }\n\n  return false;\n}\n\nstatic bool lookup_colour_palette(const VTermState *state, long index, VTermColor *col)\n{\n  if(index >= 0 && index < 16) {\n    // Normal 8 colours or high intensity - parse as palette 0\n    return lookup_colour_ansi(state, index, col);\n  }\n  else if(index >= 16 && index < 232) {\n    // 216-colour cube\n    index -= 16;\n\n    vterm_color_rgb(col, ramp6[index/6/6 % 6],\n                         ramp6[index/6   % 6],\n                         ramp6[index     % 6]);\n\n    return true;\n  }\n  else if(index >= 232 && index < 256) {\n    // 24 greyscales\n    index -= 232;\n\n    vterm_color_rgb(col, ramp24[index], ramp24[index], ramp24[index]);\n\n    return true;\n  }\n\n  return false;\n}\n\nstatic int lookup_colour(const VTermState *state, int palette, const long args[], int argcount, VTermColor *col)\n{\n  switch(palette) {\n  case 2: // RGB mode - 3 args contain colour values directly\n    if(argcount < 3)\n      return argcount;\n\n    vterm_color_rgb(col, CSI_ARG(args[0]), CSI_ARG(args[1]), CSI_ARG(args[2]));\n\n    return 3;\n\n  case 5: // XTerm 256-colour mode\n    if (!argcount || CSI_ARG_IS_MISSING(args[0])) {\n      return argcount ? 1 : 0;\n    }\n\n    vterm_color_indexed(col, args[0]);\n\n    return argcount ? 1 : 0;\n\n  default:\n    DEBUG_LOG(\"Unrecognised colour palette %d\\n\", palette);\n    return 0;\n  }\n}\n\n// Some conveniences\n\nstatic void setpenattr(VTermState *state, VTermAttr attr, VTermValueType type, VTermValue *val)\n{\n#ifdef DEBUG\n  if(type != vterm_get_attr_type(attr)) {\n    DEBUG_LOG(\"Cannot set attr %d as it has type %d, not type %d\\n\",\n        attr, vterm_get_attr_type(attr), type);\n    return;\n  }\n#endif\n  if(state->callbacks && state->callbacks->setpenattr)\n    (*state->callbacks->setpenattr)(attr, val, state->cbdata);\n}\n\nstatic void setpenattr_bool(VTermState *state, VTermAttr attr, int boolean)\n{\n  VTermValue val = { .boolean = boolean };\n  setpenattr(state, attr, VTERM_VALUETYPE_BOOL, &val);\n}\n\nstatic void setpenattr_int(VTermState *state, VTermAttr attr, int number)\n{\n  VTermValue val = { .number = number };\n  setpenattr(state, attr, VTERM_VALUETYPE_INT, &val);\n}\n\nstatic void setpenattr_col(VTermState *state, VTermAttr attr, VTermColor color)\n{\n  VTermValue val = { .color = color };\n  setpenattr(state, attr, VTERM_VALUETYPE_COLOR, &val);\n}\n\nstatic void set_pen_col_ansi(VTermState *state, VTermAttr attr, long col)\n{\n  VTermColor *colp = (attr == VTERM_ATTR_BACKGROUND) ? &state->pen.bg : &state->pen.fg;\n\n  vterm_color_indexed(colp, col);\n\n  setpenattr_col(state, attr, *colp);\n}\n\nINTERNAL void vterm_state_newpen(VTermState *state)\n{\n  // 90% grey so that pure white is brighter\n  vterm_color_rgb(&state->default_fg, 240, 240, 240);\n  vterm_color_rgb(&state->default_bg, 0, 0, 0);\n  vterm_state_set_default_colors(state, &state->default_fg, &state->default_bg);\n\n  for(int col = 0; col < 16; col++)\n    lookup_default_colour_ansi(col, &state->colors[col]);\n}\n\nINTERNAL void vterm_state_resetpen(VTermState *state)\n{\n  state->pen.bold = 0;      setpenattr_bool(state, VTERM_ATTR_BOLD, 0);\n  state->pen.underline = 0; setpenattr_int (state, VTERM_ATTR_UNDERLINE, 0);\n  state->pen.italic = 0;    setpenattr_bool(state, VTERM_ATTR_ITALIC, 0);\n  state->pen.blink = 0;     setpenattr_bool(state, VTERM_ATTR_BLINK, 0);\n  state->pen.reverse = 0;   setpenattr_bool(state, VTERM_ATTR_REVERSE, 0);\n  state->pen.conceal = 0;   setpenattr_bool(state, VTERM_ATTR_CONCEAL, 0);\n  state->pen.strike = 0;    setpenattr_bool(state, VTERM_ATTR_STRIKE, 0);\n  state->pen.font = 0;      setpenattr_int (state, VTERM_ATTR_FONT, 0);\n  state->pen.small = 0;     setpenattr_bool(state, VTERM_ATTR_SMALL, 0);\n  state->pen.baseline = 0;  setpenattr_int (state, VTERM_ATTR_BASELINE, 0);\n\n  state->pen.fg = state->default_fg;  setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->default_fg);\n  state->pen.bg = state->default_bg;  setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->default_bg);\n}\n\nINTERNAL void vterm_state_savepen(VTermState *state, int save)\n{\n  if(save) {\n    state->saved.pen = state->pen;\n  }\n  else {\n    state->pen = state->saved.pen;\n\n    setpenattr_bool(state, VTERM_ATTR_BOLD,      state->pen.bold);\n    setpenattr_int (state, VTERM_ATTR_UNDERLINE, state->pen.underline);\n    setpenattr_bool(state, VTERM_ATTR_ITALIC,    state->pen.italic);\n    setpenattr_bool(state, VTERM_ATTR_BLINK,     state->pen.blink);\n    setpenattr_bool(state, VTERM_ATTR_REVERSE,   state->pen.reverse);\n    setpenattr_bool(state, VTERM_ATTR_CONCEAL,   state->pen.conceal);\n    setpenattr_bool(state, VTERM_ATTR_STRIKE,    state->pen.strike);\n    setpenattr_int (state, VTERM_ATTR_FONT,      state->pen.font);\n    setpenattr_bool(state, VTERM_ATTR_SMALL,     state->pen.small);\n    setpenattr_int (state, VTERM_ATTR_BASELINE,  state->pen.baseline);\n\n    setpenattr_col( state, VTERM_ATTR_FOREGROUND, state->pen.fg);\n    setpenattr_col( state, VTERM_ATTR_BACKGROUND, state->pen.bg);\n  }\n}\n\nint vterm_color_is_equal(const VTermColor *a, const VTermColor *b)\n{\n  /* First make sure that the two colours are of the same type (RGB/Indexed) */\n  if (a->type != b->type) {\n    return false;\n  }\n\n  /* Depending on the type inspect the corresponding members */\n  if (VTERM_COLOR_IS_INDEXED(a)) {\n    return a->indexed.idx == b->indexed.idx;\n  }\n  else if (VTERM_COLOR_IS_RGB(a)) {\n    return    (a->rgb.red   == b->rgb.red)\n           && (a->rgb.green == b->rgb.green)\n           && (a->rgb.blue  == b->rgb.blue);\n  }\n\n  return 0;\n}\n\nvoid vterm_state_get_default_colors(const VTermState *state, VTermColor *default_fg, VTermColor *default_bg)\n{\n  *default_fg = state->default_fg;\n  *default_bg = state->default_bg;\n}\n\nvoid vterm_state_get_palette_color(const VTermState *state, int index, VTermColor *col)\n{\n  lookup_colour_palette(state, index, col);\n}\n\nvoid vterm_state_set_default_colors(VTermState *state, const VTermColor *default_fg, const VTermColor *default_bg)\n{\n  if(default_fg) {\n    state->default_fg = *default_fg;\n    state->default_fg.type = (state->default_fg.type & ~VTERM_COLOR_DEFAULT_MASK)\n                           | VTERM_COLOR_DEFAULT_FG;\n  }\n\n  if(default_bg) {\n    state->default_bg = *default_bg;\n    state->default_bg.type = (state->default_bg.type & ~VTERM_COLOR_DEFAULT_MASK)\n                           | VTERM_COLOR_DEFAULT_BG;\n  }\n}\n\nvoid vterm_state_set_palette_color(VTermState *state, int index, const VTermColor *col)\n{\n  if(index >= 0 && index < 16)\n    state->colors[index] = *col;\n}\n\nvoid vterm_state_convert_color_to_rgb(const VTermState *state, VTermColor *col)\n{\n  if (VTERM_COLOR_IS_INDEXED(col)) { /* Convert indexed colors to RGB */\n    lookup_colour_palette(state, col->indexed.idx, col);\n  }\n  col->type &= VTERM_COLOR_TYPE_MASK; /* Reset any metadata but the type */\n}\n\nvoid vterm_state_set_bold_highbright(VTermState *state, int bold_is_highbright)\n{\n  state->bold_is_highbright = bold_is_highbright;\n}\n\nINTERNAL void vterm_state_setpen(VTermState *state, const long args[], int argcount)\n{\n  // SGR - ECMA-48 8.3.117\n\n  int argi = 0;\n  int value;\n\n  while(argi < argcount) {\n    // This logic is easier to do 'done' backwards; set it true, and make it\n    // false again in the 'default' case\n    int done = 1;\n\n    long arg;\n    switch(arg = CSI_ARG(args[argi])) {\n    case CSI_ARG_MISSING:\n    case 0: // Reset\n      vterm_state_resetpen(state);\n      break;\n\n    case 1: { // Bold on\n      const VTermColor *fg = &state->pen.fg;\n      state->pen.bold = 1;\n      setpenattr_bool(state, VTERM_ATTR_BOLD, 1);\n      if(!VTERM_COLOR_IS_DEFAULT_FG(fg) && VTERM_COLOR_IS_INDEXED(fg) && fg->indexed.idx < 8 && state->bold_is_highbright)\n        set_pen_col_ansi(state, VTERM_ATTR_FOREGROUND, fg->indexed.idx + (state->pen.bold ? 8 : 0));\n      break;\n    }\n\n    case 3: // Italic on\n      state->pen.italic = 1;\n      setpenattr_bool(state, VTERM_ATTR_ITALIC, 1);\n      break;\n\n    case 4: // Underline\n      state->pen.underline = VTERM_UNDERLINE_SINGLE;\n      if(CSI_ARG_HAS_MORE(args[argi])) {\n        argi++;\n        switch(CSI_ARG(args[argi])) {\n          case 0:\n            state->pen.underline = 0;\n            break;\n          case 1:\n            state->pen.underline = VTERM_UNDERLINE_SINGLE;\n            break;\n          case 2:\n            state->pen.underline = VTERM_UNDERLINE_DOUBLE;\n            break;\n          case 3:\n            state->pen.underline = VTERM_UNDERLINE_CURLY;\n            break;\n        }\n      }\n      setpenattr_int(state, VTERM_ATTR_UNDERLINE, state->pen.underline);\n      break;\n\n    case 5: // Blink\n      state->pen.blink = 1;\n      setpenattr_bool(state, VTERM_ATTR_BLINK, 1);\n      break;\n\n    case 7: // Reverse on\n      state->pen.reverse = 1;\n      setpenattr_bool(state, VTERM_ATTR_REVERSE, 1);\n      break;\n\n    case 8: // Conceal on\n      state->pen.conceal = 1;\n      setpenattr_bool(state, VTERM_ATTR_CONCEAL, 1);\n      break;\n\n    case 9: // Strikethrough on\n      state->pen.strike = 1;\n      setpenattr_bool(state, VTERM_ATTR_STRIKE, 1);\n      break;\n\n    case 10: case 11: case 12: case 13: case 14:\n    case 15: case 16: case 17: case 18: case 19: // Select font\n      state->pen.font = CSI_ARG(args[argi]) - 10;\n      setpenattr_int(state, VTERM_ATTR_FONT, state->pen.font);\n      break;\n\n    case 21: // Underline double\n      state->pen.underline = VTERM_UNDERLINE_DOUBLE;\n      setpenattr_int(state, VTERM_ATTR_UNDERLINE, state->pen.underline);\n      break;\n\n    case 22: // Bold off\n      state->pen.bold = 0;\n      setpenattr_bool(state, VTERM_ATTR_BOLD, 0);\n      break;\n\n    case 23: // Italic and Gothic (currently unsupported) off\n      state->pen.italic = 0;\n      setpenattr_bool(state, VTERM_ATTR_ITALIC, 0);\n      break;\n\n    case 24: // Underline off\n      state->pen.underline = 0;\n      setpenattr_int(state, VTERM_ATTR_UNDERLINE, 0);\n      break;\n\n    case 25: // Blink off\n      state->pen.blink = 0;\n      setpenattr_bool(state, VTERM_ATTR_BLINK, 0);\n      break;\n\n    case 27: // Reverse off\n      state->pen.reverse = 0;\n      setpenattr_bool(state, VTERM_ATTR_REVERSE, 0);\n      break;\n\n    case 28: // Conceal off (Reveal)\n      state->pen.conceal = 0;\n      setpenattr_bool(state, VTERM_ATTR_CONCEAL, 0);\n      break;\n\n    case 29: // Strikethrough off\n      state->pen.strike = 0;\n      setpenattr_bool(state, VTERM_ATTR_STRIKE, 0);\n      break;\n\n    case 30: case 31: case 32: case 33:\n    case 34: case 35: case 36: case 37: // Foreground colour palette\n      value = CSI_ARG(args[argi]) - 30;\n      if(state->pen.bold && state->bold_is_highbright)\n        value += 8;\n      set_pen_col_ansi(state, VTERM_ATTR_FOREGROUND, value);\n      break;\n\n    case 38: // Foreground colour alternative palette\n      if(argcount - argi < 1)\n        return;\n      argi += 1 + lookup_colour(state, CSI_ARG(args[argi+1]), args+argi+2, argcount-argi-2, &state->pen.fg);\n      setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->pen.fg);\n      break;\n\n    case 39: // Foreground colour default\n      state->pen.fg = state->default_fg;\n      setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->pen.fg);\n      break;\n\n    case 40: case 41: case 42: case 43:\n    case 44: case 45: case 46: case 47: // Background colour palette\n      value = CSI_ARG(args[argi]) - 40;\n      set_pen_col_ansi(state, VTERM_ATTR_BACKGROUND, value);\n      break;\n\n    case 48: // Background colour alternative palette\n      if(argcount - argi < 1)\n        return;\n      argi += 1 + lookup_colour(state, CSI_ARG(args[argi+1]), args+argi+2, argcount-argi-2, &state->pen.bg);\n      setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->pen.bg);\n      break;\n\n    case 49: // Default background\n      state->pen.bg = state->default_bg;\n      setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->pen.bg);\n      break;\n\n    case 73: // Superscript\n    case 74: // Subscript\n    case 75: // Superscript/subscript off\n      state->pen.small = (arg != 75);\n      state->pen.baseline =\n        (arg == 73) ? VTERM_BASELINE_RAISE :\n        (arg == 74) ? VTERM_BASELINE_LOWER :\n                      VTERM_BASELINE_NORMAL;\n      setpenattr_bool(state, VTERM_ATTR_SMALL,    state->pen.small);\n      setpenattr_int (state, VTERM_ATTR_BASELINE, state->pen.baseline);\n      break;\n\n    case 90: case 91: case 92: case 93:\n    case 94: case 95: case 96: case 97: // Foreground colour high-intensity palette\n      value = CSI_ARG(args[argi]) - 90 + 8;\n      set_pen_col_ansi(state, VTERM_ATTR_FOREGROUND, value);\n      break;\n\n    case 100: case 101: case 102: case 103:\n    case 104: case 105: case 106: case 107: // Background colour high-intensity palette\n      value = CSI_ARG(args[argi]) - 100 + 8;\n      set_pen_col_ansi(state, VTERM_ATTR_BACKGROUND, value);\n      break;\n\n    default:\n      done = 0;\n      break;\n    }\n\n    if(!done)\n      DEBUG_LOG(\"libvterm: Unhandled CSI SGR %ld\\n\", arg);\n\n    while(CSI_ARG_HAS_MORE(args[argi++]));\n  }\n}\n\nstatic int vterm_state_getpen_color(const VTermColor *col, int argi, long args[], int fg)\n{\n    /* Do nothing if the given color is the default color */\n    if (( fg && VTERM_COLOR_IS_DEFAULT_FG(col)) ||\n        (!fg && VTERM_COLOR_IS_DEFAULT_BG(col))) {\n        return argi;\n    }\n\n    /* Decide whether to send an indexed color or an RGB color */\n    if (VTERM_COLOR_IS_INDEXED(col)) {\n        const uint8_t idx = col->indexed.idx;\n        if (idx < 8) {\n            args[argi++] = (idx + (fg ? 30 : 40));\n        }\n        else if (idx < 16) {\n            args[argi++] = (idx - 8 + (fg ? 90 : 100));\n        }\n        else {\n            args[argi++] = CSI_ARG_FLAG_MORE | (fg ? 38 : 48);\n            args[argi++] = CSI_ARG_FLAG_MORE | 5;\n            args[argi++] = idx;\n        }\n    }\n    else if (VTERM_COLOR_IS_RGB(col)) {\n        args[argi++] = CSI_ARG_FLAG_MORE | (fg ? 38 : 48);\n        args[argi++] = CSI_ARG_FLAG_MORE | 2;\n        args[argi++] = CSI_ARG_FLAG_MORE | col->rgb.red;\n        args[argi++] = CSI_ARG_FLAG_MORE | col->rgb.green;\n        args[argi++] = col->rgb.blue;\n    }\n    return argi;\n}\n\nINTERNAL int vterm_state_getpen(VTermState *state, long args[], int argcount)\n{\n  int argi = 0;\n\n  if(state->pen.bold)\n    args[argi++] = 1;\n\n  if(state->pen.italic)\n    args[argi++] = 3;\n\n  if(state->pen.underline == VTERM_UNDERLINE_SINGLE)\n    args[argi++] = 4;\n  if(state->pen.underline == VTERM_UNDERLINE_CURLY)\n    args[argi++] = 4 | CSI_ARG_FLAG_MORE, args[argi++] = 3;\n\n  if(state->pen.blink)\n    args[argi++] = 5;\n\n  if(state->pen.reverse)\n    args[argi++] = 7;\n\n  if(state->pen.conceal)\n    args[argi++] = 8;\n\n  if(state->pen.strike)\n    args[argi++] = 9;\n\n  if(state->pen.font)\n    args[argi++] = 10 + state->pen.font;\n\n  if(state->pen.underline == VTERM_UNDERLINE_DOUBLE)\n    args[argi++] = 21;\n\n  argi = vterm_state_getpen_color(&state->pen.fg, argi, args, true);\n\n  argi = vterm_state_getpen_color(&state->pen.bg, argi, args, false);\n\n  if(state->pen.small) {\n    if(state->pen.baseline == VTERM_BASELINE_RAISE)\n      args[argi++] = 73;\n    else if(state->pen.baseline == VTERM_BASELINE_LOWER)\n      args[argi++] = 74;\n  }\n\n  return argi;\n}\n\nint vterm_state_get_penattr(const VTermState *state, VTermAttr attr, VTermValue *val)\n{\n  switch(attr) {\n  case VTERM_ATTR_BOLD:\n    val->boolean = state->pen.bold;\n    return 1;\n\n  case VTERM_ATTR_UNDERLINE:\n    val->number = state->pen.underline;\n    return 1;\n\n  case VTERM_ATTR_ITALIC:\n    val->boolean = state->pen.italic;\n    return 1;\n\n  case VTERM_ATTR_BLINK:\n    val->boolean = state->pen.blink;\n    return 1;\n\n  case VTERM_ATTR_REVERSE:\n    val->boolean = state->pen.reverse;\n    return 1;\n\n  case VTERM_ATTR_CONCEAL:\n    val->boolean = state->pen.conceal;\n    return 1;\n\n  case VTERM_ATTR_STRIKE:\n    val->boolean = state->pen.strike;\n    return 1;\n\n  case VTERM_ATTR_FONT:\n    val->number = state->pen.font;\n    return 1;\n\n  case VTERM_ATTR_FOREGROUND:\n    val->color = state->pen.fg;\n    return 1;\n\n  case VTERM_ATTR_BACKGROUND:\n    val->color = state->pen.bg;\n    return 1;\n\n  case VTERM_ATTR_SMALL:\n    val->boolean = state->pen.small;\n    return 1;\n\n  case VTERM_ATTR_BASELINE:\n    val->number = state->pen.baseline;\n    return 1;\n\n  case VTERM_N_ATTRS:\n    return 0;\n  }\n\n  return 0;\n}\n"
  },
  {
    "path": "src/rect.h",
    "content": "/*\n * Some utility functions on VTermRect structures\n */\n\n#define STRFrect \"(%d,%d-%d,%d)\"\n#define ARGSrect(r) (r).start_row, (r).start_col, (r).end_row, (r).end_col\n\n/* Expand dst to contain src as well */\nstatic void rect_expand(VTermRect *dst, VTermRect *src)\n{\n  if(dst->start_row > src->start_row) dst->start_row = src->start_row;\n  if(dst->start_col > src->start_col) dst->start_col = src->start_col;\n  if(dst->end_row   < src->end_row)   dst->end_row   = src->end_row;\n  if(dst->end_col   < src->end_col)   dst->end_col   = src->end_col;\n}\n\n/* Clip the dst to ensure it does not step outside of bounds */\nstatic void rect_clip(VTermRect *dst, VTermRect *bounds)\n{\n  if(dst->start_row < bounds->start_row) dst->start_row = bounds->start_row;\n  if(dst->start_col < bounds->start_col) dst->start_col = bounds->start_col;\n  if(dst->end_row   > bounds->end_row)   dst->end_row   = bounds->end_row;\n  if(dst->end_col   > bounds->end_col)   dst->end_col   = bounds->end_col;\n  /* Ensure it doesn't end up negatively-sized */\n  if(dst->end_row < dst->start_row) dst->end_row = dst->start_row;\n  if(dst->end_col < dst->start_col) dst->end_col = dst->start_col;\n}\n\n/* True if the two rectangles are equal */\nstatic int rect_equal(VTermRect *a, VTermRect *b)\n{\n  return (a->start_row == b->start_row) &&\n         (a->start_col == b->start_col) &&\n         (a->end_row   == b->end_row)   &&\n         (a->end_col   == b->end_col);\n}\n\n/* True if small is contained entirely within big */\nstatic int rect_contains(VTermRect *big, VTermRect *small)\n{\n  if(small->start_row < big->start_row) return 0;\n  if(small->start_col < big->start_col) return 0;\n  if(small->end_row   > big->end_row)   return 0;\n  if(small->end_col   > big->end_col)   return 0;\n  return 1;\n}\n\n/* True if the rectangles overlap at all */\nstatic int rect_intersects(VTermRect *a, VTermRect *b)\n{\n  if(a->start_row > b->end_row || b->start_row > a->end_row)\n    return 0;\n  if(a->start_col > b->end_col || b->start_col > a->end_col)\n    return 0;\n  return 1;\n}\n"
  },
  {
    "path": "src/screen.c",
    "content": "#include \"vterm_internal.h\"\n\n#include <stdio.h>\n#include <string.h>\n\n#include \"rect.h\"\n#include \"utf8.h\"\n\n#define UNICODE_SPACE 0x20\n#define UNICODE_LINEFEED 0x0a\n\n#undef DEBUG_REFLOW\n\n/* State of the pen at some moment in time, also used in a cell */\ntypedef struct\n{\n  /* After the bitfield */\n  VTermColor   fg, bg;\n\n  unsigned int bold      : 1;\n  unsigned int underline : 2;\n  unsigned int italic    : 1;\n  unsigned int blink     : 1;\n  unsigned int reverse   : 1;\n  unsigned int conceal   : 1;\n  unsigned int strike    : 1;\n  unsigned int font      : 4; /* 0 to 9 */\n  unsigned int small     : 1;\n  unsigned int baseline  : 2;\n\n  /* Extra state storage that isn't strictly pen-related */\n  unsigned int protected_cell : 1;\n  unsigned int dwl            : 1; /* on a DECDWL or DECDHL line */\n  unsigned int dhl            : 2; /* on a DECDHL line (1=top 2=bottom) */\n} ScreenPen;\n\n/* Internal representation of a screen cell */\ntypedef struct\n{\n  uint32_t chars[VTERM_MAX_CHARS_PER_CELL];\n  ScreenPen pen;\n} ScreenCell;\n\nstruct VTermScreen\n{\n  VTerm *vt;\n  VTermState *state;\n\n  const VTermScreenCallbacks *callbacks;\n  void *cbdata;\n  bool callbacks_has_pushline4;\n\n  VTermDamageSize damage_merge;\n  /* start_row == -1 => no damage */\n  VTermRect damaged;\n  VTermRect pending_scrollrect;\n  int pending_scroll_downward, pending_scroll_rightward;\n\n  int rows;\n  int cols;\n\n  unsigned int global_reverse : 1;\n  unsigned int reflow : 1;\n\n  /* Primary and Altscreen. buffers[1] is lazily allocated as needed */\n  ScreenCell *buffers[2];\n\n  /* buffer will == buffers[0] or buffers[1], depending on altscreen */\n  ScreenCell *buffer;\n\n  /* buffer for a single screen row used in scrollback storage callbacks */\n  VTermScreenCell *sb_buffer;\n\n  ScreenPen pen;\n};\n\nstatic inline void clearcell(const VTermScreen *screen, ScreenCell *cell)\n{\n  cell->chars[0] = 0;\n  cell->pen = screen->pen;\n}\n\nstatic inline ScreenCell *getcell(const VTermScreen *screen, int row, int col)\n{\n  if(row < 0 || row >= screen->rows)\n    return NULL;\n  if(col < 0 || col >= screen->cols)\n    return NULL;\n  return screen->buffer + (screen->cols * row) + col;\n}\n\nstatic ScreenCell *alloc_buffer(VTermScreen *screen, int rows, int cols)\n{\n  ScreenCell *new_buffer = vterm_allocator_malloc(screen->vt, sizeof(ScreenCell) * rows * cols);\n\n  for(int row = 0; row < rows; row++) {\n    for(int col = 0; col < cols; col++) {\n      clearcell(screen, &new_buffer[row * cols + col]);\n    }\n  }\n\n  return new_buffer;\n}\n\nstatic void damagerect(VTermScreen *screen, VTermRect rect)\n{\n  VTermRect emit;\n\n  switch(screen->damage_merge) {\n  case VTERM_DAMAGE_CELL:\n    /* Always emit damage event */\n    emit = rect;\n    break;\n\n  case VTERM_DAMAGE_ROW:\n    /* Emit damage longer than one row. Try to merge with existing damage in\n     * the same row */\n    if(rect.end_row > rect.start_row + 1) {\n      // Bigger than 1 line - flush existing, emit this\n      vterm_screen_flush_damage(screen);\n      emit = rect;\n    }\n    else if(screen->damaged.start_row == -1) {\n      // None stored yet\n      screen->damaged = rect;\n      return;\n    }\n    else if(rect.start_row == screen->damaged.start_row) {\n      // Merge with the stored line\n      if(screen->damaged.start_col > rect.start_col)\n        screen->damaged.start_col = rect.start_col;\n      if(screen->damaged.end_col < rect.end_col)\n        screen->damaged.end_col = rect.end_col;\n      return;\n    }\n    else {\n      // Emit the currently stored line, store a new one\n      emit = screen->damaged;\n      screen->damaged = rect;\n    }\n    break;\n\n  case VTERM_DAMAGE_SCREEN:\n  case VTERM_DAMAGE_SCROLL:\n    /* Never emit damage event */\n    if(screen->damaged.start_row == -1)\n      screen->damaged = rect;\n    else {\n      rect_expand(&screen->damaged, &rect);\n    }\n    return;\n\n  default:\n    DEBUG_LOG(\"TODO: Maybe merge damage for level %d\\n\", screen->damage_merge);\n    return;\n  }\n\n  if(screen->callbacks && screen->callbacks->damage)\n    (*screen->callbacks->damage)(emit, screen->cbdata);\n}\n\nstatic void damagescreen(VTermScreen *screen)\n{\n  VTermRect rect = {\n    .start_row = 0,\n    .end_row   = screen->rows,\n    .start_col = 0,\n    .end_col   = screen->cols,\n  };\n\n  damagerect(screen, rect);\n}\n\nstatic int putglyph(VTermGlyphInfo *info, VTermPos pos, void *user)\n{\n  VTermScreen *screen = user;\n  ScreenCell *cell = getcell(screen, pos.row, pos.col);\n\n  if(!cell)\n    return 0;\n\n  int i;\n  for(i = 0; i < VTERM_MAX_CHARS_PER_CELL && info->chars[i]; i++) {\n    cell->chars[i] = info->chars[i];\n    cell->pen = screen->pen;\n  }\n  if(i < VTERM_MAX_CHARS_PER_CELL)\n    cell->chars[i] = 0;\n\n  for(int col = 1; col < info->width; col++)\n    getcell(screen, pos.row, pos.col + col)->chars[0] = (uint32_t)-1;\n\n  VTermRect rect = {\n    .start_row = pos.row,\n    .end_row   = pos.row+1,\n    .start_col = pos.col,\n    .end_col   = pos.col+info->width,\n  };\n\n  cell->pen.protected_cell = info->protected_cell;\n  cell->pen.dwl            = info->dwl;\n  cell->pen.dhl            = info->dhl;\n\n  damagerect(screen, rect);\n\n  return 1;\n}\n\nstatic void sb_pushline_from_row(VTermScreen *screen, int row, bool continuation)\n{\n  VTermPos pos = { .row = row };\n  for(pos.col = 0; pos.col < screen->cols; pos.col++)\n    vterm_screen_get_cell(screen, pos, screen->sb_buffer + pos.col);\n\n  if(screen->callbacks_has_pushline4 && screen->callbacks->sb_pushline4)\n    (screen->callbacks->sb_pushline4)(screen->cols, screen->sb_buffer, continuation, screen->cbdata);\n  else\n    (screen->callbacks->sb_pushline)(screen->cols, screen->sb_buffer, screen->cbdata);\n}\n\nstatic int premove(VTermRect rect, void *user)\n{\n  VTermScreen *screen = user;\n\n  if(((screen->callbacks && screen->callbacks->sb_pushline) ||\n        (screen->callbacks_has_pushline4 && screen->callbacks && screen->callbacks->sb_pushline4)) &&\n     rect.start_row == 0 && rect.start_col == 0 &&        // starts top-left corner\n     rect.end_col == screen->cols &&                      // full width\n     screen->buffer == screen->buffers[BUFIDX_PRIMARY]) { // not altscreen\n    for(int row = 0; row < rect.end_row; row++) {\n      const VTermLineInfo *lineinfo = vterm_state_get_lineinfo(screen->state, row);\n      sb_pushline_from_row(screen, row, lineinfo->continuation);\n    }\n  }\n\n  return 1;\n}\n\nstatic int moverect_internal(VTermRect dest, VTermRect src, void *user)\n{\n  VTermScreen *screen = user;\n\n  int cols = src.end_col - src.start_col;\n  int downward = src.start_row - dest.start_row;\n\n  int init_row, test_row, inc_row;\n  if(downward < 0) {\n    init_row = dest.end_row - 1;\n    test_row = dest.start_row - 1;\n    inc_row  = -1;\n  }\n  else {\n    init_row = dest.start_row;\n    test_row = dest.end_row;\n    inc_row  = +1;\n  }\n\n  for(int row = init_row; row != test_row; row += inc_row)\n    memmove(getcell(screen, row, dest.start_col),\n            getcell(screen, row + downward, src.start_col),\n            cols * sizeof(ScreenCell));\n\n  return 1;\n}\n\nstatic int moverect_user(VTermRect dest, VTermRect src, void *user)\n{\n  VTermScreen *screen = user;\n\n  if(screen->callbacks && screen->callbacks->moverect) {\n    if(screen->damage_merge != VTERM_DAMAGE_SCROLL)\n      // Avoid an infinite loop\n      vterm_screen_flush_damage(screen);\n\n    if((*screen->callbacks->moverect)(dest, src, screen->cbdata))\n      return 1;\n  }\n\n  damagerect(screen, dest);\n\n  return 1;\n}\n\nstatic int erase_internal(VTermRect rect, int selective, void *user)\n{\n  VTermScreen *screen = user;\n\n  for(int row = rect.start_row; row < screen->state->rows && row < rect.end_row; row++) {\n    const VTermLineInfo *info = vterm_state_get_lineinfo(screen->state, row);\n\n    for(int col = rect.start_col; col < rect.end_col; col++) {\n      ScreenCell *cell = getcell(screen, row, col);\n\n      if(selective && cell->pen.protected_cell)\n        continue;\n\n      cell->chars[0] = 0;\n      cell->pen = (ScreenPen){\n        /* Only copy .fg and .bg; leave things like rv in reset state */\n        .fg = screen->pen.fg,\n        .bg = screen->pen.bg,\n      };\n      cell->pen.dwl = info->doublewidth;\n      cell->pen.dhl = info->doubleheight;\n    }\n  }\n\n  return 1;\n}\n\nstatic int erase_user(VTermRect rect, int selective, void *user)\n{\n  VTermScreen *screen = user;\n\n  damagerect(screen, rect);\n\n  return 1;\n}\n\nstatic int erase(VTermRect rect, int selective, void *user)\n{\n  erase_internal(rect, selective, user);\n  return erase_user(rect, 0, user);\n}\n\nstatic int scrollrect(VTermRect rect, int downward, int rightward, void *user)\n{\n  VTermScreen *screen = user;\n\n  if(screen->damage_merge != VTERM_DAMAGE_SCROLL) {\n    vterm_scroll_rect(rect, downward, rightward,\n        moverect_internal, erase_internal, screen);\n\n    vterm_screen_flush_damage(screen);\n\n    vterm_scroll_rect(rect, downward, rightward,\n        moverect_user, erase_user, screen);\n\n    return 1;\n  }\n\n  if(screen->damaged.start_row != -1 &&\n     !rect_intersects(&rect, &screen->damaged)) {\n    vterm_screen_flush_damage(screen);\n  }\n\n  if(screen->pending_scrollrect.start_row == -1) {\n    screen->pending_scrollrect = rect;\n    screen->pending_scroll_downward  = downward;\n    screen->pending_scroll_rightward = rightward;\n  }\n  else if(rect_equal(&screen->pending_scrollrect, &rect) &&\n     ((screen->pending_scroll_downward  == 0 && downward  == 0) ||\n      (screen->pending_scroll_rightward == 0 && rightward == 0))) {\n    screen->pending_scroll_downward  += downward;\n    screen->pending_scroll_rightward += rightward;\n  }\n  else {\n    vterm_screen_flush_damage(screen);\n\n    screen->pending_scrollrect = rect;\n    screen->pending_scroll_downward  = downward;\n    screen->pending_scroll_rightward = rightward;\n  }\n\n  vterm_scroll_rect(rect, downward, rightward,\n      moverect_internal, erase_internal, screen);\n\n  if(screen->damaged.start_row == -1)\n    return 1;\n\n  if(rect_contains(&rect, &screen->damaged)) {\n    /* Scroll region entirely contains the damage; just move it */\n    vterm_rect_move(&screen->damaged, -downward, -rightward);\n    rect_clip(&screen->damaged, &rect);\n  }\n  /* There are a number of possible cases here, but lets restrict this to only\n   * the common case where we might actually gain some performance by\n   * optimising it. Namely, a vertical scroll that neatly cuts the damage\n   * region in half.\n   */\n  else if(rect.start_col <= screen->damaged.start_col &&\n          rect.end_col   >= screen->damaged.end_col &&\n          rightward == 0) {\n    if(screen->damaged.start_row >= rect.start_row &&\n       screen->damaged.start_row  < rect.end_row) {\n      screen->damaged.start_row -= downward;\n      if(screen->damaged.start_row < rect.start_row)\n        screen->damaged.start_row = rect.start_row;\n      if(screen->damaged.start_row > rect.end_row)\n        screen->damaged.start_row = rect.end_row;\n    }\n    if(screen->damaged.end_row >= rect.start_row &&\n       screen->damaged.end_row  < rect.end_row) {\n      screen->damaged.end_row -= downward;\n      if(screen->damaged.end_row < rect.start_row)\n        screen->damaged.end_row = rect.start_row;\n      if(screen->damaged.end_row > rect.end_row)\n        screen->damaged.end_row = rect.end_row;\n    }\n  }\n  else {\n    DEBUG_LOG(\"TODO: Just flush and redo damaged=\" STRFrect \" rect=\" STRFrect \"\\n\",\n        ARGSrect(screen->damaged), ARGSrect(rect));\n  }\n\n  return 1;\n}\n\nstatic int movecursor(VTermPos pos, VTermPos oldpos, int visible, void *user)\n{\n  VTermScreen *screen = user;\n\n  if(screen->callbacks && screen->callbacks->movecursor)\n    return (*screen->callbacks->movecursor)(pos, oldpos, visible, screen->cbdata);\n\n  return 0;\n}\n\nstatic int setpenattr(VTermAttr attr, VTermValue *val, void *user)\n{\n  VTermScreen *screen = user;\n\n  switch(attr) {\n  case VTERM_ATTR_BOLD:\n    screen->pen.bold = val->boolean;\n    return 1;\n  case VTERM_ATTR_UNDERLINE:\n    screen->pen.underline = val->number;\n    return 1;\n  case VTERM_ATTR_ITALIC:\n    screen->pen.italic = val->boolean;\n    return 1;\n  case VTERM_ATTR_BLINK:\n    screen->pen.blink = val->boolean;\n    return 1;\n  case VTERM_ATTR_REVERSE:\n    screen->pen.reverse = val->boolean;\n    return 1;\n  case VTERM_ATTR_CONCEAL:\n    screen->pen.conceal = val->boolean;\n    return 1;\n  case VTERM_ATTR_STRIKE:\n    screen->pen.strike = val->boolean;\n    return 1;\n  case VTERM_ATTR_FONT:\n    screen->pen.font = val->number;\n    return 1;\n  case VTERM_ATTR_FOREGROUND:\n    screen->pen.fg = val->color;\n    return 1;\n  case VTERM_ATTR_BACKGROUND:\n    screen->pen.bg = val->color;\n    return 1;\n  case VTERM_ATTR_SMALL:\n    screen->pen.small = val->boolean;\n    return 1;\n  case VTERM_ATTR_BASELINE:\n    screen->pen.baseline = val->number;\n    return 1;\n\n  case VTERM_N_ATTRS:\n    return 0;\n  }\n\n  return 0;\n}\n\nstatic int settermprop(VTermProp prop, VTermValue *val, void *user)\n{\n  VTermScreen *screen = user;\n\n  switch(prop) {\n  case VTERM_PROP_ALTSCREEN:\n    if(val->boolean && !screen->buffers[BUFIDX_ALTSCREEN])\n      return 0;\n\n    screen->buffer = val->boolean ? screen->buffers[BUFIDX_ALTSCREEN] : screen->buffers[BUFIDX_PRIMARY];\n    /* only send a damage event on disable; because during enable there's an\n     * erase that sends a damage anyway\n     */\n    if(!val->boolean)\n      damagescreen(screen);\n    break;\n  case VTERM_PROP_REVERSE:\n    screen->global_reverse = val->boolean;\n    damagescreen(screen);\n    break;\n  default:\n    ; /* ignore */\n  }\n\n  if(screen->callbacks && screen->callbacks->settermprop)\n    return (*screen->callbacks->settermprop)(prop, val, screen->cbdata);\n\n  return 1;\n}\n\nstatic int bell(void *user)\n{\n  VTermScreen *screen = user;\n\n  if(screen->callbacks && screen->callbacks->bell)\n    return (*screen->callbacks->bell)(screen->cbdata);\n\n  return 0;\n}\n\n/* How many cells are non-blank\n * Returns the position of the first blank cell in the trailing blank end */\nstatic int line_popcount(ScreenCell *buffer, int row, int rows, int cols)\n{\n  int col = cols - 1;\n  while(col >= 0 && buffer[row * cols + col].chars[0] == 0)\n    col--;\n  return col + 1;\n}\n\nstatic void resize_buffer(VTermScreen *screen, int bufidx, int new_rows, int new_cols, bool active, VTermStateFields *statefields)\n{\n  int old_rows = screen->rows;\n  int old_cols = screen->cols;\n\n  ScreenCell *old_buffer = screen->buffers[bufidx];\n  VTermLineInfo *old_lineinfo = statefields->lineinfos[bufidx];\n\n  ScreenCell *new_buffer = vterm_allocator_malloc(screen->vt, sizeof(ScreenCell) * new_rows * new_cols);\n  VTermLineInfo *new_lineinfo = vterm_allocator_malloc(screen->vt, sizeof(new_lineinfo[0]) * new_rows);\n\n  int old_row = old_rows - 1;\n  int new_row = new_rows - 1;\n\n  VTermPos old_cursor = statefields->pos;\n  VTermPos new_cursor = { -1, -1 };\n\n#ifdef DEBUG_REFLOW\n  fprintf(stderr, \"Resizing from %dx%d to %dx%d; cursor was at (%d,%d)\\n\",\n      old_cols, old_rows, new_cols, new_rows, old_cursor.col, old_cursor.row);\n#endif\n\n  /* Keep track of the final row that is knonw to be blank, so we know what\n   * spare space we have for scrolling into\n   */\n  int final_blank_row = new_rows;\n\n  while(old_row >= 0) {\n    int old_row_end = old_row;\n    /* TODO: Stop if dwl or dhl */\n    while(screen->reflow && old_lineinfo && old_row >= 0 && old_lineinfo[old_row].continuation)\n      old_row--;\n    int old_row_start = old_row;\n\n    int width = 0;\n    for(int row = old_row_start; row <= old_row_end; row++) {\n      if(screen->reflow && row < (old_rows - 1) && old_lineinfo[row + 1].continuation)\n        width += old_cols;\n      else\n        width += line_popcount(old_buffer, row, old_rows, old_cols);\n    }\n\n    if(final_blank_row == (new_row + 1) && width == 0)\n      final_blank_row = new_row;\n\n    int new_height = screen->reflow\n      ? width ? (width + new_cols - 1) / new_cols : 1\n      : 1;\n\n    int new_row_end = new_row;\n    int new_row_start = new_row - new_height + 1;\n\n    old_row = old_row_start;\n    int old_col = 0;\n\n    int spare_rows = new_rows - final_blank_row;\n\n    if(new_row_start < 0 && /* we'd fall off the top */\n        spare_rows >= 0 && /* we actually have spare rows */\n        (!active || new_cursor.row == -1 || (new_cursor.row - new_row_start) < new_rows))\n    {\n      /* Attempt to scroll content down into the blank rows at the bottom to\n       * make it fit\n       */\n      int downwards = -new_row_start;\n      if(downwards > spare_rows)\n        downwards = spare_rows;\n      int rowcount = new_rows - downwards;\n\n#ifdef DEBUG_REFLOW\n      fprintf(stderr, \"  scroll %d rows +%d downwards\\n\", rowcount, downwards);\n#endif\n\n      memmove(&new_buffer[downwards * new_cols], &new_buffer[0],   rowcount * new_cols * sizeof(ScreenCell));\n      memmove(&new_lineinfo[downwards],          &new_lineinfo[0], rowcount            * sizeof(new_lineinfo[0]));\n\n      new_row += downwards;\n      new_row_start += downwards;\n      new_row_end += downwards;\n\n      if(new_cursor.row >= 0)\n        new_cursor.row += downwards;\n\n      final_blank_row += downwards;\n    }\n\n#ifdef DEBUG_REFLOW\n    fprintf(stderr, \"  rows [%d..%d] <- [%d..%d] width=%d\\n\",\n        new_row_start, new_row_end, old_row_start, old_row_end, width);\n#endif\n\n    if(new_row_start < 0) {\n      if(old_row_start <= old_cursor.row && old_cursor.row < old_row_end) {\n        new_cursor.row = 0;\n        new_cursor.col = old_cursor.col;\n        if(new_cursor.col >= new_cols)\n          new_cursor.col = new_cols-1;\n      }\n      break;\n    }\n\n    for(new_row = new_row_start, old_row = old_row_start; new_row <= new_row_end; new_row++) {\n      int count = width >= new_cols ? new_cols : width;\n      width -= count;\n\n      int new_col = 0;\n\n      while(count) {\n        /* TODO: This could surely be done a lot faster by memcpy()'ing the entire range */\n        new_buffer[new_row * new_cols + new_col] = old_buffer[old_row * old_cols + old_col];\n\n        if(old_cursor.row == old_row && old_cursor.col == old_col)\n          new_cursor.row = new_row, new_cursor.col = new_col;\n\n        old_col++;\n        if(old_col == old_cols) {\n          old_row++;\n\n          if(!screen->reflow) {\n            new_col++;\n            break;\n          }\n          old_col = 0;\n        }\n\n        new_col++;\n        count--;\n      }\n\n      if(old_cursor.row == old_row && old_cursor.col >= old_col) {\n        new_cursor.row = new_row, new_cursor.col = (old_cursor.col - old_col + new_col);\n        if(new_cursor.col >= new_cols)\n          new_cursor.col = new_cols-1;\n      }\n\n      while(new_col < new_cols) {\n        clearcell(screen, &new_buffer[new_row * new_cols + new_col]);\n        new_col++;\n      }\n\n      new_lineinfo[new_row].continuation = (new_row > new_row_start);\n    }\n\n    old_row = old_row_start - 1;\n    new_row = new_row_start - 1;\n  }\n\n  if(old_cursor.row <= old_row) {\n    /* cursor would have moved entirely off the top of the screen; lets just\n     * bring it within range */\n    new_cursor.row = 0, new_cursor.col = old_cursor.col;\n    if(new_cursor.col >= new_cols)\n      new_cursor.col = new_cols-1;\n  }\n\n  /* We really expect the cursor position to be set by now */\n  if(active && (new_cursor.row == -1 || new_cursor.col == -1)) {\n    fprintf(stderr, \"screen_resize failed to update cursor position\\n\");\n    abort();\n  }\n\n  if(old_row >= 0 && bufidx == BUFIDX_PRIMARY) {\n    /* Push spare lines to scrollback buffer */\n    if((screen->callbacks && screen->callbacks->sb_pushline) ||\n          (screen->callbacks_has_pushline4 && screen->callbacks && screen->callbacks->sb_pushline4))\n      for(int row = 0; row <= old_row; row++) {\n        const VTermLineInfo *lineinfo = old_lineinfo + row;\n        sb_pushline_from_row(screen, row, lineinfo->continuation);\n      }\n    if(active)\n      statefields->pos.row -= (old_row + 1);\n  }\n  if(new_row >= 0 && bufidx == BUFIDX_PRIMARY &&\n      screen->callbacks && screen->callbacks->sb_popline) {\n    /* Try to backfill rows by popping scrollback buffer */\n    while(new_row >= 0) {\n      if(!(screen->callbacks->sb_popline(old_cols, screen->sb_buffer, screen->cbdata)))\n        break;\n\n      VTermPos pos = { .row = new_row };\n      for(pos.col = 0; pos.col < old_cols && pos.col < new_cols; pos.col += screen->sb_buffer[pos.col].width) {\n        VTermScreenCell *src = &screen->sb_buffer[pos.col];\n        ScreenCell *dst = &new_buffer[pos.row * new_cols + pos.col];\n\n        for(int i = 0; i < VTERM_MAX_CHARS_PER_CELL; i++) {\n          dst->chars[i] = src->chars[i];\n          if(!src->chars[i])\n            break;\n        }\n\n        dst->pen.bold      = src->attrs.bold;\n        dst->pen.underline = src->attrs.underline;\n        dst->pen.italic    = src->attrs.italic;\n        dst->pen.blink     = src->attrs.blink;\n        dst->pen.reverse   = src->attrs.reverse ^ screen->global_reverse;\n        dst->pen.conceal   = src->attrs.conceal;\n        dst->pen.strike    = src->attrs.strike;\n        dst->pen.font      = src->attrs.font;\n        dst->pen.small     = src->attrs.small;\n        dst->pen.baseline  = src->attrs.baseline;\n\n        dst->pen.fg = src->fg;\n        dst->pen.bg = src->bg;\n\n        if(src->width == 2 && pos.col < (new_cols-1))\n          (dst + 1)->chars[0] = (uint32_t) -1;\n      }\n      for( ; pos.col < new_cols; pos.col++)\n        clearcell(screen, &new_buffer[pos.row * new_cols + pos.col]);\n      new_row--;\n\n      if(active)\n        statefields->pos.row++;\n    }\n  }\n  if(new_row >= 0) {\n    /* Scroll new rows back up to the top and fill in blanks at the bottom */\n    int moverows = new_rows - new_row - 1;\n    memmove(&new_buffer[0], &new_buffer[(new_row + 1) * new_cols], moverows * new_cols * sizeof(ScreenCell));\n    memmove(&new_lineinfo[0], &new_lineinfo[new_row + 1], moverows * sizeof(new_lineinfo[0]));\n\n    new_cursor.row -= (new_row + 1);\n\n    for(new_row = moverows; new_row < new_rows; new_row++) {\n      for(int col = 0; col < new_cols; col++)\n        clearcell(screen, &new_buffer[new_row * new_cols + col]);\n      new_lineinfo[new_row] = (VTermLineInfo){ 0 };\n    }\n  }\n\n  vterm_allocator_free(screen->vt, old_buffer);\n  screen->buffers[bufidx] = new_buffer;\n\n  vterm_allocator_free(screen->vt, old_lineinfo);\n  statefields->lineinfos[bufidx] = new_lineinfo;\n\n  if(active)\n    statefields->pos = new_cursor;\n\n  return;\n}\n\nstatic int resize(int new_rows, int new_cols, VTermStateFields *fields, void *user)\n{\n  VTermScreen *screen = user;\n\n  int altscreen_active = (screen->buffers[BUFIDX_ALTSCREEN] && screen->buffer == screen->buffers[BUFIDX_ALTSCREEN]);\n\n  int old_rows = screen->rows;\n  int old_cols = screen->cols;\n\n  if(new_cols > old_cols) {\n    /* Ensure that ->sb_buffer is large enough for a new or and old row */\n    if(screen->sb_buffer)\n      vterm_allocator_free(screen->vt, screen->sb_buffer);\n\n    screen->sb_buffer = vterm_allocator_malloc(screen->vt, sizeof(VTermScreenCell) * new_cols);\n  }\n\n  resize_buffer(screen, 0, new_rows, new_cols, !altscreen_active, fields);\n  if(screen->buffers[BUFIDX_ALTSCREEN])\n    resize_buffer(screen, 1, new_rows, new_cols, altscreen_active, fields);\n  else if(new_rows != old_rows) {\n    /* We don't need a full resize of the altscreen because it isn't enabled\n     * but we should at least keep the lineinfo the right size */\n    vterm_allocator_free(screen->vt, fields->lineinfos[BUFIDX_ALTSCREEN]);\n\n    VTermLineInfo *new_lineinfo = vterm_allocator_malloc(screen->vt, sizeof(new_lineinfo[0]) * new_rows);\n    for(int row = 0; row < new_rows; row++)\n      new_lineinfo[row] = (VTermLineInfo){ 0 };\n\n    fields->lineinfos[BUFIDX_ALTSCREEN] = new_lineinfo;\n  }\n\n  screen->buffer = altscreen_active ? screen->buffers[BUFIDX_ALTSCREEN] : screen->buffers[BUFIDX_PRIMARY];\n\n  screen->rows = new_rows;\n  screen->cols = new_cols;\n\n  if(new_cols <= old_cols) {\n    if(screen->sb_buffer)\n      vterm_allocator_free(screen->vt, screen->sb_buffer);\n\n    screen->sb_buffer = vterm_allocator_malloc(screen->vt, sizeof(VTermScreenCell) * new_cols);\n  }\n\n  /* TODO: Maaaaybe we can optimise this if there's no reflow happening */\n  damagescreen(screen);\n\n  if(screen->callbacks && screen->callbacks->resize)\n    return (*screen->callbacks->resize)(new_rows, new_cols, screen->cbdata);\n\n  return 1;\n}\n\nstatic int setlineinfo(int row, const VTermLineInfo *newinfo, const VTermLineInfo *oldinfo, void *user)\n{\n  VTermScreen *screen = user;\n\n  if(newinfo->doublewidth != oldinfo->doublewidth ||\n     newinfo->doubleheight != oldinfo->doubleheight) {\n    for(int col = 0; col < screen->cols; col++) {\n      ScreenCell *cell = getcell(screen, row, col);\n      cell->pen.dwl = newinfo->doublewidth;\n      cell->pen.dhl = newinfo->doubleheight;\n    }\n\n    VTermRect rect = {\n      .start_row = row,\n      .end_row   = row + 1,\n      .start_col = 0,\n      .end_col   = newinfo->doublewidth ? screen->cols / 2 : screen->cols,\n    };\n    damagerect(screen, rect);\n\n    if(newinfo->doublewidth) {\n      rect.start_col = screen->cols / 2;\n      rect.end_col   = screen->cols;\n\n      erase_internal(rect, 0, user);\n    }\n  }\n\n  return 1;\n}\n\nstatic int sb_clear(void *user) {\n  VTermScreen *screen = user;\n\n  if(screen->callbacks && screen->callbacks->sb_clear)\n    if((*screen->callbacks->sb_clear)(screen->cbdata))\n      return 1;\n\n  return 0;\n}\n\nstatic VTermStateCallbacks state_cbs = {\n  .putglyph    = &putglyph,\n  .movecursor  = &movecursor,\n  .premove     = &premove,\n  .scrollrect  = &scrollrect,\n  .erase       = &erase,\n  .setpenattr  = &setpenattr,\n  .settermprop = &settermprop,\n  .bell        = &bell,\n  .resize      = &resize,\n  .setlineinfo = &setlineinfo,\n  .sb_clear    = &sb_clear,\n};\n\nstatic VTermScreen *screen_new(VTerm *vt)\n{\n  VTermState *state = vterm_obtain_state(vt);\n  if(!state)\n    return NULL;\n\n  VTermScreen *screen = vterm_allocator_malloc(vt, sizeof(VTermScreen));\n  int rows, cols;\n\n  vterm_get_size(vt, &rows, &cols);\n\n  screen->vt = vt;\n  screen->state = state;\n\n  screen->damage_merge = VTERM_DAMAGE_CELL;\n  screen->damaged.start_row = -1;\n  screen->pending_scrollrect.start_row = -1;\n\n  screen->rows = rows;\n  screen->cols = cols;\n\n  screen->global_reverse = false;\n  screen->reflow = false;\n\n  screen->callbacks = NULL;\n  screen->cbdata    = NULL;\n  screen->callbacks_has_pushline4 = false;\n\n  screen->buffers[BUFIDX_PRIMARY] = alloc_buffer(screen, rows, cols);\n\n  screen->buffer = screen->buffers[BUFIDX_PRIMARY];\n\n  screen->sb_buffer = vterm_allocator_malloc(screen->vt, sizeof(VTermScreenCell) * cols);\n\n  vterm_state_set_callbacks(screen->state, &state_cbs, screen);\n  vterm_state_callbacks_has_premove(screen->state);\n\n  return screen;\n}\n\nINTERNAL void vterm_screen_free(VTermScreen *screen)\n{\n  vterm_allocator_free(screen->vt, screen->buffers[BUFIDX_PRIMARY]);\n  if(screen->buffers[BUFIDX_ALTSCREEN])\n    vterm_allocator_free(screen->vt, screen->buffers[BUFIDX_ALTSCREEN]);\n\n  vterm_allocator_free(screen->vt, screen->sb_buffer);\n\n  vterm_allocator_free(screen->vt, screen);\n}\n\nvoid vterm_screen_reset(VTermScreen *screen, int hard)\n{\n  screen->damaged.start_row = -1;\n  screen->pending_scrollrect.start_row = -1;\n  vterm_state_reset(screen->state, hard);\n  vterm_screen_flush_damage(screen);\n}\n\nstatic size_t _get_chars(const VTermScreen *screen, const int utf8, void *buffer, size_t len, const VTermRect rect)\n{\n  size_t outpos = 0;\n  int padding = 0;\n\n#define PUT(c)                                             \\\n  if(utf8) {                                               \\\n    size_t thislen = utf8_seqlen(c);                       \\\n    if(buffer && outpos + thislen <= len)                  \\\n      outpos += fill_utf8((c), (char *)buffer + outpos);   \\\n    else                                                   \\\n      outpos += thislen;                                   \\\n  }                                                        \\\n  else {                                                   \\\n    if(buffer && outpos + 1 <= len)                        \\\n      ((uint32_t*)buffer)[outpos++] = (c);                 \\\n    else                                                   \\\n      outpos++;                                            \\\n  }\n\n  for(int row = rect.start_row; row < rect.end_row; row++) {\n    for(int col = rect.start_col; col < rect.end_col; col++) {\n      ScreenCell *cell = getcell(screen, row, col);\n\n      if(cell->chars[0] == 0)\n        // Erased cell, might need a space\n        padding++;\n      else if(cell->chars[0] == (uint32_t)-1)\n        // Gap behind a double-width char, do nothing\n        ;\n      else {\n        while(padding) {\n          PUT(UNICODE_SPACE);\n          padding--;\n        }\n        for(int i = 0; i < VTERM_MAX_CHARS_PER_CELL && cell->chars[i]; i++) {\n          PUT(cell->chars[i]);\n        }\n      }\n    }\n\n    if(row < rect.end_row - 1) {\n      PUT(UNICODE_LINEFEED);\n      padding = 0;\n    }\n  }\n\n  return outpos;\n}\n\nsize_t vterm_screen_get_chars(const VTermScreen *screen, uint32_t *chars, size_t len, const VTermRect rect)\n{\n  return _get_chars(screen, 0, chars, len, rect);\n}\n\nsize_t vterm_screen_get_text(const VTermScreen *screen, char *str, size_t len, const VTermRect rect)\n{\n  return _get_chars(screen, 1, str, len, rect);\n}\n\n/* Copy internal to external representation of a screen cell */\nint vterm_screen_get_cell(const VTermScreen *screen, VTermPos pos, VTermScreenCell *cell)\n{\n  ScreenCell *intcell = getcell(screen, pos.row, pos.col);\n  if(!intcell)\n    return 0;\n\n  for(int i = 0; i < VTERM_MAX_CHARS_PER_CELL; i++) {\n    cell->chars[i] = intcell->chars[i];\n    if(!intcell->chars[i])\n      break;\n  }\n\n  cell->attrs.bold      = intcell->pen.bold;\n  cell->attrs.underline = intcell->pen.underline;\n  cell->attrs.italic    = intcell->pen.italic;\n  cell->attrs.blink     = intcell->pen.blink;\n  cell->attrs.reverse   = intcell->pen.reverse ^ screen->global_reverse;\n  cell->attrs.conceal   = intcell->pen.conceal;\n  cell->attrs.strike    = intcell->pen.strike;\n  cell->attrs.font      = intcell->pen.font;\n  cell->attrs.small     = intcell->pen.small;\n  cell->attrs.baseline  = intcell->pen.baseline;\n\n  cell->attrs.dwl = intcell->pen.dwl;\n  cell->attrs.dhl = intcell->pen.dhl;\n\n  cell->fg = intcell->pen.fg;\n  cell->bg = intcell->pen.bg;\n\n  if(pos.col < (screen->cols - 1) &&\n     getcell(screen, pos.row, pos.col + 1)->chars[0] == (uint32_t)-1)\n    cell->width = 2;\n  else\n    cell->width = 1;\n\n  return 1;\n}\n\nint vterm_screen_is_eol(const VTermScreen *screen, VTermPos pos)\n{\n  /* This cell is EOL if this and every cell to the right is black */\n  for(; pos.col < screen->cols; pos.col++) {\n    ScreenCell *cell = getcell(screen, pos.row, pos.col);\n    if(cell->chars[0] != 0)\n      return 0;\n  }\n\n  return 1;\n}\n\nVTermScreen *vterm_obtain_screen(VTerm *vt)\n{\n  if(vt->screen)\n    return vt->screen;\n\n  VTermScreen *screen = screen_new(vt);\n  vt->screen = screen;\n\n  return screen;\n}\n\nvoid vterm_screen_enable_reflow(VTermScreen *screen, bool reflow)\n{\n  screen->reflow = reflow;\n}\n\n#undef vterm_screen_set_reflow\nvoid vterm_screen_set_reflow(VTermScreen *screen, bool reflow)\n{\n  vterm_screen_enable_reflow(screen, reflow);\n}\n\nvoid vterm_screen_enable_altscreen(VTermScreen *screen, int altscreen)\n{\n  if(!screen->buffers[BUFIDX_ALTSCREEN] && altscreen) {\n    int rows, cols;\n    vterm_get_size(screen->vt, &rows, &cols);\n\n    screen->buffers[BUFIDX_ALTSCREEN] = alloc_buffer(screen, rows, cols);\n  }\n}\n\nvoid vterm_screen_set_callbacks(VTermScreen *screen, const VTermScreenCallbacks *callbacks, void *user)\n{\n  screen->callbacks = callbacks;\n  screen->cbdata = user;\n}\n\nvoid *vterm_screen_get_cbdata(VTermScreen *screen)\n{\n  return screen->cbdata;\n}\n\nvoid vterm_screen_callbacks_has_pushline4(VTermScreen *screen)\n{\n  screen->callbacks_has_pushline4 = true;\n}\n\nvoid vterm_screen_set_unrecognised_fallbacks(VTermScreen *screen, const VTermStateFallbacks *fallbacks, void *user)\n{\n  vterm_state_set_unrecognised_fallbacks(screen->state, fallbacks, user);\n}\n\nvoid *vterm_screen_get_unrecognised_fbdata(VTermScreen *screen)\n{\n  return vterm_state_get_unrecognised_fbdata(screen->state);\n}\n\nvoid vterm_screen_flush_damage(VTermScreen *screen)\n{\n  if(screen->pending_scrollrect.start_row != -1) {\n    vterm_scroll_rect(screen->pending_scrollrect, screen->pending_scroll_downward, screen->pending_scroll_rightward,\n        moverect_user, erase_user, screen);\n\n    screen->pending_scrollrect.start_row = -1;\n  }\n\n  if(screen->damaged.start_row != -1) {\n    if(screen->callbacks && screen->callbacks->damage)\n      (*screen->callbacks->damage)(screen->damaged, screen->cbdata);\n\n    screen->damaged.start_row = -1;\n  }\n}\n\nvoid vterm_screen_set_damage_merge(VTermScreen *screen, VTermDamageSize size)\n{\n  vterm_screen_flush_damage(screen);\n  screen->damage_merge = size;\n}\n\nstatic int attrs_differ(VTermAttrMask attrs, ScreenCell *a, ScreenCell *b)\n{\n  if((attrs & VTERM_ATTR_BOLD_MASK)       && (a->pen.bold != b->pen.bold))\n    return 1;\n  if((attrs & VTERM_ATTR_UNDERLINE_MASK)  && (a->pen.underline != b->pen.underline))\n    return 1;\n  if((attrs & VTERM_ATTR_ITALIC_MASK)     && (a->pen.italic != b->pen.italic))\n    return 1;\n  if((attrs & VTERM_ATTR_BLINK_MASK)      && (a->pen.blink != b->pen.blink))\n    return 1;\n  if((attrs & VTERM_ATTR_REVERSE_MASK)    && (a->pen.reverse != b->pen.reverse))\n    return 1;\n  if((attrs & VTERM_ATTR_CONCEAL_MASK)    && (a->pen.conceal != b->pen.conceal))\n    return 1;\n  if((attrs & VTERM_ATTR_STRIKE_MASK)     && (a->pen.strike != b->pen.strike))\n    return 1;\n  if((attrs & VTERM_ATTR_FONT_MASK)       && (a->pen.font != b->pen.font))\n    return 1;\n  if((attrs & VTERM_ATTR_FOREGROUND_MASK) && !vterm_color_is_equal(&a->pen.fg, &b->pen.fg))\n    return 1;\n  if((attrs & VTERM_ATTR_BACKGROUND_MASK) && !vterm_color_is_equal(&a->pen.bg, &b->pen.bg))\n    return 1;\n  if((attrs & VTERM_ATTR_SMALL_MASK)    && (a->pen.small != b->pen.small))\n    return 1;\n  if((attrs & VTERM_ATTR_BASELINE_MASK)    && (a->pen.baseline != b->pen.baseline))\n    return 1;\n\n  return 0;\n}\n\nint vterm_screen_get_attrs_extent(const VTermScreen *screen, VTermRect *extent, VTermPos pos, VTermAttrMask attrs)\n{\n  ScreenCell *target = getcell(screen, pos.row, pos.col);\n\n  // TODO: bounds check\n  extent->start_row = pos.row;\n  extent->end_row   = pos.row + 1;\n\n  if(extent->start_col < 0)\n    extent->start_col = 0;\n  if(extent->end_col < 0)\n    extent->end_col = screen->cols;\n\n  int col;\n\n  for(col = pos.col - 1; col >= extent->start_col; col--)\n    if(attrs_differ(attrs, target, getcell(screen, pos.row, col)))\n      break;\n  extent->start_col = col + 1;\n\n  for(col = pos.col + 1; col < extent->end_col; col++)\n    if(attrs_differ(attrs, target, getcell(screen, pos.row, col)))\n      break;\n  extent->end_col = col - 1;\n\n  return 1;\n}\n\nvoid vterm_screen_convert_color_to_rgb(const VTermScreen *screen, VTermColor *col)\n{\n  vterm_state_convert_color_to_rgb(screen->state, col);\n}\n\nstatic void reset_default_colours(VTermScreen *screen, ScreenCell *buffer)\n{\n  for(int row = 0; row <= screen->rows - 1; row++)\n    for(int col = 0; col <= screen->cols - 1; col++) {\n      ScreenCell *cell = &buffer[row * screen->cols + col];\n      if(VTERM_COLOR_IS_DEFAULT_FG(&cell->pen.fg))\n        cell->pen.fg = screen->pen.fg;\n      if(VTERM_COLOR_IS_DEFAULT_BG(&cell->pen.bg))\n        cell->pen.bg = screen->pen.bg;\n    }\n}\n\nvoid vterm_screen_set_default_colors(VTermScreen *screen, const VTermColor *default_fg, const VTermColor *default_bg)\n{\n  vterm_state_set_default_colors(screen->state, default_fg, default_bg);\n\n  if(default_fg && VTERM_COLOR_IS_DEFAULT_FG(&screen->pen.fg)) {\n    screen->pen.fg = *default_fg;\n    screen->pen.fg.type = (screen->pen.fg.type & ~VTERM_COLOR_DEFAULT_MASK)\n                        | VTERM_COLOR_DEFAULT_FG;\n  }\n\n  if(default_bg && VTERM_COLOR_IS_DEFAULT_BG(&screen->pen.bg)) {\n    screen->pen.bg = *default_bg;\n    screen->pen.bg.type = (screen->pen.bg.type & ~VTERM_COLOR_DEFAULT_MASK)\n                        | VTERM_COLOR_DEFAULT_BG;\n  }\n\n  reset_default_colours(screen, screen->buffers[0]);\n  if(screen->buffers[1])\n    reset_default_colours(screen, screen->buffers[1]);\n}\n"
  },
  {
    "path": "src/state.c",
    "content": "#include \"vterm_internal.h\"\n\n#include <stdio.h>\n#include <string.h>\n\n#define strneq(a,b,n) (strncmp(a,b,n)==0)\n\n#if defined(DEBUG) && DEBUG > 1\n# define DEBUG_GLYPH_COMBINE\n#endif\n\n/* Some convenient wrappers to make callback functions easier */\n\nstatic void putglyph(VTermState *state, const uint32_t chars[], int width, VTermPos pos)\n{\n  VTermGlyphInfo info = {\n    .chars = chars,\n    .width = width,\n    .protected_cell = state->protected_cell,\n    .dwl = state->lineinfo[pos.row].doublewidth,\n    .dhl = state->lineinfo[pos.row].doubleheight,\n  };\n\n  if(state->callbacks && state->callbacks->putglyph)\n    if((*state->callbacks->putglyph)(&info, pos, state->cbdata))\n      return;\n\n  DEBUG_LOG(\"libvterm: Unhandled putglyph U+%04x at (%d,%d)\\n\", chars[0], pos.col, pos.row);\n}\n\nstatic void updatecursor(VTermState *state, VTermPos *oldpos, int cancel_phantom)\n{\n  if(state->pos.col == oldpos->col && state->pos.row == oldpos->row)\n    return;\n\n  if(cancel_phantom)\n    state->at_phantom = 0;\n\n  if(state->callbacks && state->callbacks->movecursor)\n    if((*state->callbacks->movecursor)(state->pos, *oldpos, state->mode.cursor_visible, state->cbdata))\n      return;\n}\n\nstatic void erase(VTermState *state, VTermRect rect, int selective)\n{\n  if(rect.end_col == state->cols) {\n    /* If we're erasing the final cells of any lines, cancel the continuation\n     * marker on the subsequent line\n     */\n    for(int row = rect.start_row + 1; row < rect.end_row + 1 && row < state->rows; row++)\n      state->lineinfo[row].continuation = 0;\n  }\n\n  if(state->callbacks && state->callbacks->erase)\n    if((*state->callbacks->erase)(rect, selective, state->cbdata))\n      return;\n}\n\nstatic VTermState *vterm_state_new(VTerm *vt)\n{\n  VTermState *state = vterm_allocator_malloc(vt, sizeof(VTermState));\n\n  state->vt = vt;\n\n  state->rows = vt->rows;\n  state->cols = vt->cols;\n\n  state->mouse_col     = 0;\n  state->mouse_row     = 0;\n  state->mouse_buttons = 0;\n\n  state->mouse_protocol = MOUSE_X10;\n\n  state->callbacks = NULL;\n  state->cbdata    = NULL;\n  state->callbacks_has_premove = false;\n\n  state->selection.callbacks = NULL;\n  state->selection.user      = NULL;\n  state->selection.buffer    = NULL;\n\n  vterm_state_newpen(state);\n\n  state->bold_is_highbright = 0;\n\n  state->combine_chars_size = 16;\n  state->combine_chars = vterm_allocator_malloc(state->vt, state->combine_chars_size * sizeof(state->combine_chars[0]));\n\n  state->tabstops = vterm_allocator_malloc(state->vt, (state->cols + 7) / 8);\n\n  state->lineinfos[BUFIDX_PRIMARY]   = vterm_allocator_malloc(state->vt, state->rows * sizeof(VTermLineInfo));\n  /* TODO: Make an 'enable' function */\n  state->lineinfos[BUFIDX_ALTSCREEN] = vterm_allocator_malloc(state->vt, state->rows * sizeof(VTermLineInfo));\n  state->lineinfo = state->lineinfos[BUFIDX_PRIMARY];\n\n  state->encoding_utf8.enc = vterm_lookup_encoding(ENC_UTF8, 'u');\n  if(*state->encoding_utf8.enc->init)\n    (*state->encoding_utf8.enc->init)(state->encoding_utf8.enc, state->encoding_utf8.data);\n\n  return state;\n}\n\nINTERNAL void vterm_state_free(VTermState *state)\n{\n  vterm_allocator_free(state->vt, state->tabstops);\n  vterm_allocator_free(state->vt, state->lineinfos[BUFIDX_PRIMARY]);\n  if(state->lineinfos[BUFIDX_ALTSCREEN])\n    vterm_allocator_free(state->vt, state->lineinfos[BUFIDX_ALTSCREEN]);\n  vterm_allocator_free(state->vt, state->combine_chars);\n  vterm_allocator_free(state->vt, state);\n}\n\nstatic void scroll(VTermState *state, VTermRect rect, int downward, int rightward)\n{\n  if(!downward && !rightward)\n    return;\n\n  int rows = rect.end_row - rect.start_row;\n  if(downward > rows)\n    downward = rows;\n  else if(downward < -rows)\n    downward = -rows;\n\n  int cols = rect.end_col - rect.start_col;\n  if(rightward > cols)\n    rightward = cols;\n  else if(rightward < -cols)\n    rightward = -cols;\n\n  if(state->callbacks_has_premove && state->callbacks && state->callbacks->premove) {\n    // TODO: technically this logic is wrong if both downward != 0 and rightward != 0\n\n    /* Work out what subsection of the destination area is about to be destroyed */\n    if(downward > 0)\n      /* about to destroy the top */\n      (*state->callbacks->premove)((VTermRect){\n          .start_row = rect.start_row, .end_row = rect.start_row + downward,\n          .start_col = rect.start_col, .end_col = rect.end_col}, state->cbdata);\n    else if(downward < 0)\n      /* about to destroy the bottom */\n      (*state->callbacks->premove)((VTermRect){\n          .start_row = rect.end_row + downward, .end_row = rect.end_row,\n          .start_col = rect.start_col,          .end_col = rect.end_col}, state->cbdata);\n\n    if(rightward > 0)\n      /* about to destroy the left */\n      (*state->callbacks->premove)((VTermRect){\n          .start_row = rect.start_row, .end_row = rect.end_row,\n          .start_col = rect.start_col, .end_col = rect.start_col + rightward}, state->cbdata);\n    else if(rightward < 0)\n      /* about to destroy the right */\n      (*state->callbacks->premove)((VTermRect){\n          .start_row = rect.start_row,           .end_row = rect.end_row,\n          .start_col = rect.end_col + rightward, .end_col = rect.end_col}, state->cbdata);\n  }\n\n  // Update lineinfo if full line\n  if(rect.start_col == 0 && rect.end_col == state->cols && rightward == 0) {\n    int height = rect.end_row - rect.start_row - abs(downward);\n\n    if(downward > 0) {\n      memmove(state->lineinfo + rect.start_row,\n              state->lineinfo + rect.start_row + downward,\n              height * sizeof(state->lineinfo[0]));\n      for(int row = rect.end_row - downward; row < rect.end_row; row++)\n        state->lineinfo[row] = (VTermLineInfo){ 0 };\n    }\n    else {\n      memmove(state->lineinfo + rect.start_row - downward,\n              state->lineinfo + rect.start_row,\n              height * sizeof(state->lineinfo[0]));\n      for(int row = rect.start_row; row < rect.start_row - downward; row++)\n        state->lineinfo[row] = (VTermLineInfo){ 0 };\n    }\n  }\n\n  if(state->callbacks && state->callbacks->scrollrect)\n    if((*state->callbacks->scrollrect)(rect, downward, rightward, state->cbdata))\n      return;\n\n  if(state->callbacks)\n    vterm_scroll_rect(rect, downward, rightward,\n        state->callbacks->moverect, state->callbacks->erase, state->cbdata);\n}\n\nstatic void linefeed(VTermState *state)\n{\n  if(state->pos.row == SCROLLREGION_BOTTOM(state) - 1) {\n    VTermRect rect = {\n      .start_row = state->scrollregion_top,\n      .end_row   = SCROLLREGION_BOTTOM(state),\n      .start_col = SCROLLREGION_LEFT(state),\n      .end_col   = SCROLLREGION_RIGHT(state),\n    };\n\n    scroll(state, rect, 1, 0);\n  }\n  else if(state->pos.row < state->rows-1)\n    state->pos.row++;\n}\n\nstatic void grow_combine_buffer(VTermState *state)\n{\n  size_t    new_size = state->combine_chars_size * 2;\n  uint32_t *new_chars = vterm_allocator_malloc(state->vt, new_size * sizeof(new_chars[0]));\n\n  memcpy(new_chars, state->combine_chars, state->combine_chars_size * sizeof(new_chars[0]));\n\n  vterm_allocator_free(state->vt, state->combine_chars);\n\n  state->combine_chars = new_chars;\n  state->combine_chars_size = new_size;\n}\n\nstatic void set_col_tabstop(VTermState *state, int col)\n{\n  unsigned char mask = 1 << (col & 7);\n  state->tabstops[col >> 3] |= mask;\n}\n\nstatic void clear_col_tabstop(VTermState *state, int col)\n{\n  unsigned char mask = 1 << (col & 7);\n  state->tabstops[col >> 3] &= ~mask;\n}\n\nstatic int is_col_tabstop(VTermState *state, int col)\n{\n  unsigned char mask = 1 << (col & 7);\n  return state->tabstops[col >> 3] & mask;\n}\n\nstatic int is_cursor_in_scrollregion(const VTermState *state)\n{\n  if(state->pos.row < state->scrollregion_top ||\n     state->pos.row >= SCROLLREGION_BOTTOM(state))\n    return 0;\n  if(state->pos.col < SCROLLREGION_LEFT(state) ||\n     state->pos.col >= SCROLLREGION_RIGHT(state))\n    return 0;\n\n  return 1;\n}\n\nstatic void tab(VTermState *state, int count, int direction)\n{\n  while(count > 0) {\n    if(direction > 0) {\n      if(state->pos.col >= THISROWWIDTH(state)-1)\n        return;\n\n      state->pos.col++;\n    }\n    else if(direction < 0) {\n      if(state->pos.col < 1)\n        return;\n\n      state->pos.col--;\n    }\n\n    if(is_col_tabstop(state, state->pos.col))\n      count--;\n  }\n}\n\n#define NO_FORCE 0\n#define FORCE    1\n\n#define DWL_OFF 0\n#define DWL_ON  1\n\n#define DHL_OFF    0\n#define DHL_TOP    1\n#define DHL_BOTTOM 2\n\nstatic void set_lineinfo(VTermState *state, int row, int force, int dwl, int dhl)\n{\n  VTermLineInfo info = state->lineinfo[row];\n\n  if(dwl == DWL_OFF)\n    info.doublewidth = DWL_OFF;\n  else if(dwl == DWL_ON)\n    info.doublewidth = DWL_ON;\n  // else -1 to ignore\n\n  if(dhl == DHL_OFF)\n    info.doubleheight = DHL_OFF;\n  else if(dhl == DHL_TOP)\n    info.doubleheight = DHL_TOP;\n  else if(dhl == DHL_BOTTOM)\n    info.doubleheight = DHL_BOTTOM;\n\n  if((state->callbacks &&\n      state->callbacks->setlineinfo &&\n      (*state->callbacks->setlineinfo)(row, &info, state->lineinfo + row, state->cbdata))\n      || force)\n    state->lineinfo[row] = info;\n}\n\nstatic int on_text(const char bytes[], size_t len, void *user)\n{\n  VTermState *state = user;\n\n  VTermPos oldpos = state->pos;\n\n  uint32_t *codepoints = (uint32_t *)(state->vt->tmpbuffer);\n  size_t maxpoints = (state->vt->tmpbuffer_len) / sizeof(uint32_t);\n\n  int npoints = 0;\n  size_t eaten = 0;\n\n  VTermEncodingInstance *encoding =\n    state->gsingle_set     ? &state->encoding[state->gsingle_set] :\n    !(bytes[eaten] & 0x80) ? &state->encoding[state->gl_set] :\n    state->vt->mode.utf8   ? &state->encoding_utf8 :\n                             &state->encoding[state->gr_set];\n\n  (*encoding->enc->decode)(encoding->enc, encoding->data,\n      codepoints, &npoints, state->gsingle_set ? 1 : maxpoints,\n      bytes, &eaten, len);\n\n  /* There's a chance an encoding (e.g. UTF-8) hasn't found enough bytes yet\n   * for even a single codepoint\n   */\n  if(!npoints)\n    return eaten;\n\n  if(state->gsingle_set && npoints)\n    state->gsingle_set = 0;\n\n  int i = 0;\n\n  /* This is a combining char. that needs to be merged with the previous\n   * glyph output */\n  if(vterm_unicode_is_combining(codepoints[i])) {\n    /* See if the cursor has moved since */\n    if(state->pos.row == state->combine_pos.row && state->pos.col == state->combine_pos.col + state->combine_width) {\n#ifdef DEBUG_GLYPH_COMBINE\n      int printpos;\n      printf(\"DEBUG: COMBINING SPLIT GLYPH of chars {\");\n      for(printpos = 0; state->combine_chars[printpos]; printpos++)\n        printf(\"U+%04x \", state->combine_chars[printpos]);\n      printf(\"} + {\");\n#endif\n\n      /* Find where we need to append these combining chars */\n      int saved_i = 0;\n      while(state->combine_chars[saved_i])\n        saved_i++;\n\n      /* Add extra ones */\n      while(i < npoints && vterm_unicode_is_combining(codepoints[i])) {\n        if(saved_i >= state->combine_chars_size)\n          grow_combine_buffer(state);\n        state->combine_chars[saved_i++] = codepoints[i++];\n      }\n      if(saved_i >= state->combine_chars_size)\n        grow_combine_buffer(state);\n      state->combine_chars[saved_i] = 0;\n\n#ifdef DEBUG_GLYPH_COMBINE\n      for(; state->combine_chars[printpos]; printpos++)\n        printf(\"U+%04x \", state->combine_chars[printpos]);\n      printf(\"}\\n\");\n#endif\n\n      /* Now render it */\n      putglyph(state, state->combine_chars, state->combine_width, state->combine_pos);\n    }\n    else {\n      DEBUG_LOG(\"libvterm: TODO: Skip over split char+combining\\n\");\n    }\n  }\n\n  for(; i < npoints; i++) {\n    // Try to find combining characters following this\n    int glyph_starts = i;\n    int glyph_ends;\n    for(glyph_ends = i + 1;\n        (glyph_ends < npoints) && (glyph_ends < glyph_starts + VTERM_MAX_CHARS_PER_CELL);\n        glyph_ends++)\n      if(!vterm_unicode_is_combining(codepoints[glyph_ends]))\n        break;\n\n    int width = 0;\n\n    uint32_t chars[VTERM_MAX_CHARS_PER_CELL + 1];\n\n    for( ; i < glyph_ends; i++) {\n      chars[i - glyph_starts] = codepoints[i];\n      int this_width = vterm_unicode_width(codepoints[i]);\n#ifdef DEBUG\n      if(this_width < 0) {\n        fprintf(stderr, \"Text with negative-width codepoint U+%04x\\n\", codepoints[i]);\n        abort();\n      }\n#endif\n      width += this_width;\n    }\n\n    while(i < npoints && vterm_unicode_is_combining(codepoints[i]))\n      i++;\n\n    chars[glyph_ends - glyph_starts] = 0;\n    i--;\n\n#ifdef DEBUG_GLYPH_COMBINE\n    int printpos;\n    printf(\"DEBUG: COMBINED GLYPH of %d chars {\", glyph_ends - glyph_starts);\n    for(printpos = 0; printpos < glyph_ends - glyph_starts; printpos++)\n      printf(\"U+%04x \", chars[printpos]);\n    printf(\"}, onscreen width %d\\n\", width);\n#endif\n\n    if(state->at_phantom || state->pos.col + width > THISROWWIDTH(state)) {\n      linefeed(state);\n      state->pos.col = 0;\n      state->at_phantom = 0;\n      state->lineinfo[state->pos.row].continuation = 1;\n    }\n\n    if(state->mode.insert) {\n      /* TODO: This will be a little inefficient for large bodies of text, as\n       * it'll have to 'ICH' effectively before every glyph. We should scan\n       * ahead and ICH as many times as required\n       */\n      VTermRect rect = {\n        .start_row = state->pos.row,\n        .end_row   = state->pos.row + 1,\n        .start_col = state->pos.col,\n        .end_col   = THISROWWIDTH(state),\n      };\n      scroll(state, rect, 0, -1);\n    }\n\n    putglyph(state, chars, width, state->pos);\n\n    if(i == npoints - 1) {\n      /* End of the buffer. Save the chars in case we have to combine with\n       * more on the next call */\n      int save_i;\n      for(save_i = 0; chars[save_i]; save_i++) {\n        if(save_i >= state->combine_chars_size)\n          grow_combine_buffer(state);\n        state->combine_chars[save_i] = chars[save_i];\n      }\n      if(save_i >= state->combine_chars_size)\n        grow_combine_buffer(state);\n      state->combine_chars[save_i] = 0;\n      state->combine_width = width;\n      state->combine_pos = state->pos;\n    }\n\n    if(state->pos.col + width >= THISROWWIDTH(state)) {\n      if(state->mode.autowrap)\n        state->at_phantom = 1;\n    }\n    else {\n      state->pos.col += width;\n    }\n  }\n\n  updatecursor(state, &oldpos, 0);\n\n#ifdef DEBUG\n  if(state->pos.row < 0 || state->pos.row >= state->rows ||\n     state->pos.col < 0 || state->pos.col >= state->cols) {\n    fprintf(stderr, \"Position out of bounds after text: (%d,%d)\\n\",\n        state->pos.row, state->pos.col);\n    abort();\n  }\n#endif\n\n  return eaten;\n}\n\nstatic int on_control(unsigned char control, void *user)\n{\n  VTermState *state = user;\n\n  VTermPos oldpos = state->pos;\n\n  switch(control) {\n  case 0x07: // BEL - ECMA-48 8.3.3\n    if(state->callbacks && state->callbacks->bell)\n      (*state->callbacks->bell)(state->cbdata);\n    break;\n\n  case 0x08: // BS - ECMA-48 8.3.5\n    if(state->pos.col > 0)\n      state->pos.col--;\n    break;\n\n  case 0x09: // HT - ECMA-48 8.3.60\n    tab(state, 1, +1);\n    break;\n\n  case 0x0a: // LF - ECMA-48 8.3.74\n  case 0x0b: // VT\n  case 0x0c: // FF\n    linefeed(state);\n    if(state->mode.newline)\n      state->pos.col = 0;\n    break;\n\n  case 0x0d: // CR - ECMA-48 8.3.15\n    state->pos.col = 0;\n    break;\n\n  case 0x0e: // LS1 - ECMA-48 8.3.76\n    state->gl_set = 1;\n    break;\n\n  case 0x0f: // LS0 - ECMA-48 8.3.75\n    state->gl_set = 0;\n    break;\n\n  case 0x84: // IND - DEPRECATED but implemented for completeness\n    linefeed(state);\n    break;\n\n  case 0x85: // NEL - ECMA-48 8.3.86\n    linefeed(state);\n    state->pos.col = 0;\n    break;\n\n  case 0x88: // HTS - ECMA-48 8.3.62\n    set_col_tabstop(state, state->pos.col);\n    break;\n\n  case 0x8d: // RI - ECMA-48 8.3.104\n    if(state->pos.row == state->scrollregion_top) {\n      VTermRect rect = {\n        .start_row = state->scrollregion_top,\n        .end_row   = SCROLLREGION_BOTTOM(state),\n        .start_col = SCROLLREGION_LEFT(state),\n        .end_col   = SCROLLREGION_RIGHT(state),\n      };\n\n      scroll(state, rect, -1, 0);\n    }\n    else if(state->pos.row > 0)\n        state->pos.row--;\n    break;\n\n  case 0x8e: // SS2 - ECMA-48 8.3.141\n    state->gsingle_set = 2;\n    break;\n\n  case 0x8f: // SS3 - ECMA-48 8.3.142\n    state->gsingle_set = 3;\n    break;\n\n  default:\n    if(state->fallbacks && state->fallbacks->control)\n      if((*state->fallbacks->control)(control, state->fbdata))\n        return 1;\n\n    return 0;\n  }\n\n  updatecursor(state, &oldpos, 1);\n\n#ifdef DEBUG\n  if(state->pos.row < 0 || state->pos.row >= state->rows ||\n     state->pos.col < 0 || state->pos.col >= state->cols) {\n    fprintf(stderr, \"Position out of bounds after Ctrl %02x: (%d,%d)\\n\",\n        control, state->pos.row, state->pos.col);\n    abort();\n  }\n#endif\n\n  return 1;\n}\n\nstatic int settermprop_bool(VTermState *state, VTermProp prop, int v)\n{\n  VTermValue val = { .boolean = v };\n  return vterm_state_set_termprop(state, prop, &val);\n}\n\nstatic int settermprop_int(VTermState *state, VTermProp prop, int v)\n{\n  VTermValue val = { .number = v };\n  return vterm_state_set_termprop(state, prop, &val);\n}\n\nstatic int settermprop_string(VTermState *state, VTermProp prop, VTermStringFragment frag)\n{\n  VTermValue val = { .string = frag };\n  return vterm_state_set_termprop(state, prop, &val);\n}\n\nstatic void savecursor(VTermState *state, int save)\n{\n  if(save) {\n    state->saved.pos = state->pos;\n    state->saved.mode.cursor_visible = state->mode.cursor_visible;\n    state->saved.mode.cursor_blink   = state->mode.cursor_blink;\n    state->saved.mode.cursor_shape   = state->mode.cursor_shape;\n\n    vterm_state_savepen(state, 1);\n  }\n  else {\n    VTermPos oldpos = state->pos;\n\n    state->pos = state->saved.pos;\n\n    settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, state->saved.mode.cursor_visible);\n    settermprop_bool(state, VTERM_PROP_CURSORBLINK,   state->saved.mode.cursor_blink);\n    settermprop_int (state, VTERM_PROP_CURSORSHAPE,   state->saved.mode.cursor_shape);\n\n    vterm_state_savepen(state, 0);\n\n    updatecursor(state, &oldpos, 1);\n  }\n}\n\nstatic int on_escape(const char *bytes, size_t len, void *user)\n{\n  VTermState *state = user;\n\n  /* Easier to decode this from the first byte, even though the final\n   * byte terminates it\n   */\n  switch(bytes[0]) {\n  case ' ':\n    if(len != 2)\n      return 0;\n\n    switch(bytes[1]) {\n      case 'F': // S7C1T\n        state->vt->mode.ctrl8bit = 0;\n        break;\n\n      case 'G': // S8C1T\n        state->vt->mode.ctrl8bit = 1;\n        break;\n\n      default:\n        return 0;\n    }\n    return 2;\n\n  case '#':\n    if(len != 2)\n      return 0;\n\n    switch(bytes[1]) {\n      case '3': // DECDHL top\n        if(state->mode.leftrightmargin)\n          break;\n        set_lineinfo(state, state->pos.row, NO_FORCE, DWL_ON, DHL_TOP);\n        break;\n\n      case '4': // DECDHL bottom\n        if(state->mode.leftrightmargin)\n          break;\n        set_lineinfo(state, state->pos.row, NO_FORCE, DWL_ON, DHL_BOTTOM);\n        break;\n\n      case '5': // DECSWL\n        if(state->mode.leftrightmargin)\n          break;\n        set_lineinfo(state, state->pos.row, NO_FORCE, DWL_OFF, DHL_OFF);\n        break;\n\n      case '6': // DECDWL\n        if(state->mode.leftrightmargin)\n          break;\n        set_lineinfo(state, state->pos.row, NO_FORCE, DWL_ON, DHL_OFF);\n        break;\n\n      case '8': // DECALN\n      {\n        VTermPos pos;\n        uint32_t E[] = { 'E', 0 };\n        for(pos.row = 0; pos.row < state->rows; pos.row++)\n          for(pos.col = 0; pos.col < ROWWIDTH(state, pos.row); pos.col++)\n            putglyph(state, E, 1, pos);\n        break;\n      }\n\n      default:\n        return 0;\n    }\n    return 2;\n\n  case '(': case ')': case '*': case '+': // SCS\n    if(len != 2)\n      return 0;\n\n    {\n      int setnum = bytes[0] - 0x28;\n      VTermEncoding *newenc = vterm_lookup_encoding(ENC_SINGLE_94, bytes[1]);\n\n      if(newenc) {\n        state->encoding[setnum].enc = newenc;\n\n        if(newenc->init)\n          (*newenc->init)(newenc, state->encoding[setnum].data);\n      }\n    }\n\n    return 2;\n\n  case '7': // DECSC\n    savecursor(state, 1);\n    return 1;\n\n  case '8': // DECRC\n    savecursor(state, 0);\n    return 1;\n\n  case '<': // Ignored by VT100. Used in VT52 mode to switch up to VT100\n    return 1;\n\n  case '=': // DECKPAM\n    state->mode.keypad = 1;\n    return 1;\n\n  case '>': // DECKPNM\n    state->mode.keypad = 0;\n    return 1;\n\n  case 'c': // RIS - ECMA-48 8.3.105\n  {\n    VTermPos oldpos = state->pos;\n    vterm_state_reset(state, 1);\n    if(state->callbacks && state->callbacks->movecursor)\n      (*state->callbacks->movecursor)(state->pos, oldpos, state->mode.cursor_visible, state->cbdata);\n    return 1;\n  }\n\n  case 'n': // LS2 - ECMA-48 8.3.78\n    state->gl_set = 2;\n    return 1;\n\n  case 'o': // LS3 - ECMA-48 8.3.80\n    state->gl_set = 3;\n    return 1;\n\n  case '~': // LS1R - ECMA-48 8.3.77\n    state->gr_set = 1;\n    return 1;\n\n  case '}': // LS2R - ECMA-48 8.3.79\n    state->gr_set = 2;\n    return 1;\n\n  case '|': // LS3R - ECMA-48 8.3.81\n    state->gr_set = 3;\n    return 1;\n\n  default:\n    return 0;\n  }\n}\n\nstatic void set_mode(VTermState *state, int num, int val)\n{\n  switch(num) {\n  case 4: // IRM - ECMA-48 7.2.10\n    state->mode.insert = val;\n    break;\n\n  case 20: // LNM - ANSI X3.4-1977\n    state->mode.newline = val;\n    break;\n\n  default:\n    DEBUG_LOG(\"libvterm: Unknown mode %d\\n\", num);\n    return;\n  }\n}\n\nstatic void set_dec_mode(VTermState *state, int num, int val)\n{\n  switch(num) {\n  case 1:\n    state->mode.cursor = val;\n    break;\n\n  case 5: // DECSCNM - screen mode\n    settermprop_bool(state, VTERM_PROP_REVERSE, val);\n    break;\n\n  case 6: // DECOM - origin mode\n    {\n      VTermPos oldpos = state->pos;\n      state->mode.origin = val;\n      state->pos.row = state->mode.origin ? state->scrollregion_top : 0;\n      state->pos.col = state->mode.origin ? SCROLLREGION_LEFT(state) : 0;\n      updatecursor(state, &oldpos, 1);\n    }\n    break;\n\n  case 7:\n    state->mode.autowrap = val;\n    break;\n\n  case 12:\n    settermprop_bool(state, VTERM_PROP_CURSORBLINK, val);\n    break;\n\n  case 25:\n    settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, val);\n    break;\n\n  case 69: // DECVSSM - vertical split screen mode\n           // DECLRMM - left/right margin mode\n    state->mode.leftrightmargin = val;\n    if(val) {\n      // Setting DECVSSM must clear doublewidth/doubleheight state of every line\n      for(int row = 0; row < state->rows; row++)\n        set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF);\n    }\n\n    break;\n\n  case 1000:\n  case 1002:\n  case 1003:\n    settermprop_int(state, VTERM_PROP_MOUSE,\n        !val          ? VTERM_PROP_MOUSE_NONE  :\n        (num == 1000) ? VTERM_PROP_MOUSE_CLICK :\n        (num == 1002) ? VTERM_PROP_MOUSE_DRAG  :\n                        VTERM_PROP_MOUSE_MOVE);\n    break;\n\n  case 1004:\n    settermprop_bool(state, VTERM_PROP_FOCUSREPORT, val);\n    state->mode.report_focus = val;\n    break;\n\n  case 1005:\n    state->mouse_protocol = val ? MOUSE_UTF8 : MOUSE_X10;\n    break;\n\n  case 1006:\n    state->mouse_protocol = val ? MOUSE_SGR : MOUSE_X10;\n    break;\n\n  case 1015:\n    state->mouse_protocol = val ? MOUSE_RXVT : MOUSE_X10;\n    break;\n\n  case 1047:\n    settermprop_bool(state, VTERM_PROP_ALTSCREEN, val);\n    break;\n\n  case 1048:\n    savecursor(state, val);\n    break;\n\n  case 1049:\n    settermprop_bool(state, VTERM_PROP_ALTSCREEN, val);\n    savecursor(state, val);\n    break;\n\n  case 2004:\n    state->mode.bracketpaste = val;\n    break;\n\n  default:\n    DEBUG_LOG(\"libvterm: Unknown DEC mode %d\\n\", num);\n    return;\n  }\n}\n\nstatic void request_dec_mode(VTermState *state, int num)\n{\n  int reply;\n\n  switch(num) {\n    case 1:\n      reply = state->mode.cursor;\n      break;\n\n    case 5:\n      reply = state->mode.screen;\n      break;\n\n    case 6:\n      reply = state->mode.origin;\n      break;\n\n    case 7:\n      reply = state->mode.autowrap;\n      break;\n\n    case 12:\n      reply = state->mode.cursor_blink;\n      break;\n\n    case 25:\n      reply = state->mode.cursor_visible;\n      break;\n\n    case 69:\n      reply = state->mode.leftrightmargin;\n      break;\n\n    case 1000:\n      reply = state->mouse_flags == MOUSE_WANT_CLICK;\n      break;\n\n    case 1002:\n      reply = state->mouse_flags == (MOUSE_WANT_CLICK|MOUSE_WANT_DRAG);\n      break;\n\n    case 1003:\n      reply = state->mouse_flags == (MOUSE_WANT_CLICK|MOUSE_WANT_MOVE);\n      break;\n\n    case 1004:\n      reply = state->mode.report_focus;\n      break;\n\n    case 1005:\n      reply = state->mouse_protocol == MOUSE_UTF8;\n      break;\n\n    case 1006:\n      reply = state->mouse_protocol == MOUSE_SGR;\n      break;\n\n    case 1015:\n      reply = state->mouse_protocol == MOUSE_RXVT;\n      break;\n\n    case 1047:\n      reply = state->mode.alt_screen;\n      break;\n\n    case 2004:\n      reply = state->mode.bracketpaste;\n      break;\n\n    default:\n      vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, \"?%d;%d$y\", num, 0);\n      return;\n  }\n\n  vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, \"?%d;%d$y\", num, reply ? 1 : 2);\n}\n\nstatic void request_version_string(VTermState *state)\n{\n  vterm_push_output_sprintf_str(state->vt, C1_DCS, true, \">|libvterm(%d.%d)\",\n      VTERM_VERSION_MAJOR, VTERM_VERSION_MINOR);\n}\n\nstatic int on_csi(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user)\n{\n  VTermState *state = user;\n  int leader_byte = 0;\n  int intermed_byte = 0;\n  int cancel_phantom = 1;\n\n  if(leader && leader[0]) {\n    if(leader[1]) // longer than 1 char\n      return 0;\n\n    switch(leader[0]) {\n    case '?':\n    case '>':\n      leader_byte = leader[0];\n      break;\n    default:\n      return 0;\n    }\n  }\n\n  if(intermed && intermed[0]) {\n    if(intermed[1]) // longer than 1 char\n      return 0;\n\n    switch(intermed[0]) {\n    case ' ':\n    case '!':\n    case '\"':\n    case '$':\n    case '\\'':\n      intermed_byte = intermed[0];\n      break;\n    default:\n      return 0;\n    }\n  }\n\n  VTermPos oldpos = state->pos;\n\n  // Some temporaries for later code\n  int count, val;\n  int row, col;\n  VTermRect rect;\n  int selective;\n\n#define LBOUND(v,min) if((v) < (min)) (v) = (min)\n#define UBOUND(v,max) if((v) > (max)) (v) = (max)\n\n#define LEADER(l,b) ((l << 8) | b)\n#define INTERMED(i,b) ((i << 16) | b)\n\n  switch(intermed_byte << 16 | leader_byte << 8 | command) {\n  case 0x40: // ICH - ECMA-48 8.3.64\n    count = CSI_ARG_COUNT(args[0]);\n\n    if(!is_cursor_in_scrollregion(state))\n      break;\n\n    rect.start_row = state->pos.row;\n    rect.end_row   = state->pos.row + 1;\n    rect.start_col = state->pos.col;\n    if(state->mode.leftrightmargin)\n      rect.end_col = SCROLLREGION_RIGHT(state);\n    else\n      rect.end_col = THISROWWIDTH(state);\n\n    scroll(state, rect, 0, -count);\n\n    break;\n\n  case 0x41: // CUU - ECMA-48 8.3.22\n    count = CSI_ARG_COUNT(args[0]);\n    state->pos.row -= count;\n    state->at_phantom = 0;\n    break;\n\n  case 0x42: // CUD - ECMA-48 8.3.19\n    count = CSI_ARG_COUNT(args[0]);\n    state->pos.row += count;\n    state->at_phantom = 0;\n    break;\n\n  case 0x43: // CUF - ECMA-48 8.3.20\n    count = CSI_ARG_COUNT(args[0]);\n    state->pos.col += count;\n    state->at_phantom = 0;\n    break;\n\n  case 0x44: // CUB - ECMA-48 8.3.18\n    count = CSI_ARG_COUNT(args[0]);\n    state->pos.col -= count;\n    state->at_phantom = 0;\n    break;\n\n  case 0x45: // CNL - ECMA-48 8.3.12\n    count = CSI_ARG_COUNT(args[0]);\n    state->pos.col = 0;\n    state->pos.row += count;\n    state->at_phantom = 0;\n    break;\n\n  case 0x46: // CPL - ECMA-48 8.3.13\n    count = CSI_ARG_COUNT(args[0]);\n    state->pos.col = 0;\n    state->pos.row -= count;\n    state->at_phantom = 0;\n    break;\n\n  case 0x47: // CHA - ECMA-48 8.3.9\n    val = CSI_ARG_OR(args[0], 1);\n    state->pos.col = val-1;\n    state->at_phantom = 0;\n    break;\n\n  case 0x48: // CUP - ECMA-48 8.3.21\n    row = CSI_ARG_OR(args[0], 1);\n    col = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? 1 : CSI_ARG(args[1]);\n    // zero-based\n    state->pos.row = row-1;\n    state->pos.col = col-1;\n    if(state->mode.origin) {\n      state->pos.row += state->scrollregion_top;\n      state->pos.col += SCROLLREGION_LEFT(state);\n    }\n    state->at_phantom = 0;\n    break;\n\n  case 0x49: // CHT - ECMA-48 8.3.10\n    count = CSI_ARG_COUNT(args[0]);\n    tab(state, count, +1);\n    break;\n\n  case 0x4a: // ED - ECMA-48 8.3.39\n  case LEADER('?', 0x4a): // DECSED - Selective Erase in Display\n    selective = (leader_byte == '?');\n    switch(CSI_ARG(args[0])) {\n    case CSI_ARG_MISSING:\n    case 0:\n      rect.start_row = state->pos.row; rect.end_row = state->pos.row + 1;\n      rect.start_col = state->pos.col; rect.end_col = state->cols;\n      if(rect.end_col > rect.start_col)\n        erase(state, rect, selective);\n\n      rect.start_row = state->pos.row + 1; rect.end_row = state->rows;\n      rect.start_col = 0;\n      for(int row = rect.start_row; row < rect.end_row; row++)\n        set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF);\n      if(rect.end_row > rect.start_row)\n        erase(state, rect, selective);\n      break;\n\n    case 1:\n      rect.start_row = 0; rect.end_row = state->pos.row;\n      rect.start_col = 0; rect.end_col = state->cols;\n      for(int row = rect.start_row; row < rect.end_row; row++)\n        set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF);\n      if(rect.end_col > rect.start_col)\n        erase(state, rect, selective);\n\n      rect.start_row = state->pos.row; rect.end_row = state->pos.row + 1;\n                          rect.end_col = state->pos.col + 1;\n      if(rect.end_row > rect.start_row)\n        erase(state, rect, selective);\n      break;\n\n    case 2:\n      rect.start_row = 0; rect.end_row = state->rows;\n      rect.start_col = 0; rect.end_col = state->cols;\n      for(int row = rect.start_row; row < rect.end_row; row++)\n        set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF);\n      erase(state, rect, selective);\n      break;\n\n    case 3:\n      if(state->callbacks && state->callbacks->sb_clear)\n        if((*state->callbacks->sb_clear)(state->cbdata))\n          return 1;\n      break;\n    }\n    break;\n\n  case 0x4b: // EL - ECMA-48 8.3.41\n  case LEADER('?', 0x4b): // DECSEL - Selective Erase in Line\n    selective = (leader_byte == '?');\n    rect.start_row = state->pos.row;\n    rect.end_row   = state->pos.row + 1;\n\n    switch(CSI_ARG(args[0])) {\n    case CSI_ARG_MISSING:\n    case 0:\n      rect.start_col = state->pos.col; rect.end_col = THISROWWIDTH(state); break;\n    case 1:\n      rect.start_col = 0; rect.end_col = state->pos.col + 1; break;\n    case 2:\n      rect.start_col = 0; rect.end_col = THISROWWIDTH(state); break;\n    default:\n      return 0;\n    }\n\n    if(rect.end_col > rect.start_col)\n      erase(state, rect, selective);\n\n    break;\n\n  case 0x4c: // IL - ECMA-48 8.3.67\n    count = CSI_ARG_COUNT(args[0]);\n\n    if(!is_cursor_in_scrollregion(state))\n      break;\n\n    rect.start_row = state->pos.row;\n    rect.end_row   = SCROLLREGION_BOTTOM(state);\n    rect.start_col = SCROLLREGION_LEFT(state);\n    rect.end_col   = SCROLLREGION_RIGHT(state);\n\n    scroll(state, rect, -count, 0);\n\n    break;\n\n  case 0x4d: // DL - ECMA-48 8.3.32\n    count = CSI_ARG_COUNT(args[0]);\n\n    if(!is_cursor_in_scrollregion(state))\n      break;\n\n    rect.start_row = state->pos.row;\n    rect.end_row   = SCROLLREGION_BOTTOM(state);\n    rect.start_col = SCROLLREGION_LEFT(state);\n    rect.end_col   = SCROLLREGION_RIGHT(state);\n\n    scroll(state, rect, count, 0);\n\n    break;\n\n  case 0x50: // DCH - ECMA-48 8.3.26\n    count = CSI_ARG_COUNT(args[0]);\n\n    if(!is_cursor_in_scrollregion(state))\n      break;\n\n    rect.start_row = state->pos.row;\n    rect.end_row   = state->pos.row + 1;\n    rect.start_col = state->pos.col;\n    if(state->mode.leftrightmargin)\n      rect.end_col = SCROLLREGION_RIGHT(state);\n    else\n      rect.end_col = THISROWWIDTH(state);\n\n    scroll(state, rect, 0, count);\n\n    break;\n\n  case 0x53: // SU - ECMA-48 8.3.147\n    count = CSI_ARG_COUNT(args[0]);\n\n    rect.start_row = state->scrollregion_top;\n    rect.end_row   = SCROLLREGION_BOTTOM(state);\n    rect.start_col = SCROLLREGION_LEFT(state);\n    rect.end_col   = SCROLLREGION_RIGHT(state);\n\n    scroll(state, rect, count, 0);\n\n    break;\n\n  case 0x54: // SD - ECMA-48 8.3.113\n    count = CSI_ARG_COUNT(args[0]);\n\n    rect.start_row = state->scrollregion_top;\n    rect.end_row   = SCROLLREGION_BOTTOM(state);\n    rect.start_col = SCROLLREGION_LEFT(state);\n    rect.end_col   = SCROLLREGION_RIGHT(state);\n\n    scroll(state, rect, -count, 0);\n\n    break;\n\n  case 0x58: // ECH - ECMA-48 8.3.38\n    count = CSI_ARG_COUNT(args[0]);\n\n    rect.start_row = state->pos.row;\n    rect.end_row   = state->pos.row + 1;\n    rect.start_col = state->pos.col;\n    rect.end_col   = state->pos.col + count;\n    UBOUND(rect.end_col, THISROWWIDTH(state));\n\n    erase(state, rect, 0);\n    break;\n\n  case 0x5a: // CBT - ECMA-48 8.3.7\n    count = CSI_ARG_COUNT(args[0]);\n    tab(state, count, -1);\n    break;\n\n  case 0x60: // HPA - ECMA-48 8.3.57\n    col = CSI_ARG_OR(args[0], 1);\n    state->pos.col = col-1;\n    state->at_phantom = 0;\n    break;\n\n  case 0x61: // HPR - ECMA-48 8.3.59\n    count = CSI_ARG_COUNT(args[0]);\n    state->pos.col += count;\n    state->at_phantom = 0;\n    break;\n\n  case 0x62: { // REP - ECMA-48 8.3.103\n    const int row_width = THISROWWIDTH(state);\n    count = CSI_ARG_COUNT(args[0]);\n    col = state->pos.col + count;\n    UBOUND(col, row_width);\n    while (state->pos.col < col) {\n      putglyph(state, state->combine_chars, state->combine_width, state->pos);\n      state->pos.col += state->combine_width;\n    }\n    if (state->pos.col + state->combine_width >= row_width) {\n      if (state->mode.autowrap) {\n        state->at_phantom = 1;\n        cancel_phantom = 0;\n      }\n    }\n    break;\n  }\n\n  case 0x63: // DA - ECMA-48 8.3.24\n    val = CSI_ARG_OR(args[0], 0);\n    if(val == 0)\n      // DEC VT100 response\n      vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, \"?1;2c\");\n    break;\n\n  case LEADER('>', 0x63): // DEC secondary Device Attributes\n    vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, \">%d;%d;%dc\", 0, 100, 0);\n    break;\n\n  case 0x64: // VPA - ECMA-48 8.3.158\n    row = CSI_ARG_OR(args[0], 1);\n    state->pos.row = row-1;\n    if(state->mode.origin)\n      state->pos.row += state->scrollregion_top;\n    state->at_phantom = 0;\n    break;\n\n  case 0x65: // VPR - ECMA-48 8.3.160\n    count = CSI_ARG_COUNT(args[0]);\n    state->pos.row += count;\n    state->at_phantom = 0;\n    break;\n\n  case 0x66: // HVP - ECMA-48 8.3.63\n    row = CSI_ARG_OR(args[0], 1);\n    col = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? 1 : CSI_ARG(args[1]);\n    // zero-based\n    state->pos.row = row-1;\n    state->pos.col = col-1;\n    if(state->mode.origin) {\n      state->pos.row += state->scrollregion_top;\n      state->pos.col += SCROLLREGION_LEFT(state);\n    }\n    state->at_phantom = 0;\n    break;\n\n  case 0x67: // TBC - ECMA-48 8.3.154\n    val = CSI_ARG_OR(args[0], 0);\n\n    switch(val) {\n    case 0:\n      clear_col_tabstop(state, state->pos.col);\n      break;\n    case 3:\n    case 5:\n      for(col = 0; col < state->cols; col++)\n        clear_col_tabstop(state, col);\n      break;\n    case 1:\n    case 2:\n    case 4:\n      break;\n    /* TODO: 1, 2 and 4 aren't meaningful yet without line tab stops */\n    default:\n      return 0;\n    }\n    break;\n\n  case 0x68: // SM - ECMA-48 8.3.125\n    if(!CSI_ARG_IS_MISSING(args[0]))\n      set_mode(state, CSI_ARG(args[0]), 1);\n    break;\n\n  case LEADER('?', 0x68): // DEC private mode set\n    for(int i = 0; i < argcount; i++) {\n      if(!CSI_ARG_IS_MISSING(args[i]))\n        set_dec_mode(state, CSI_ARG(args[i]), 1);\n    }\n    break;\n\n  case 0x6a: // HPB - ECMA-48 8.3.58\n    count = CSI_ARG_COUNT(args[0]);\n    state->pos.col -= count;\n    state->at_phantom = 0;\n    break;\n\n  case 0x6b: // VPB - ECMA-48 8.3.159\n    count = CSI_ARG_COUNT(args[0]);\n    state->pos.row -= count;\n    state->at_phantom = 0;\n    break;\n\n  case 0x6c: // RM - ECMA-48 8.3.106\n    if(!CSI_ARG_IS_MISSING(args[0]))\n      set_mode(state, CSI_ARG(args[0]), 0);\n    break;\n\n  case LEADER('?', 0x6c): // DEC private mode reset\n    for(int i = 0; i < argcount; i++) {\n      if(!CSI_ARG_IS_MISSING(args[i]))\n        set_dec_mode(state, CSI_ARG(args[i]), 0);\n    }\n    break;\n\n  case 0x6d: // SGR - ECMA-48 8.3.117\n    vterm_state_setpen(state, args, argcount);\n    break;\n\n  case LEADER('?', 0x6d): // DECSGR\n    /* No actual DEC terminal recognised these, but some printers did. These\n     * are alternative ways to request subscript/superscript/off\n     */\n    for(int argi = 0; argi < argcount; argi++) {\n      long arg;\n      switch(arg = CSI_ARG(args[argi])) {\n        case 4: // Superscript on\n          arg = 73;\n          vterm_state_setpen(state, &arg, 1);\n          break;\n        case 5: // Subscript on\n          arg = 74;\n          vterm_state_setpen(state, &arg, 1);\n          break;\n        case 24: // Super+subscript off\n          arg = 75;\n          vterm_state_setpen(state, &arg, 1);\n          break;\n      }\n    }\n    break;\n\n  case 0x6e: // DSR - ECMA-48 8.3.35\n  case LEADER('?', 0x6e): // DECDSR\n    val = CSI_ARG_OR(args[0], 0);\n\n    {\n      char *qmark = (leader_byte == '?') ? \"?\" : \"\";\n\n      switch(val) {\n      case 0: case 1: case 2: case 3: case 4:\n        // ignore - these are replies\n        break;\n      case 5:\n        vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, \"%s0n\", qmark);\n        break;\n      case 6: // CPR - cursor position report\n        vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, \"%s%d;%dR\", qmark, state->pos.row + 1, state->pos.col + 1);\n        break;\n      }\n    }\n    break;\n\n\n  case INTERMED('!', 0x70): // DECSTR - DEC soft terminal reset\n    vterm_state_reset(state, 0);\n    break;\n\n  case LEADER('?', INTERMED('$', 0x70)):\n    request_dec_mode(state, CSI_ARG(args[0]));\n    break;\n\n  case LEADER('>', 0x71): // XTVERSION - xterm query version string\n    request_version_string(state);\n    break;\n\n  case INTERMED(' ', 0x71): // DECSCUSR - DEC set cursor shape\n    val = CSI_ARG_OR(args[0], 1);\n\n    switch(val) {\n    case 0: case 1:\n      settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1);\n      settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BLOCK);\n      break;\n    case 2:\n      settermprop_bool(state, VTERM_PROP_CURSORBLINK, 0);\n      settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BLOCK);\n      break;\n    case 3:\n      settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1);\n      settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_UNDERLINE);\n      break;\n    case 4:\n      settermprop_bool(state, VTERM_PROP_CURSORBLINK, 0);\n      settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_UNDERLINE);\n      break;\n    case 5:\n      settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1);\n      settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BAR_LEFT);\n      break;\n    case 6:\n      settermprop_bool(state, VTERM_PROP_CURSORBLINK, 0);\n      settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BAR_LEFT);\n      break;\n    }\n\n    break;\n\n  case INTERMED('\"', 0x71): // DECSCA - DEC select character protection attribute\n    val = CSI_ARG_OR(args[0], 0);\n\n    switch(val) {\n    case 0: case 2:\n      state->protected_cell = 0;\n      break;\n    case 1:\n      state->protected_cell = 1;\n      break;\n    }\n\n    break;\n\n  case 0x72: // DECSTBM - DEC custom\n    state->scrollregion_top = CSI_ARG_OR(args[0], 1) - 1;\n    state->scrollregion_bottom = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? -1 : CSI_ARG(args[1]);\n    LBOUND(state->scrollregion_top, 0);\n    UBOUND(state->scrollregion_top, state->rows);\n    LBOUND(state->scrollregion_bottom, -1);\n    if(state->scrollregion_top == 0 && state->scrollregion_bottom == state->rows)\n      state->scrollregion_bottom = -1;\n    else\n      UBOUND(state->scrollregion_bottom, state->rows);\n\n    if(SCROLLREGION_BOTTOM(state) <= state->scrollregion_top) {\n      // Invalid\n      state->scrollregion_top    = 0;\n      state->scrollregion_bottom = -1;\n    }\n\n    // Setting the scrolling region restores the cursor to the home position\n    state->pos.row = 0;\n    state->pos.col = 0;\n    if(state->mode.origin) {\n      state->pos.row += state->scrollregion_top;\n      state->pos.col += SCROLLREGION_LEFT(state);\n    }\n\n    break;\n\n  case 0x73: // DECSLRM - DEC custom\n    // Always allow setting these margins, just they won't take effect without DECVSSM\n    state->scrollregion_left = CSI_ARG_OR(args[0], 1) - 1;\n    state->scrollregion_right = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? -1 : CSI_ARG(args[1]);\n    LBOUND(state->scrollregion_left, 0);\n    UBOUND(state->scrollregion_left, state->cols);\n    LBOUND(state->scrollregion_right, -1);\n    if(state->scrollregion_left == 0 && state->scrollregion_right == state->cols)\n      state->scrollregion_right = -1;\n    else\n      UBOUND(state->scrollregion_right, state->cols);\n\n    if(state->scrollregion_right > -1 &&\n       state->scrollregion_right <= state->scrollregion_left) {\n      // Invalid\n      state->scrollregion_left  = 0;\n      state->scrollregion_right = -1;\n    }\n\n    // Setting the scrolling region restores the cursor to the home position\n    state->pos.row = 0;\n    state->pos.col = 0;\n    if(state->mode.origin) {\n      state->pos.row += state->scrollregion_top;\n      state->pos.col += SCROLLREGION_LEFT(state);\n    }\n\n    break;\n\n  case INTERMED('\\'', 0x7D): // DECIC\n    count = CSI_ARG_COUNT(args[0]);\n\n    if(!is_cursor_in_scrollregion(state))\n      break;\n\n    rect.start_row = state->scrollregion_top;\n    rect.end_row   = SCROLLREGION_BOTTOM(state);\n    rect.start_col = state->pos.col;\n    rect.end_col   = SCROLLREGION_RIGHT(state);\n\n    scroll(state, rect, 0, -count);\n\n    break;\n\n  case INTERMED('\\'', 0x7E): // DECDC\n    count = CSI_ARG_COUNT(args[0]);\n\n    if(!is_cursor_in_scrollregion(state))\n      break;\n\n    rect.start_row = state->scrollregion_top;\n    rect.end_row   = SCROLLREGION_BOTTOM(state);\n    rect.start_col = state->pos.col;\n    rect.end_col   = SCROLLREGION_RIGHT(state);\n\n    scroll(state, rect, 0, count);\n\n    break;\n\n  default:\n    if(state->fallbacks && state->fallbacks->csi)\n      if((*state->fallbacks->csi)(leader, args, argcount, intermed, command, state->fbdata))\n        return 1;\n\n    return 0;\n  }\n\n  if(state->mode.origin) {\n    LBOUND(state->pos.row, state->scrollregion_top);\n    UBOUND(state->pos.row, SCROLLREGION_BOTTOM(state)-1);\n    LBOUND(state->pos.col, SCROLLREGION_LEFT(state));\n    UBOUND(state->pos.col, SCROLLREGION_RIGHT(state)-1);\n  }\n  else {\n    LBOUND(state->pos.row, 0);\n    UBOUND(state->pos.row, state->rows-1);\n    LBOUND(state->pos.col, 0);\n    UBOUND(state->pos.col, THISROWWIDTH(state)-1);\n  }\n\n  updatecursor(state, &oldpos, cancel_phantom);\n\n#ifdef DEBUG\n  if(state->pos.row < 0 || state->pos.row >= state->rows ||\n     state->pos.col < 0 || state->pos.col >= state->cols) {\n    fprintf(stderr, \"Position out of bounds after CSI %c: (%d,%d)\\n\",\n        command, state->pos.row, state->pos.col);\n    abort();\n  }\n\n  if(SCROLLREGION_BOTTOM(state) <= state->scrollregion_top) {\n    fprintf(stderr, \"Scroll region height out of bounds after CSI %c: %d <= %d\\n\",\n        command, SCROLLREGION_BOTTOM(state), state->scrollregion_top);\n    abort();\n  }\n\n  if(SCROLLREGION_RIGHT(state) <= SCROLLREGION_LEFT(state)) {\n    fprintf(stderr, \"Scroll region width out of bounds after CSI %c: %d <= %d\\n\",\n        command, SCROLLREGION_RIGHT(state), SCROLLREGION_LEFT(state));\n    abort();\n  }\n#endif\n\n  return 1;\n}\n\nstatic char base64_one(uint8_t b)\n{\n  if(b < 26)\n    return 'A' + b;\n  else if(b < 52)\n    return 'a' + b - 26;\n  else if(b < 62)\n    return '0' + b - 52;\n  else if(b == 62)\n    return '+';\n  else if(b == 63)\n    return '/';\n  return 0;\n}\n\nstatic uint8_t unbase64one(char c)\n{\n  if(c >= 'A' && c <= 'Z')\n    return c - 'A';\n  else if(c >= 'a' && c <= 'z')\n    return c - 'a' + 26;\n  else if(c >= '0' && c <= '9')\n    return c - '0' + 52;\n  else if(c == '+')\n    return 62;\n  else if(c == '/')\n    return 63;\n\n  return 0xFF;\n}\n\nstatic void osc_selection(VTermState *state, VTermStringFragment frag)\n{\n  if(frag.initial) {\n    state->tmp.selection.mask = 0;\n    state->tmp.selection.state = SELECTION_INITIAL;\n  }\n\n  while(!state->tmp.selection.state && frag.len) {\n    /* Parse selection parameter */\n    switch(frag.str[0]) {\n      case 'c':\n        state->tmp.selection.mask |= VTERM_SELECTION_CLIPBOARD;\n        break;\n      case 'p':\n        state->tmp.selection.mask |= VTERM_SELECTION_PRIMARY;\n        break;\n      case 'q':\n        state->tmp.selection.mask |= VTERM_SELECTION_SECONDARY;\n        break;\n      case 's':\n        state->tmp.selection.mask |= VTERM_SELECTION_SELECT;\n        break;\n      case '0':\n      case '1':\n      case '2':\n      case '3':\n      case '4':\n      case '5':\n      case '6':\n      case '7':\n        state->tmp.selection.mask |= (VTERM_SELECTION_CUT0 << (frag.str[0] - '0'));\n        break;\n\n      case ';':\n        state->tmp.selection.state = SELECTION_SELECTED;\n        if(!state->tmp.selection.mask)\n          state->tmp.selection.mask = VTERM_SELECTION_SELECT|VTERM_SELECTION_CUT0;\n        break;\n    }\n\n    frag.str++;\n    frag.len--;\n  }\n\n  if(!frag.len) {\n    /* Clear selection if we're already finished but didn't do anything */\n    if(frag.final && state->selection.callbacks->set) {\n      (*state->selection.callbacks->set)(state->tmp.selection.mask, (VTermStringFragment){\n              .str     = NULL,\n              .len     = 0,\n              .initial = state->tmp.selection.state != SELECTION_SET,\n              .final   = true,\n            }, state->selection.user);\n    }\n    return;\n  }\n\n  if(state->tmp.selection.state == SELECTION_SELECTED) {\n    if(frag.str[0] == '?') {\n      state->tmp.selection.state = SELECTION_QUERY;\n    }\n    else {\n      state->tmp.selection.state = SELECTION_SET_INITIAL;\n      state->tmp.selection.recvpartial = 0;\n    }\n  }\n\n  if(state->tmp.selection.state == SELECTION_QUERY) {\n    if(state->selection.callbacks->query)\n      (*state->selection.callbacks->query)(state->tmp.selection.mask, state->selection.user);\n    return;\n  }\n\n  if(state->tmp.selection.state == SELECTION_INVALID)\n    return;\n\n  if(state->selection.callbacks->set) {\n    size_t bufcur = 0;\n    char *buffer = state->selection.buffer;\n\n    uint32_t x = 0; /* Current decoding value */\n    int n = 0;      /* Number of sextets consumed */\n\n    if(state->tmp.selection.recvpartial) {\n      n = state->tmp.selection.recvpartial >> 24;\n      x = state->tmp.selection.recvpartial & 0x03FFFF; /* could be up to 18 bits of state in here */\n\n      state->tmp.selection.recvpartial = 0;\n    }\n\n    while((state->selection.buflen - bufcur) >= 3 && frag.len) {\n      if(frag.str[0] == '=') {\n        if(n == 2) {\n          buffer[0] = (x >> 4) & 0xFF;\n          buffer += 1, bufcur += 1;\n        }\n        if(n == 3) {\n          buffer[0] = (x >> 10) & 0xFF;\n          buffer[1] = (x >>  2) & 0xFF;\n          buffer += 2, bufcur += 2;\n        }\n\n        while(frag.len && frag.str[0] == '=')\n          frag.str++, frag.len--;\n\n        n = 0;\n      }\n      else {\n        uint8_t b = unbase64one(frag.str[0]);\n        if(b == 0xFF) {\n          DEBUG_LOG(\"base64decode bad input %02X\\n\", (uint8_t)frag.str[0]);\n\n          state->tmp.selection.state = SELECTION_INVALID;\n          if(state->selection.callbacks->set) {\n            (*state->selection.callbacks->set)(state->tmp.selection.mask, (VTermStringFragment){\n                .str     = NULL,\n                .len     = 0,\n                .initial = true,\n                .final   = true,\n                }, state->selection.user);\n          }\n          break;\n        }\n\n        x = (x << 6) | b;\n        n++;\n        frag.str++, frag.len--;\n\n        if(n == 4) {\n          buffer[0] = (x >> 16) & 0xFF;\n          buffer[1] = (x >>  8) & 0xFF;\n          buffer[2] = (x >>  0) & 0xFF;\n\n          buffer += 3, bufcur += 3;\n          x = 0;\n          n = 0;\n        }\n      }\n\n      if(!frag.len || (state->selection.buflen - bufcur) < 3) {\n        if(bufcur) {\n          (*state->selection.callbacks->set)(state->tmp.selection.mask, (VTermStringFragment){\n              .str     = state->selection.buffer,\n              .len     = bufcur,\n              .initial = state->tmp.selection.state == SELECTION_SET_INITIAL,\n              .final   = frag.final && !frag.len,\n            }, state->selection.user);\n          state->tmp.selection.state = SELECTION_SET;\n        }\n\n        buffer = state->selection.buffer;\n        bufcur = 0;\n      }\n    }\n\n    if(n)\n      state->tmp.selection.recvpartial = (n << 24) | x;\n  }\n}\n\nstatic int on_osc(int command, VTermStringFragment frag, void *user)\n{\n  VTermState *state = user;\n\n  switch(command) {\n    case 0:\n      settermprop_string(state, VTERM_PROP_ICONNAME, frag);\n      settermprop_string(state, VTERM_PROP_TITLE, frag);\n      return 1;\n\n    case 1:\n      settermprop_string(state, VTERM_PROP_ICONNAME, frag);\n      return 1;\n\n    case 2:\n      settermprop_string(state, VTERM_PROP_TITLE, frag);\n      return 1;\n\n    case 52:\n      if(state->selection.callbacks)\n        osc_selection(state, frag);\n\n      return 1;\n\n    default:\n      if(state->fallbacks && state->fallbacks->osc)\n        if((*state->fallbacks->osc)(command, frag, state->fbdata))\n          return 1;\n  }\n\n  return 0;\n}\n\nstatic void request_status_string(VTermState *state, VTermStringFragment frag)\n{\n  VTerm *vt = state->vt;\n\n  char *tmp = state->tmp.decrqss;\n\n  if(frag.initial)\n    tmp[0] = tmp[1] = tmp[2] = tmp[3] = 0;\n\n  int i = 0;\n  while(i < sizeof(state->tmp.decrqss)-1 && tmp[i])\n    i++;\n  while(i < sizeof(state->tmp.decrqss)-1 && frag.len--)\n    tmp[i++] = (frag.str++)[0];\n  tmp[i] = 0;\n\n  if(!frag.final)\n    return;\n\n  switch(tmp[0] | tmp[1]<<8 | tmp[2]<<16) {\n    case 'm': {\n      // Query SGR\n      long args[20];\n      int argc = vterm_state_getpen(state, args, sizeof(args)/sizeof(args[0]));\n      size_t cur = 0;\n\n      cur += snprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur,\n          vt->mode.ctrl8bit ? \"\\x90\" \"1$r\" : ESC_S \"P\" \"1$r\"); // DCS 1$r ...\n      if(cur >= vt->tmpbuffer_len)\n        return;\n\n      for(int argi = 0; argi < argc; argi++) {\n        cur += snprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur,\n            argi == argc - 1             ? \"%ld\" :\n            CSI_ARG_HAS_MORE(args[argi]) ? \"%ld:\" :\n                                           \"%ld;\",\n            CSI_ARG(args[argi]));\n        if(cur >= vt->tmpbuffer_len)\n          return;\n      }\n\n      cur += snprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur,\n          vt->mode.ctrl8bit ? \"m\" \"\\x9C\" : \"m\" ESC_S \"\\\\\"); // ... m ST\n      if(cur >= vt->tmpbuffer_len)\n        return;\n\n      vterm_push_output_bytes(vt, vt->tmpbuffer, cur);\n      return;\n    }\n\n    case 'r':\n      // Query DECSTBM\n      vterm_push_output_sprintf_str(vt, C1_DCS, true,\n          \"1$r%d;%dr\", state->scrollregion_top+1, SCROLLREGION_BOTTOM(state));\n      return;\n\n    case 's':\n      // Query DECSLRM\n      vterm_push_output_sprintf_str(vt, C1_DCS, true,\n          \"1$r%d;%ds\", SCROLLREGION_LEFT(state)+1, SCROLLREGION_RIGHT(state));\n      return;\n\n    case ' '|('q'<<8): {\n      // Query DECSCUSR\n      int reply;\n      switch(state->mode.cursor_shape) {\n        case VTERM_PROP_CURSORSHAPE_BLOCK:     reply = 2; break;\n        case VTERM_PROP_CURSORSHAPE_UNDERLINE: reply = 4; break;\n        case VTERM_PROP_CURSORSHAPE_BAR_LEFT:  reply = 6; break;\n      }\n      if(state->mode.cursor_blink)\n        reply--;\n      vterm_push_output_sprintf_str(vt, C1_DCS, true,\n          \"1$r%d q\", reply);\n      return;\n    }\n\n    case '\\\"'|('q'<<8):\n      // Query DECSCA\n      vterm_push_output_sprintf_str(vt, C1_DCS, true,\n          \"1$r%d\\\"q\", state->protected_cell ? 1 : 2);\n      return;\n  }\n\n  vterm_push_output_sprintf_str(state->vt, C1_DCS, true, \"0$r\");\n}\n\nstatic int on_dcs(const char *command, size_t commandlen, VTermStringFragment frag, void *user)\n{\n  VTermState *state = user;\n\n  if(commandlen == 2 && strneq(command, \"$q\", 2)) {\n    request_status_string(state, frag);\n    return 1;\n  }\n  else if(state->fallbacks && state->fallbacks->dcs)\n    if((*state->fallbacks->dcs)(command, commandlen, frag, state->fbdata))\n      return 1;\n\n  DEBUG_LOG(\"libvterm: Unhandled DCS %.*s\\n\", (int)commandlen, command);\n  return 0;\n}\n\nstatic int on_apc(VTermStringFragment frag, void *user)\n{\n  VTermState *state = user;\n\n  if(state->fallbacks && state->fallbacks->apc)\n    if((*state->fallbacks->apc)(frag, state->fbdata))\n      return 1;\n\n  /* No DEBUG_LOG because all APCs are unhandled */\n  return 0;\n}\n\nstatic int on_pm(VTermStringFragment frag, void *user)\n{\n  VTermState *state = user;\n\n  if(state->fallbacks && state->fallbacks->pm)\n    if((*state->fallbacks->pm)(frag, state->fbdata))\n      return 1;\n\n  /* No DEBUG_LOG because all PMs are unhandled */\n  return 0;\n}\n\nstatic int on_sos(VTermStringFragment frag, void *user)\n{\n  VTermState *state = user;\n\n  if(state->fallbacks && state->fallbacks->sos)\n    if((*state->fallbacks->sos)(frag, state->fbdata))\n      return 1;\n\n  /* No DEBUG_LOG because all SOSs are unhandled */\n  return 0;\n}\n\nstatic int on_resize(int rows, int cols, void *user)\n{\n  VTermState *state = user;\n  VTermPos oldpos = state->pos;\n\n  if(cols != state->cols) {\n    unsigned char *newtabstops = vterm_allocator_malloc(state->vt, (cols + 7) / 8);\n\n    /* TODO: This can all be done much more efficiently bytewise */\n    int col;\n    for(col = 0; col < state->cols && col < cols; col++) {\n      unsigned char mask = 1 << (col & 7);\n      if(state->tabstops[col >> 3] & mask)\n        newtabstops[col >> 3] |= mask;\n      else\n        newtabstops[col >> 3] &= ~mask;\n      }\n\n    for( ; col < cols; col++) {\n      unsigned char mask = 1 << (col & 7);\n      if(col % 8 == 0)\n        newtabstops[col >> 3] |= mask;\n      else\n        newtabstops[col >> 3] &= ~mask;\n    }\n\n    vterm_allocator_free(state->vt, state->tabstops);\n    state->tabstops = newtabstops;\n  }\n\n  state->rows = rows;\n  state->cols = cols;\n\n  if(state->scrollregion_bottom > -1)\n    UBOUND(state->scrollregion_bottom, state->rows);\n  if(state->scrollregion_right > -1)\n    UBOUND(state->scrollregion_right, state->cols);\n\n  VTermStateFields fields = {\n    .pos       = state->pos,\n    .lineinfos = { [0] = state->lineinfos[0], [1] = state->lineinfos[1] },\n  };\n\n  if(state->callbacks && state->callbacks->resize) {\n    (*state->callbacks->resize)(rows, cols, &fields, state->cbdata);\n    state->pos = fields.pos;\n\n    state->lineinfos[0] = fields.lineinfos[0];\n    state->lineinfos[1] = fields.lineinfos[1];\n  }\n  else {\n    if(rows != state->rows) {\n      for(int bufidx = BUFIDX_PRIMARY; bufidx <= BUFIDX_ALTSCREEN; bufidx++) {\n        VTermLineInfo *oldlineinfo = state->lineinfos[bufidx];\n        if(!oldlineinfo)\n          continue;\n\n        VTermLineInfo *newlineinfo = vterm_allocator_malloc(state->vt, rows * sizeof(VTermLineInfo));\n\n        int row;\n        for(row = 0; row < state->rows && row < rows; row++) {\n          newlineinfo[row] = oldlineinfo[row];\n        }\n\n        for( ; row < rows; row++) {\n          newlineinfo[row] = (VTermLineInfo){\n            .doublewidth = 0,\n          };\n        }\n\n        vterm_allocator_free(state->vt, state->lineinfos[bufidx]);\n        state->lineinfos[bufidx] = newlineinfo;\n      }\n    }\n  }\n\n  state->lineinfo = state->lineinfos[state->mode.alt_screen ? BUFIDX_ALTSCREEN : BUFIDX_PRIMARY];\n\n  if(state->at_phantom && state->pos.col < cols-1) {\n    state->at_phantom = 0;\n    state->pos.col++;\n  }\n\n  if(state->pos.row < 0)\n    state->pos.row = 0;\n  if(state->pos.row >= rows)\n    state->pos.row = rows - 1;\n  if(state->pos.col < 0)\n    state->pos.col = 0;\n  if(state->pos.col >= cols)\n    state->pos.col = cols - 1;\n\n  updatecursor(state, &oldpos, 1);\n\n  return 1;\n}\n\nstatic const VTermParserCallbacks parser_callbacks = {\n  .text    = on_text,\n  .control = on_control,\n  .escape  = on_escape,\n  .csi     = on_csi,\n  .osc     = on_osc,\n  .dcs     = on_dcs,\n  .apc     = on_apc,\n  .pm      = on_pm,\n  .sos     = on_sos,\n  .resize  = on_resize,\n};\n\nVTermState *vterm_obtain_state(VTerm *vt)\n{\n  if(vt->state)\n    return vt->state;\n\n  VTermState *state = vterm_state_new(vt);\n  vt->state = state;\n\n  vterm_parser_set_callbacks(vt, &parser_callbacks, state);\n\n  return state;\n}\n\nvoid vterm_state_reset(VTermState *state, int hard)\n{\n  state->scrollregion_top = 0;\n  state->scrollregion_bottom = -1;\n  state->scrollregion_left = 0;\n  state->scrollregion_right = -1;\n\n  state->mode.keypad          = 0;\n  state->mode.cursor          = 0;\n  state->mode.autowrap        = 1;\n  state->mode.insert          = 0;\n  state->mode.newline         = 0;\n  state->mode.alt_screen      = 0;\n  state->mode.origin          = 0;\n  state->mode.leftrightmargin = 0;\n  state->mode.bracketpaste    = 0;\n  state->mode.report_focus    = 0;\n\n  state->mouse_flags = 0;\n\n  state->vt->mode.ctrl8bit   = 0;\n\n  for(int col = 0; col < state->cols; col++)\n    if(col % 8 == 0)\n      set_col_tabstop(state, col);\n    else\n      clear_col_tabstop(state, col);\n\n  for(int row = 0; row < state->rows; row++)\n    set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF);\n\n  if(state->callbacks && state->callbacks->initpen)\n    (*state->callbacks->initpen)(state->cbdata);\n\n  vterm_state_resetpen(state);\n\n  VTermEncoding *default_enc = state->vt->mode.utf8 ?\n      vterm_lookup_encoding(ENC_UTF8,      'u') :\n      vterm_lookup_encoding(ENC_SINGLE_94, 'B');\n\n  for(int i = 0; i < 4; i++) {\n    state->encoding[i].enc = default_enc;\n    if(default_enc->init)\n      (*default_enc->init)(default_enc, state->encoding[i].data);\n  }\n\n  state->gl_set = 0;\n  state->gr_set = 1;\n  state->gsingle_set = 0;\n\n  state->protected_cell = 0;\n\n  // Initialise the props\n  settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, 1);\n  settermprop_bool(state, VTERM_PROP_CURSORBLINK,   1);\n  settermprop_int (state, VTERM_PROP_CURSORSHAPE,   VTERM_PROP_CURSORSHAPE_BLOCK);\n\n  if(hard) {\n    state->pos.row = 0;\n    state->pos.col = 0;\n    state->at_phantom = 0;\n\n    VTermRect rect = { 0, state->rows, 0, state->cols };\n    erase(state, rect, 0);\n  }\n}\n\nvoid vterm_state_get_cursorpos(const VTermState *state, VTermPos *cursorpos)\n{\n  *cursorpos = state->pos;\n}\n\nvoid vterm_state_set_callbacks(VTermState *state, const VTermStateCallbacks *callbacks, void *user)\n{\n  if(callbacks) {\n    state->callbacks = callbacks;\n    state->cbdata = user;\n\n    if(state->callbacks && state->callbacks->initpen)\n      (*state->callbacks->initpen)(state->cbdata);\n  }\n  else {\n    state->callbacks = NULL;\n    state->cbdata = NULL;\n  }\n}\n\nvoid vterm_state_callbacks_has_premove(VTermState *state)\n{\n  state->callbacks_has_premove = true;\n}\n\nvoid *vterm_state_get_cbdata(VTermState *state)\n{\n  return state->cbdata;\n}\n\nvoid vterm_state_set_unrecognised_fallbacks(VTermState *state, const VTermStateFallbacks *fallbacks, void *user)\n{\n  if(fallbacks) {\n    state->fallbacks = fallbacks;\n    state->fbdata = user;\n  }\n  else {\n    state->fallbacks = NULL;\n    state->fbdata = NULL;\n  }\n}\n\nvoid *vterm_state_get_unrecognised_fbdata(VTermState *state)\n{\n  return state->fbdata;\n}\n\nint vterm_state_set_termprop(VTermState *state, VTermProp prop, VTermValue *val)\n{\n  /* Only store the new value of the property if usercode said it was happy.\n   * This is especially important for altscreen switching */\n  if(state->callbacks && state->callbacks->settermprop)\n    if(!(*state->callbacks->settermprop)(prop, val, state->cbdata))\n      return 0;\n\n  switch(prop) {\n  case VTERM_PROP_TITLE:\n  case VTERM_PROP_ICONNAME:\n    // we don't store these, just transparently pass through\n    return 1;\n  case VTERM_PROP_CURSORVISIBLE:\n    state->mode.cursor_visible = val->boolean;\n    return 1;\n  case VTERM_PROP_CURSORBLINK:\n    state->mode.cursor_blink = val->boolean;\n    return 1;\n  case VTERM_PROP_CURSORSHAPE:\n    state->mode.cursor_shape = val->number;\n    return 1;\n  case VTERM_PROP_REVERSE:\n    state->mode.screen = val->boolean;\n    return 1;\n  case VTERM_PROP_ALTSCREEN:\n    state->mode.alt_screen = val->boolean;\n    state->lineinfo = state->lineinfos[state->mode.alt_screen ? BUFIDX_ALTSCREEN : BUFIDX_PRIMARY];\n    if(state->mode.alt_screen) {\n      VTermRect rect = {\n        .start_row = 0,\n        .start_col = 0,\n        .end_row = state->rows,\n        .end_col = state->cols,\n      };\n      erase(state, rect, 0);\n    }\n    return 1;\n  case VTERM_PROP_MOUSE:\n    state->mouse_flags = 0;\n    if(val->number)\n      state->mouse_flags |= MOUSE_WANT_CLICK;\n    if(val->number == VTERM_PROP_MOUSE_DRAG)\n      state->mouse_flags |= MOUSE_WANT_DRAG;\n    if(val->number == VTERM_PROP_MOUSE_MOVE)\n      state->mouse_flags |= MOUSE_WANT_MOVE;\n    return 1;\n  case VTERM_PROP_FOCUSREPORT:\n    state->mode.report_focus = val->boolean;\n    return 1;\n\n  case VTERM_N_PROPS:\n    return 0;\n  }\n\n  return 0;\n}\n\nvoid vterm_state_focus_in(VTermState *state)\n{\n  if(state->mode.report_focus)\n    vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, \"I\");\n}\n\nvoid vterm_state_focus_out(VTermState *state)\n{\n  if(state->mode.report_focus)\n    vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, \"O\");\n}\n\nconst VTermLineInfo *vterm_state_get_lineinfo(const VTermState *state, int row)\n{\n  return state->lineinfo + row;\n}\n\nvoid vterm_state_set_selection_callbacks(VTermState *state, const VTermSelectionCallbacks *callbacks, void *user,\n    char *buffer, size_t buflen)\n{\n  if(buflen && !buffer)\n    buffer = vterm_allocator_malloc(state->vt, buflen);\n\n  state->selection.callbacks = callbacks;\n  state->selection.user      = user;\n  state->selection.buffer    = buffer;\n  state->selection.buflen    = buflen;\n}\n\nvoid vterm_state_send_selection(VTermState *state, VTermSelectionMask mask, VTermStringFragment frag)\n{\n  VTerm *vt = state->vt;\n\n  if(frag.initial) {\n    /* TODO: support sending more than one mask bit */\n    const static char selection_chars[] = \"cpqs\";\n    int idx;\n    for(idx = 0; idx < 4; idx++)\n      if(mask & (1 << idx))\n        break;\n\n    vterm_push_output_sprintf_str(vt, C1_OSC, false, \"52;%c;\", selection_chars[idx]);\n\n    state->tmp.selection.sendpartial = 0;\n  }\n\n  if(frag.len) {\n    size_t bufcur = 0;\n    char *buffer = state->selection.buffer;\n\n    uint32_t x = 0;\n    int n = 0;\n\n    if(state->tmp.selection.sendpartial) {\n      n = state->tmp.selection.sendpartial >> 24;\n      x = state->tmp.selection.sendpartial & 0xFFFFFF;\n\n      state->tmp.selection.sendpartial = 0;\n    }\n\n    while((state->selection.buflen - bufcur) >= 4 && frag.len) {\n      x = (x << 8) | frag.str[0];\n      n++;\n      frag.str++, frag.len--;\n\n      if(n == 3) {\n        buffer[0] = base64_one((x >> 18) & 0x3F);\n        buffer[1] = base64_one((x >> 12) & 0x3F);\n        buffer[2] = base64_one((x >>  6) & 0x3F);\n        buffer[3] = base64_one((x >>  0) & 0x3F);\n\n        buffer += 4, bufcur += 4;\n        x = 0;\n        n = 0;\n      }\n\n      if(!frag.len || (state->selection.buflen - bufcur) < 4) {\n        if(bufcur)\n          vterm_push_output_bytes(vt, state->selection.buffer, bufcur);\n\n        buffer = state->selection.buffer;\n        bufcur = 0;\n      }\n    }\n\n    if(n)\n      state->tmp.selection.sendpartial = (n << 24) | x;\n  }\n\n  if(frag.final) {\n    if(state->tmp.selection.sendpartial) {\n      int n      = state->tmp.selection.sendpartial >> 24;\n      uint32_t x = state->tmp.selection.sendpartial & 0xFFFFFF;\n      char *buffer = state->selection.buffer;\n\n      /* n is either 1 or 2 now */\n      x <<= (n == 1) ? 16 : 8;\n\n      buffer[0] = base64_one((x >> 18) & 0x3F);\n      buffer[1] = base64_one((x >> 12) & 0x3F);\n      buffer[2] = (n == 1) ? '=' : base64_one((x >>  6) & 0x3F);\n      buffer[3] = '=';\n\n      vterm_push_output_sprintf_str(vt, 0, true, \"%.*s\", 4, buffer);\n    }\n    else\n      vterm_push_output_sprintf_str(vt, 0, true, \"\");\n  }\n}\n"
  },
  {
    "path": "src/unicode.c",
    "content": "#include \"vterm_internal.h\"\n\n// ### The following from http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c\n// With modifications:\n//   made functions static\n//   moved 'combining' table to file scope, so other functions can see it\n// ###################################################################\n\n/*\n * This is an implementation of wcwidth() and wcswidth() (defined in\n * IEEE Std 1002.1-2001) for Unicode.\n *\n * http://www.opengroup.org/onlinepubs/007904975/functions/wcwidth.html\n * http://www.opengroup.org/onlinepubs/007904975/functions/wcswidth.html\n *\n * In fixed-width output devices, Latin characters all occupy a single\n * \"cell\" position of equal width, whereas ideographic CJK characters\n * occupy two such cells. Interoperability between terminal-line\n * applications and (teletype-style) character terminals using the\n * UTF-8 encoding requires agreement on which character should advance\n * the cursor by how many cell positions. No established formal\n * standards exist at present on which Unicode character shall occupy\n * how many cell positions on character terminals. These routines are\n * a first attempt of defining such behavior based on simple rules\n * applied to data provided by the Unicode Consortium.\n *\n * For some graphical characters, the Unicode standard explicitly\n * defines a character-cell width via the definition of the East Asian\n * FullWidth (F), Wide (W), Half-width (H), and Narrow (Na) classes.\n * In all these cases, there is no ambiguity about which width a\n * terminal shall use. For characters in the East Asian Ambiguous (A)\n * class, the width choice depends purely on a preference of backward\n * compatibility with either historic CJK or Western practice.\n * Choosing single-width for these characters is easy to justify as\n * the appropriate long-term solution, as the CJK practice of\n * displaying these characters as double-width comes from historic\n * implementation simplicity (8-bit encoded characters were displayed\n * single-width and 16-bit ones double-width, even for Greek,\n * Cyrillic, etc.) and not any typographic considerations.\n *\n * Much less clear is the choice of width for the Not East Asian\n * (Neutral) class. Existing practice does not dictate a width for any\n * of these characters. It would nevertheless make sense\n * typographically to allocate two character cells to characters such\n * as for instance EM SPACE or VOLUME INTEGRAL, which cannot be\n * represented adequately with a single-width glyph. The following\n * routines at present merely assign a single-cell width to all\n * neutral characters, in the interest of simplicity. This is not\n * entirely satisfactory and should be reconsidered before\n * establishing a formal standard in this area. At the moment, the\n * decision which Not East Asian (Neutral) characters should be\n * represented by double-width glyphs cannot yet be answered by\n * applying a simple rule from the Unicode database content. Setting\n * up a proper standard for the behavior of UTF-8 character terminals\n * will require a careful analysis not only of each Unicode character,\n * but also of each presentation form, something the author of these\n * routines has avoided to do so far.\n *\n * http://www.unicode.org/unicode/reports/tr11/\n *\n * Markus Kuhn -- 2007-05-26 (Unicode 5.0)\n *\n * Permission to use, copy, modify, and distribute this software\n * for any purpose and without fee is hereby granted. The author\n * disclaims all warranties with regard to this software.\n *\n * Latest version: http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c\n */\n\nstruct interval {\n  int first;\n  int last;\n};\n\n/* sorted list of non-overlapping intervals of non-spacing characters */\n/* generated by \"uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c\" */\nstatic const struct interval combining[] = {\n  { 0x0300, 0x036F }, { 0x0483, 0x0486 }, { 0x0488, 0x0489 },\n  { 0x0591, 0x05BD }, { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 },\n  { 0x05C4, 0x05C5 }, { 0x05C7, 0x05C7 }, { 0x0600, 0x0603 },\n  { 0x0610, 0x0615 }, { 0x064B, 0x065E }, { 0x0670, 0x0670 },\n  { 0x06D6, 0x06E4 }, { 0x06E7, 0x06E8 }, { 0x06EA, 0x06ED },\n  { 0x070F, 0x070F }, { 0x0711, 0x0711 }, { 0x0730, 0x074A },\n  { 0x07A6, 0x07B0 }, { 0x07EB, 0x07F3 }, { 0x0901, 0x0902 },\n  { 0x093C, 0x093C }, { 0x0941, 0x0948 }, { 0x094D, 0x094D },\n  { 0x0951, 0x0954 }, { 0x0962, 0x0963 }, { 0x0981, 0x0981 },\n  { 0x09BC, 0x09BC }, { 0x09C1, 0x09C4 }, { 0x09CD, 0x09CD },\n  { 0x09E2, 0x09E3 }, { 0x0A01, 0x0A02 }, { 0x0A3C, 0x0A3C },\n  { 0x0A41, 0x0A42 }, { 0x0A47, 0x0A48 }, { 0x0A4B, 0x0A4D },\n  { 0x0A70, 0x0A71 }, { 0x0A81, 0x0A82 }, { 0x0ABC, 0x0ABC },\n  { 0x0AC1, 0x0AC5 }, { 0x0AC7, 0x0AC8 }, { 0x0ACD, 0x0ACD },\n  { 0x0AE2, 0x0AE3 }, { 0x0B01, 0x0B01 }, { 0x0B3C, 0x0B3C },\n  { 0x0B3F, 0x0B3F }, { 0x0B41, 0x0B43 }, { 0x0B4D, 0x0B4D },\n  { 0x0B56, 0x0B56 }, { 0x0B82, 0x0B82 }, { 0x0BC0, 0x0BC0 },\n  { 0x0BCD, 0x0BCD }, { 0x0C3E, 0x0C40 }, { 0x0C46, 0x0C48 },\n  { 0x0C4A, 0x0C4D }, { 0x0C55, 0x0C56 }, { 0x0CBC, 0x0CBC },\n  { 0x0CBF, 0x0CBF }, { 0x0CC6, 0x0CC6 }, { 0x0CCC, 0x0CCD },\n  { 0x0CE2, 0x0CE3 }, { 0x0D41, 0x0D43 }, { 0x0D4D, 0x0D4D },\n  { 0x0DCA, 0x0DCA }, { 0x0DD2, 0x0DD4 }, { 0x0DD6, 0x0DD6 },\n  { 0x0E31, 0x0E31 }, { 0x0E34, 0x0E3A }, { 0x0E47, 0x0E4E },\n  { 0x0EB1, 0x0EB1 }, { 0x0EB4, 0x0EB9 }, { 0x0EBB, 0x0EBC },\n  { 0x0EC8, 0x0ECD }, { 0x0F18, 0x0F19 }, { 0x0F35, 0x0F35 },\n  { 0x0F37, 0x0F37 }, { 0x0F39, 0x0F39 }, { 0x0F71, 0x0F7E },\n  { 0x0F80, 0x0F84 }, { 0x0F86, 0x0F87 }, { 0x0F90, 0x0F97 },\n  { 0x0F99, 0x0FBC }, { 0x0FC6, 0x0FC6 }, { 0x102D, 0x1030 },\n  { 0x1032, 0x1032 }, { 0x1036, 0x1037 }, { 0x1039, 0x1039 },\n  { 0x1058, 0x1059 }, { 0x1160, 0x11FF }, { 0x135F, 0x135F },\n  { 0x1712, 0x1714 }, { 0x1732, 0x1734 }, { 0x1752, 0x1753 },\n  { 0x1772, 0x1773 }, { 0x17B4, 0x17B5 }, { 0x17B7, 0x17BD },\n  { 0x17C6, 0x17C6 }, { 0x17C9, 0x17D3 }, { 0x17DD, 0x17DD },\n  { 0x180B, 0x180D }, { 0x18A9, 0x18A9 }, { 0x1920, 0x1922 },\n  { 0x1927, 0x1928 }, { 0x1932, 0x1932 }, { 0x1939, 0x193B },\n  { 0x1A17, 0x1A18 }, { 0x1B00, 0x1B03 }, { 0x1B34, 0x1B34 },\n  { 0x1B36, 0x1B3A }, { 0x1B3C, 0x1B3C }, { 0x1B42, 0x1B42 },\n  { 0x1B6B, 0x1B73 }, { 0x1DC0, 0x1DCA }, { 0x1DFE, 0x1DFF },\n  { 0x200B, 0x200F }, { 0x202A, 0x202E }, { 0x2060, 0x2063 },\n  { 0x206A, 0x206F }, { 0x20D0, 0x20EF }, { 0x302A, 0x302F },\n  { 0x3099, 0x309A }, { 0xA806, 0xA806 }, { 0xA80B, 0xA80B },\n  { 0xA825, 0xA826 }, { 0xFB1E, 0xFB1E }, { 0xFE00, 0xFE0F },\n  { 0xFE20, 0xFE23 }, { 0xFEFF, 0xFEFF }, { 0xFFF9, 0xFFFB },\n  { 0x10A01, 0x10A03 }, { 0x10A05, 0x10A06 }, { 0x10A0C, 0x10A0F },\n  { 0x10A38, 0x10A3A }, { 0x10A3F, 0x10A3F }, { 0x1D167, 0x1D169 },\n  { 0x1D173, 0x1D182 }, { 0x1D185, 0x1D18B }, { 0x1D1AA, 0x1D1AD },\n  { 0x1D242, 0x1D244 }, { 0xE0001, 0xE0001 }, { 0xE0020, 0xE007F },\n  { 0xE0100, 0xE01EF }\n};\n\n\n/* auxiliary function for binary search in interval table */\nstatic int bisearch(uint32_t ucs, const struct interval *table, int max) {\n  int min = 0;\n  int mid;\n\n  if (ucs < table[0].first || ucs > table[max].last)\n    return 0;\n  while (max >= min) {\n    mid = (min + max) / 2;\n    if (ucs > table[mid].last)\n      min = mid + 1;\n    else if (ucs < table[mid].first)\n      max = mid - 1;\n    else\n      return 1;\n  }\n\n  return 0;\n}\n\n\n/* The following two functions define the column width of an ISO 10646\n * character as follows:\n *\n *    - The null character (U+0000) has a column width of 0.\n *\n *    - Other C0/C1 control characters and DEL will lead to a return\n *      value of -1.\n *\n *    - Non-spacing and enclosing combining characters (general\n *      category code Mn or Me in the Unicode database) have a\n *      column width of 0.\n *\n *    - SOFT HYPHEN (U+00AD) has a column width of 1.\n *\n *    - Other format characters (general category code Cf in the Unicode\n *      database) and ZERO WIDTH SPACE (U+200B) have a column width of 0.\n *\n *    - Hangul Jamo medial vowels and final consonants (U+1160-U+11FF)\n *      have a column width of 0.\n *\n *    - Spacing characters in the East Asian Wide (W) or East Asian\n *      Full-width (F) category as defined in Unicode Technical\n *      Report #11 have a column width of 2.\n *\n *    - All remaining characters (including all printable\n *      ISO 8859-1 and WGL4 characters, Unicode control characters,\n *      etc.) have a column width of 1.\n *\n * This implementation assumes that uint32_t characters are encoded\n * in ISO 10646.\n */\n\n\nstatic int mk_wcwidth(uint32_t ucs)\n{\n  /* test for 8-bit control characters */\n  if (ucs == 0)\n    return 0;\n  if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0))\n    return -1;\n\n  /* binary search in table of non-spacing characters */\n  if (bisearch(ucs, combining,\n               sizeof(combining) / sizeof(struct interval) - 1))\n    return 0;\n\n  /* if we arrive here, ucs is not a combining or C0/C1 control character */\n\n  return 1 + \n    (ucs >= 0x1100 &&\n     (ucs <= 0x115f ||                    /* Hangul Jamo init. consonants */\n      ucs == 0x2329 || ucs == 0x232a ||\n      (ucs >= 0x2e80 && ucs <= 0xa4cf &&\n       ucs != 0x303f) ||                  /* CJK ... Yi */\n      (ucs >= 0xac00 && ucs <= 0xd7a3) || /* Hangul Syllables */\n      (ucs >= 0xf900 && ucs <= 0xfaff) || /* CJK Compatibility Ideographs */\n      (ucs >= 0xfe10 && ucs <= 0xfe19) || /* Vertical forms */\n      (ucs >= 0xfe30 && ucs <= 0xfe6f) || /* CJK Compatibility Forms */\n      (ucs >= 0xff00 && ucs <= 0xff60) || /* Fullwidth Forms */\n      (ucs >= 0xffe0 && ucs <= 0xffe6) ||\n      (ucs >= 0x20000 && ucs <= 0x2fffd) ||\n      (ucs >= 0x30000 && ucs <= 0x3fffd)));\n}\n\n\n#ifdef USE_MK_WCWIDTH_CJK\n\n/*\n * The following functions are the same as mk_wcwidth() and\n * mk_wcswidth(), except that spacing characters in the East Asian\n * Ambiguous (A) category as defined in Unicode Technical Report #11\n * have a column width of 2. This variant might be useful for users of\n * CJK legacy encodings who want to migrate to UCS without changing\n * the traditional terminal character-width behaviour. It is not\n * otherwise recommended for general use.\n */\nstatic int mk_wcwidth_cjk(uint32_t ucs)\n{\n  /* sorted list of non-overlapping intervals of East Asian Ambiguous\n   * characters, generated by \"uniset +WIDTH-A -cat=Me -cat=Mn -cat=Cf c\" */\n  static const struct interval ambiguous[] = {\n    { 0x00A1, 0x00A1 }, { 0x00A4, 0x00A4 }, { 0x00A7, 0x00A8 },\n    { 0x00AA, 0x00AA }, { 0x00AE, 0x00AE }, { 0x00B0, 0x00B4 },\n    { 0x00B6, 0x00BA }, { 0x00BC, 0x00BF }, { 0x00C6, 0x00C6 },\n    { 0x00D0, 0x00D0 }, { 0x00D7, 0x00D8 }, { 0x00DE, 0x00E1 },\n    { 0x00E6, 0x00E6 }, { 0x00E8, 0x00EA }, { 0x00EC, 0x00ED },\n    { 0x00F0, 0x00F0 }, { 0x00F2, 0x00F3 }, { 0x00F7, 0x00FA },\n    { 0x00FC, 0x00FC }, { 0x00FE, 0x00FE }, { 0x0101, 0x0101 },\n    { 0x0111, 0x0111 }, { 0x0113, 0x0113 }, { 0x011B, 0x011B },\n    { 0x0126, 0x0127 }, { 0x012B, 0x012B }, { 0x0131, 0x0133 },\n    { 0x0138, 0x0138 }, { 0x013F, 0x0142 }, { 0x0144, 0x0144 },\n    { 0x0148, 0x014B }, { 0x014D, 0x014D }, { 0x0152, 0x0153 },\n    { 0x0166, 0x0167 }, { 0x016B, 0x016B }, { 0x01CE, 0x01CE },\n    { 0x01D0, 0x01D0 }, { 0x01D2, 0x01D2 }, { 0x01D4, 0x01D4 },\n    { 0x01D6, 0x01D6 }, { 0x01D8, 0x01D8 }, { 0x01DA, 0x01DA },\n    { 0x01DC, 0x01DC }, { 0x0251, 0x0251 }, { 0x0261, 0x0261 },\n    { 0x02C4, 0x02C4 }, { 0x02C7, 0x02C7 }, { 0x02C9, 0x02CB },\n    { 0x02CD, 0x02CD }, { 0x02D0, 0x02D0 }, { 0x02D8, 0x02DB },\n    { 0x02DD, 0x02DD }, { 0x02DF, 0x02DF }, { 0x0391, 0x03A1 },\n    { 0x03A3, 0x03A9 }, { 0x03B1, 0x03C1 }, { 0x03C3, 0x03C9 },\n    { 0x0401, 0x0401 }, { 0x0410, 0x044F }, { 0x0451, 0x0451 },\n    { 0x2010, 0x2010 }, { 0x2013, 0x2016 }, { 0x2018, 0x2019 },\n    { 0x201C, 0x201D }, { 0x2020, 0x2022 }, { 0x2024, 0x2027 },\n    { 0x2030, 0x2030 }, { 0x2032, 0x2033 }, { 0x2035, 0x2035 },\n    { 0x203B, 0x203B }, { 0x203E, 0x203E }, { 0x2074, 0x2074 },\n    { 0x207F, 0x207F }, { 0x2081, 0x2084 }, { 0x20AC, 0x20AC },\n    { 0x2103, 0x2103 }, { 0x2105, 0x2105 }, { 0x2109, 0x2109 },\n    { 0x2113, 0x2113 }, { 0x2116, 0x2116 }, { 0x2121, 0x2122 },\n    { 0x2126, 0x2126 }, { 0x212B, 0x212B }, { 0x2153, 0x2154 },\n    { 0x215B, 0x215E }, { 0x2160, 0x216B }, { 0x2170, 0x2179 },\n    { 0x2190, 0x2199 }, { 0x21B8, 0x21B9 }, { 0x21D2, 0x21D2 },\n    { 0x21D4, 0x21D4 }, { 0x21E7, 0x21E7 }, { 0x2200, 0x2200 },\n    { 0x2202, 0x2203 }, { 0x2207, 0x2208 }, { 0x220B, 0x220B },\n    { 0x220F, 0x220F }, { 0x2211, 0x2211 }, { 0x2215, 0x2215 },\n    { 0x221A, 0x221A }, { 0x221D, 0x2220 }, { 0x2223, 0x2223 },\n    { 0x2225, 0x2225 }, { 0x2227, 0x222C }, { 0x222E, 0x222E },\n    { 0x2234, 0x2237 }, { 0x223C, 0x223D }, { 0x2248, 0x2248 },\n    { 0x224C, 0x224C }, { 0x2252, 0x2252 }, { 0x2260, 0x2261 },\n    { 0x2264, 0x2267 }, { 0x226A, 0x226B }, { 0x226E, 0x226F },\n    { 0x2282, 0x2283 }, { 0x2286, 0x2287 }, { 0x2295, 0x2295 },\n    { 0x2299, 0x2299 }, { 0x22A5, 0x22A5 }, { 0x22BF, 0x22BF },\n    { 0x2312, 0x2312 }, { 0x2460, 0x24E9 }, { 0x24EB, 0x254B },\n    { 0x2550, 0x2573 }, { 0x2580, 0x258F }, { 0x2592, 0x2595 },\n    { 0x25A0, 0x25A1 }, { 0x25A3, 0x25A9 }, { 0x25B2, 0x25B3 },\n    { 0x25B6, 0x25B7 }, { 0x25BC, 0x25BD }, { 0x25C0, 0x25C1 },\n    { 0x25C6, 0x25C8 }, { 0x25CB, 0x25CB }, { 0x25CE, 0x25D1 },\n    { 0x25E2, 0x25E5 }, { 0x25EF, 0x25EF }, { 0x2605, 0x2606 },\n    { 0x2609, 0x2609 }, { 0x260E, 0x260F }, { 0x2614, 0x2615 },\n    { 0x261C, 0x261C }, { 0x261E, 0x261E }, { 0x2640, 0x2640 },\n    { 0x2642, 0x2642 }, { 0x2660, 0x2661 }, { 0x2663, 0x2665 },\n    { 0x2667, 0x266A }, { 0x266C, 0x266D }, { 0x266F, 0x266F },\n    { 0x273D, 0x273D }, { 0x2776, 0x277F }, { 0xE000, 0xF8FF },\n    { 0xFFFD, 0xFFFD }, { 0xF0000, 0xFFFFD }, { 0x100000, 0x10FFFD }\n  };\n\n  /* binary search in table of non-spacing characters */\n  if (bisearch(ucs, ambiguous,\n               sizeof(ambiguous) / sizeof(struct interval) - 1))\n    return 2;\n\n  return mk_wcwidth(ucs);\n}\n\n#endif\n\n// ################################\n// ### The rest added by Paul Evans\n\nstatic const struct interval fullwidth[] = {\n#include \"fullwidth.inc\"\n};\n\nINTERNAL int vterm_unicode_width(uint32_t codepoint)\n{\n  if(bisearch(codepoint, fullwidth, sizeof(fullwidth) / sizeof(fullwidth[0]) - 1))\n    return 2;\n\n  return mk_wcwidth(codepoint);\n}\n\nINTERNAL int vterm_unicode_is_combining(uint32_t codepoint)\n{\n  return bisearch(codepoint, combining, sizeof(combining) / sizeof(struct interval) - 1);\n}\n"
  },
  {
    "path": "src/utf8.h",
    "content": "/* The following functions copied and adapted from libtermkey\n *\n * http://www.leonerd.org.uk/code/libtermkey/\n */\nstatic inline unsigned int utf8_seqlen(long codepoint)\n{\n  if(codepoint < 0x0000080) return 1;\n  if(codepoint < 0x0000800) return 2;\n  if(codepoint < 0x0010000) return 3;\n  if(codepoint < 0x0200000) return 4;\n  if(codepoint < 0x4000000) return 5;\n  return 6;\n}\n\n/* Does NOT NUL-terminate the buffer */\nstatic int fill_utf8(long codepoint, char *str)\n{\n  int nbytes = utf8_seqlen(codepoint);\n\n  // This is easier done backwards\n  int b = nbytes;\n  while(b > 1) {\n    b--;\n    str[b] = 0x80 | (codepoint & 0x3f);\n    codepoint >>= 6;\n  }\n\n  switch(nbytes) {\n    case 1: str[0] =        (codepoint & 0x7f); break;\n    case 2: str[0] = 0xc0 | (codepoint & 0x1f); break;\n    case 3: str[0] = 0xe0 | (codepoint & 0x0f); break;\n    case 4: str[0] = 0xf0 | (codepoint & 0x07); break;\n    case 5: str[0] = 0xf8 | (codepoint & 0x03); break;\n    case 6: str[0] = 0xfc | (codepoint & 0x01); break;\n  }\n\n  return nbytes;\n}\n/* end copy */\n"
  },
  {
    "path": "src/vterm.c",
    "content": "#include \"vterm_internal.h\"\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <stdarg.h>\n#include <string.h>\n\n/*****************\n * API functions *\n *****************/\n\nstatic void *default_malloc(size_t size, void *allocdata)\n{\n  void *ptr = malloc(size);\n  if(ptr)\n    memset(ptr, 0, size);\n  return ptr;\n}\n\nstatic void default_free(void *ptr, void *allocdata)\n{\n  free(ptr);\n}\n\nstatic VTermAllocatorFunctions default_allocator = {\n  .malloc = &default_malloc,\n  .free   = &default_free,\n};\n\nVTerm *vterm_new(int rows, int cols)\n{\n  return vterm_build(&(const struct VTermBuilder){\n      .rows = rows,\n      .cols = cols,\n    });\n}\n\nVTerm *vterm_new_with_allocator(int rows, int cols, VTermAllocatorFunctions *funcs, void *allocdata)\n{\n  return vterm_build(&(const struct VTermBuilder){\n      .rows = rows,\n      .cols = cols,\n      .allocator = funcs,\n      .allocdata = allocdata,\n    });\n}\n\n/* A handy macro for defaulting values out of builder fields */\n#define DEFAULT(v, def)  ((v) ? (v) : (def))\n\nVTerm *vterm_build(const struct VTermBuilder *builder)\n{\n  const VTermAllocatorFunctions *allocator = DEFAULT(builder->allocator, &default_allocator);\n\n  /* Need to bootstrap using the allocator function directly */\n  VTerm *vt = (*allocator->malloc)(sizeof(VTerm), builder->allocdata);\n\n  vt->allocator = allocator;\n  vt->allocdata = builder->allocdata;\n\n  vt->rows = builder->rows;\n  vt->cols = builder->cols;\n\n  vt->parser.state = NORMAL;\n\n  vt->parser.callbacks = NULL;\n  vt->parser.cbdata    = NULL;\n\n  vt->parser.emit_nul  = false;\n\n  vt->outfunc = NULL;\n  vt->outdata = NULL;\n\n  vt->outbuffer_len = DEFAULT(builder->outbuffer_len, 4096);\n  vt->outbuffer_cur = 0;\n  vt->outbuffer = vterm_allocator_malloc(vt, vt->outbuffer_len);\n\n  vt->tmpbuffer_len = DEFAULT(builder->tmpbuffer_len, 4096);\n  vt->tmpbuffer = vterm_allocator_malloc(vt, vt->tmpbuffer_len);\n\n  return vt;\n}\n\nvoid vterm_free(VTerm *vt)\n{\n  if(vt->screen)\n    vterm_screen_free(vt->screen);\n\n  if(vt->state)\n    vterm_state_free(vt->state);\n\n  vterm_allocator_free(vt, vt->outbuffer);\n  vterm_allocator_free(vt, vt->tmpbuffer);\n\n  vterm_allocator_free(vt, vt);\n}\n\nINTERNAL void *vterm_allocator_malloc(VTerm *vt, size_t size)\n{\n  return (*vt->allocator->malloc)(size, vt->allocdata);\n}\n\nINTERNAL void vterm_allocator_free(VTerm *vt, void *ptr)\n{\n  (*vt->allocator->free)(ptr, vt->allocdata);\n}\n\nvoid vterm_get_size(const VTerm *vt, int *rowsp, int *colsp)\n{\n  if(rowsp)\n    *rowsp = vt->rows;\n  if(colsp)\n    *colsp = vt->cols;\n}\n\nvoid vterm_set_size(VTerm *vt, int rows, int cols)\n{\n  if(rows < 1 || cols < 1)\n    return;\n\n  vt->rows = rows;\n  vt->cols = cols;\n\n  if(vt->parser.callbacks && vt->parser.callbacks->resize)\n    (*vt->parser.callbacks->resize)(rows, cols, vt->parser.cbdata);\n}\n\nint vterm_get_utf8(const VTerm *vt)\n{\n  return vt->mode.utf8;\n}\n\nvoid vterm_set_utf8(VTerm *vt, int is_utf8)\n{\n  vt->mode.utf8 = is_utf8;\n}\n\nvoid vterm_output_set_callback(VTerm *vt, VTermOutputCallback *func, void *user)\n{\n  vt->outfunc = func;\n  vt->outdata = user;\n}\n\nINTERNAL void vterm_push_output_bytes(VTerm *vt, const char *bytes, size_t len)\n{\n  if(vt->outfunc) {\n    (vt->outfunc)(bytes, len, vt->outdata);\n    return;\n  }\n\n  if(len > vt->outbuffer_len - vt->outbuffer_cur)\n    return;\n\n  memcpy(vt->outbuffer + vt->outbuffer_cur, bytes, len);\n  vt->outbuffer_cur += len;\n}\n\nINTERNAL void vterm_push_output_vsprintf(VTerm *vt, const char *format, va_list args)\n{\n  size_t len = vsnprintf(vt->tmpbuffer, vt->tmpbuffer_len,\n      format, args);\n\n  vterm_push_output_bytes(vt, vt->tmpbuffer, len);\n}\n\nINTERNAL void vterm_push_output_sprintf(VTerm *vt, const char *format, ...)\n{\n  va_list args;\n  va_start(args, format);\n  vterm_push_output_vsprintf(vt, format, args);\n  va_end(args);\n}\n\nINTERNAL void vterm_push_output_sprintf_ctrl(VTerm *vt, unsigned char ctrl, const char *fmt, ...)\n{\n  size_t cur;\n\n  if(ctrl >= 0x80 && !vt->mode.ctrl8bit)\n    cur = snprintf(vt->tmpbuffer, vt->tmpbuffer_len,\n        ESC_S \"%c\", ctrl - 0x40);\n  else\n    cur = snprintf(vt->tmpbuffer, vt->tmpbuffer_len,\n        \"%c\", ctrl);\n\n  if(cur >= vt->tmpbuffer_len)\n    return;\n\n  va_list args;\n  va_start(args, fmt);\n  cur += vsnprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur,\n      fmt, args);\n  va_end(args);\n\n  if(cur >= vt->tmpbuffer_len)\n    return;\n\n  vterm_push_output_bytes(vt, vt->tmpbuffer, cur);\n}\n\nINTERNAL void vterm_push_output_sprintf_str(VTerm *vt, unsigned char ctrl, bool term, const char *fmt, ...)\n{\n  size_t cur = 0;\n\n  if(ctrl) {\n    if(ctrl >= 0x80 && !vt->mode.ctrl8bit)\n      cur = snprintf(vt->tmpbuffer, vt->tmpbuffer_len,\n          ESC_S \"%c\", ctrl - 0x40);\n    else\n      cur = snprintf(vt->tmpbuffer, vt->tmpbuffer_len,\n          \"%c\", ctrl);\n\n    if(cur >= vt->tmpbuffer_len)\n      return;\n  }\n\n  va_list args;\n  va_start(args, fmt);\n  cur += vsnprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur,\n      fmt, args);\n  va_end(args);\n\n  if(cur >= vt->tmpbuffer_len)\n    return;\n\n  if(term) {\n    cur += snprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur,\n        vt->mode.ctrl8bit ? \"\\x9C\" : ESC_S \"\\\\\"); // ST\n\n    if(cur >= vt->tmpbuffer_len)\n      return;\n  }\n\n  vterm_push_output_bytes(vt, vt->tmpbuffer, cur);\n}\n\nsize_t vterm_output_get_buffer_size(const VTerm *vt)\n{\n  return vt->outbuffer_len;\n}\n\nsize_t vterm_output_get_buffer_current(const VTerm *vt)\n{\n  return vt->outbuffer_cur;\n}\n\nsize_t vterm_output_get_buffer_remaining(const VTerm *vt)\n{\n  return vt->outbuffer_len - vt->outbuffer_cur;\n}\n\nsize_t vterm_output_read(VTerm *vt, char *buffer, size_t len)\n{\n  if(len > vt->outbuffer_cur)\n    len = vt->outbuffer_cur;\n\n  memcpy(buffer, vt->outbuffer, len);\n\n  if(len < vt->outbuffer_cur)\n    memmove(vt->outbuffer, vt->outbuffer + len, vt->outbuffer_cur - len);\n\n  vt->outbuffer_cur -= len;\n\n  return len;\n}\n\nVTermValueType vterm_get_attr_type(VTermAttr attr)\n{\n  switch(attr) {\n    case VTERM_ATTR_BOLD:       return VTERM_VALUETYPE_BOOL;\n    case VTERM_ATTR_UNDERLINE:  return VTERM_VALUETYPE_INT;\n    case VTERM_ATTR_ITALIC:     return VTERM_VALUETYPE_BOOL;\n    case VTERM_ATTR_BLINK:      return VTERM_VALUETYPE_BOOL;\n    case VTERM_ATTR_REVERSE:    return VTERM_VALUETYPE_BOOL;\n    case VTERM_ATTR_CONCEAL:    return VTERM_VALUETYPE_BOOL;\n    case VTERM_ATTR_STRIKE:     return VTERM_VALUETYPE_BOOL;\n    case VTERM_ATTR_FONT:       return VTERM_VALUETYPE_INT;\n    case VTERM_ATTR_FOREGROUND: return VTERM_VALUETYPE_COLOR;\n    case VTERM_ATTR_BACKGROUND: return VTERM_VALUETYPE_COLOR;\n    case VTERM_ATTR_SMALL:      return VTERM_VALUETYPE_BOOL;\n    case VTERM_ATTR_BASELINE:   return VTERM_VALUETYPE_INT;\n\n    case VTERM_N_ATTRS: return 0;\n  }\n  return 0; /* UNREACHABLE */\n}\n\nVTermValueType vterm_get_prop_type(VTermProp prop)\n{\n  switch(prop) {\n    case VTERM_PROP_CURSORVISIBLE: return VTERM_VALUETYPE_BOOL;\n    case VTERM_PROP_CURSORBLINK:   return VTERM_VALUETYPE_BOOL;\n    case VTERM_PROP_ALTSCREEN:     return VTERM_VALUETYPE_BOOL;\n    case VTERM_PROP_TITLE:         return VTERM_VALUETYPE_STRING;\n    case VTERM_PROP_ICONNAME:      return VTERM_VALUETYPE_STRING;\n    case VTERM_PROP_REVERSE:       return VTERM_VALUETYPE_BOOL;\n    case VTERM_PROP_CURSORSHAPE:   return VTERM_VALUETYPE_INT;\n    case VTERM_PROP_MOUSE:         return VTERM_VALUETYPE_INT;\n    case VTERM_PROP_FOCUSREPORT:   return VTERM_VALUETYPE_BOOL;\n\n    case VTERM_N_PROPS: return 0;\n  }\n  return 0; /* UNREACHABLE */\n}\n\nvoid vterm_scroll_rect(VTermRect rect,\n    int downward,\n    int rightward,\n    int (*moverect)(VTermRect src, VTermRect dest, void *user),\n    int (*eraserect)(VTermRect rect, int selective, void *user),\n    void *user)\n{\n  VTermRect src;\n  VTermRect dest;\n\n  if(abs(downward)  >= rect.end_row - rect.start_row ||\n     abs(rightward) >= rect.end_col - rect.start_col) {\n    /* Scroll more than area; just erase the lot */\n    (*eraserect)(rect, 0, user);\n    return;\n  }\n\n  if(rightward >= 0) {\n    /* rect: [XXX................]\n     * src:     [----------------]\n     * dest: [----------------]\n     */\n    dest.start_col = rect.start_col;\n    dest.end_col   = rect.end_col   - rightward;\n    src.start_col  = rect.start_col + rightward;\n    src.end_col    = rect.end_col;\n  }\n  else {\n    /* rect: [................XXX]\n     * src:  [----------------]\n     * dest:    [----------------]\n     */\n    int leftward = -rightward;\n    dest.start_col = rect.start_col + leftward;\n    dest.end_col   = rect.end_col;\n    src.start_col  = rect.start_col;\n    src.end_col    = rect.end_col - leftward;\n  }\n\n  if(downward >= 0) {\n    dest.start_row = rect.start_row;\n    dest.end_row   = rect.end_row   - downward;\n    src.start_row  = rect.start_row + downward;\n    src.end_row    = rect.end_row;\n  }\n  else {\n    int upward = -downward;\n    dest.start_row = rect.start_row + upward;\n    dest.end_row   = rect.end_row;\n    src.start_row  = rect.start_row;\n    src.end_row    = rect.end_row - upward;\n  }\n\n  if(moverect)\n    (*moverect)(dest, src, user);\n\n  if(downward > 0)\n    rect.start_row = rect.end_row - downward;\n  else if(downward < 0)\n    rect.end_row = rect.start_row - downward;\n\n  if(rightward > 0)\n    rect.start_col = rect.end_col - rightward;\n  else if(rightward < 0)\n    rect.end_col = rect.start_col - rightward;\n\n  (*eraserect)(rect, 0, user);\n}\n\nvoid vterm_copy_cells(VTermRect dest,\n    VTermRect src,\n    void (*copycell)(VTermPos dest, VTermPos src, void *user),\n    void *user)\n{\n  int downward  = src.start_row - dest.start_row;\n  int rightward = src.start_col - dest.start_col;\n\n  int init_row, test_row, init_col, test_col;\n  int inc_row, inc_col;\n\n  if(downward < 0) {\n    init_row = dest.end_row - 1;\n    test_row = dest.start_row - 1;\n    inc_row = -1;\n  }\n  else /* downward >= 0 */ {\n    init_row = dest.start_row;\n    test_row = dest.end_row;\n    inc_row = +1;\n  }\n\n  if(rightward < 0) {\n    init_col = dest.end_col - 1;\n    test_col = dest.start_col - 1;\n    inc_col = -1;\n  }\n  else /* rightward >= 0 */ {\n    init_col = dest.start_col;\n    test_col = dest.end_col;\n    inc_col = +1;\n  }\n\n  VTermPos pos;\n  for(pos.row = init_row; pos.row != test_row; pos.row += inc_row)\n    for(pos.col = init_col; pos.col != test_col; pos.col += inc_col) {\n      VTermPos srcpos = { pos.row + downward, pos.col + rightward };\n      (*copycell)(pos, srcpos, user);\n    }\n}\n\nvoid vterm_check_version(int major, int minor)\n{\n  if(major != VTERM_VERSION_MAJOR) {\n    fprintf(stderr, \"libvterm major version mismatch; %d (wants) != %d (library)\\n\",\n        major, VTERM_VERSION_MAJOR);\n    exit(1);\n  }\n\n  if(minor > VTERM_VERSION_MINOR) {\n    fprintf(stderr, \"libvterm minor version mismatch; %d (wants) > %d (library)\\n\",\n        minor, VTERM_VERSION_MINOR);\n    exit(1);\n  }\n\n  // Happy\n}\n"
  },
  {
    "path": "src/vterm_internal.h",
    "content": "#ifndef __VTERM_INTERNAL_H__\n#define __VTERM_INTERNAL_H__\n\n#include \"vterm.h\"\n\n#include <stdarg.h>\n\n#if defined(__GNUC__)\n# define INTERNAL __attribute__((visibility(\"internal\")))\n#else\n# define INTERNAL\n#endif\n\n#ifdef DEBUG\n# define DEBUG_LOG(...) fprintf(stderr, __VA_ARGS__)\n#else\n# define DEBUG_LOG(...)\n#endif\n\n#define ESC_S \"\\x1b\"\n\n#define INTERMED_MAX 16\n\n#define CSI_ARGS_MAX 16\n#define CSI_LEADER_MAX 16\n\n#define BUFIDX_PRIMARY   0\n#define BUFIDX_ALTSCREEN 1\n\ntypedef struct VTermEncoding VTermEncoding;\n\ntypedef struct {\n  VTermEncoding *enc;\n\n  // This size should be increased if required by other stateful encodings\n  char           data[4*sizeof(uint32_t)];\n} VTermEncodingInstance;\n\nstruct VTermPen\n{\n  VTermColor fg;\n  VTermColor bg;\n  unsigned int bold:1;\n  unsigned int underline:2;\n  unsigned int italic:1;\n  unsigned int blink:1;\n  unsigned int reverse:1;\n  unsigned int conceal:1;\n  unsigned int strike:1;\n  unsigned int font:4; /* To store 0-9 */\n  unsigned int small:1;\n  unsigned int baseline:2;\n};\n\nstruct VTermState\n{\n  VTerm *vt;\n\n  const VTermStateCallbacks *callbacks;\n  void *cbdata;\n  bool callbacks_has_premove;\n\n  const VTermStateFallbacks *fallbacks;\n  void *fbdata;\n\n  int rows;\n  int cols;\n\n  /* Current cursor position */\n  VTermPos pos;\n\n  int at_phantom; /* True if we're on the \"81st\" phantom column to defer a wraparound */\n\n  int scrollregion_top;\n  int scrollregion_bottom; /* -1 means unbounded */\n#define SCROLLREGION_BOTTOM(state) ((state)->scrollregion_bottom > -1 ? (state)->scrollregion_bottom : (state)->rows)\n  int scrollregion_left;\n#define SCROLLREGION_LEFT(state)  ((state)->mode.leftrightmargin ? (state)->scrollregion_left : 0)\n  int scrollregion_right; /* -1 means unbounded */\n#define SCROLLREGION_RIGHT(state) ((state)->mode.leftrightmargin && (state)->scrollregion_right > -1 ? (state)->scrollregion_right : (state)->cols)\n\n  /* Bitvector of tab stops */\n  unsigned char *tabstops;\n\n  /* Primary and Altscreen; lineinfos[1] is lazily allocated as needed */\n  VTermLineInfo *lineinfos[2];\n\n  /* lineinfo will == lineinfos[0] or lineinfos[1], depending on altscreen */\n  VTermLineInfo *lineinfo;\n#define ROWWIDTH(state,row) ((state)->lineinfo[(row)].doublewidth ? ((state)->cols / 2) : (state)->cols)\n#define THISROWWIDTH(state) ROWWIDTH(state, (state)->pos.row)\n\n  /* Mouse state */\n  int mouse_col, mouse_row;\n  int mouse_buttons;\n  int mouse_flags;\n#define MOUSE_WANT_CLICK 0x01\n#define MOUSE_WANT_DRAG  0x02\n#define MOUSE_WANT_MOVE  0x04\n\n  enum { MOUSE_X10, MOUSE_UTF8, MOUSE_SGR, MOUSE_RXVT } mouse_protocol;\n\n  /* Last glyph output, for Unicode recombining purposes */\n  uint32_t *combine_chars;\n  size_t combine_chars_size; // Number of ELEMENTS in the above\n  int combine_width; // The width of the glyph above\n  VTermPos combine_pos;   // Position before movement\n\n  struct {\n    unsigned int keypad:1;\n    unsigned int cursor:1;\n    unsigned int autowrap:1;\n    unsigned int insert:1;\n    unsigned int newline:1;\n    unsigned int cursor_visible:1;\n    unsigned int cursor_blink:1;\n    unsigned int cursor_shape:2;\n    unsigned int alt_screen:1;\n    unsigned int origin:1;\n    unsigned int screen:1;\n    unsigned int leftrightmargin:1;\n    unsigned int bracketpaste:1;\n    unsigned int report_focus:1;\n  } mode;\n\n  VTermEncodingInstance encoding[4], encoding_utf8;\n  int gl_set, gr_set, gsingle_set;\n\n  struct VTermPen pen;\n\n  VTermColor default_fg;\n  VTermColor default_bg;\n  VTermColor colors[16]; // Store the 8 ANSI and the 8 ANSI high-brights only\n\n  int bold_is_highbright;\n\n  unsigned int protected_cell : 1;\n\n  /* Saved state under DEC mode 1048/1049 */\n  struct {\n    VTermPos pos;\n    struct VTermPen pen;\n\n    struct {\n      unsigned int cursor_visible:1;\n      unsigned int cursor_blink:1;\n      unsigned int cursor_shape:2;\n    } mode;\n  } saved;\n\n  /* Temporary state for DECRQSS parsing */\n  union {\n    char decrqss[4];\n    struct {\n      uint16_t mask;\n      enum {\n        SELECTION_INITIAL,\n        SELECTION_SELECTED,\n        SELECTION_QUERY,\n        SELECTION_SET_INITIAL,\n        SELECTION_SET,\n        SELECTION_INVALID,\n      } state : 8;\n      uint32_t recvpartial;\n      uint32_t sendpartial;\n    } selection;\n  } tmp;\n\n  struct {\n    const VTermSelectionCallbacks *callbacks;\n    void *user;\n    char *buffer;\n    size_t buflen;\n  } selection;\n};\n\nstruct VTerm\n{\n  const VTermAllocatorFunctions *allocator;\n  void *allocdata;\n\n  int rows;\n  int cols;\n\n  struct {\n    unsigned int utf8:1;\n    unsigned int ctrl8bit:1;\n  } mode;\n\n  struct {\n    enum VTermParserState {\n      NORMAL,\n      CSI_LEADER,\n      CSI_ARGS,\n      CSI_INTERMED,\n      DCS_COMMAND,\n      /* below here are the \"string states\" */\n      OSC_COMMAND,\n      OSC,\n      DCS,\n      APC,\n      PM,\n      SOS,\n    } state;\n\n    bool in_esc : 1;\n\n    int intermedlen;\n    char intermed[INTERMED_MAX];\n\n    union {\n      struct {\n        int leaderlen;\n        char leader[CSI_LEADER_MAX];\n\n        int argi;\n        long args[CSI_ARGS_MAX];\n      } csi;\n      struct {\n        int command;\n      } osc;\n      struct {\n        int commandlen;\n        char command[CSI_LEADER_MAX];\n      } dcs;\n    } v;\n\n    const VTermParserCallbacks *callbacks;\n    void *cbdata;\n\n    bool string_initial;\n\n    bool emit_nul;\n  } parser;\n\n  /* len == malloc()ed size; cur == number of valid bytes */\n\n  VTermOutputCallback *outfunc;\n  void                *outdata;\n\n  char  *outbuffer;\n  size_t outbuffer_len;\n  size_t outbuffer_cur;\n\n  char  *tmpbuffer;\n  size_t tmpbuffer_len;\n\n  VTermState *state;\n  VTermScreen *screen;\n};\n\nstruct VTermEncoding {\n  void (*init) (VTermEncoding *enc, void *data);\n  void (*decode)(VTermEncoding *enc, void *data,\n                 uint32_t cp[], int *cpi, int cplen,\n                 const char bytes[], size_t *pos, size_t len);\n};\n\ntypedef enum {\n  ENC_UTF8,\n  ENC_SINGLE_94\n} VTermEncodingType;\n\nvoid *vterm_allocator_malloc(VTerm *vt, size_t size);\nvoid  vterm_allocator_free(VTerm *vt, void *ptr);\n\nvoid vterm_push_output_bytes(VTerm *vt, const char *bytes, size_t len);\nvoid vterm_push_output_vsprintf(VTerm *vt, const char *format, va_list args);\nvoid vterm_push_output_sprintf(VTerm *vt, const char *format, ...);\nvoid vterm_push_output_sprintf_ctrl(VTerm *vt, unsigned char ctrl, const char *fmt, ...);\nvoid vterm_push_output_sprintf_str(VTerm *vt, unsigned char ctrl, bool term, const char *fmt, ...);\n\nvoid vterm_state_free(VTermState *state);\n\nvoid vterm_state_newpen(VTermState *state);\nvoid vterm_state_resetpen(VTermState *state);\nvoid vterm_state_setpen(VTermState *state, const long args[], int argcount);\nint  vterm_state_getpen(VTermState *state, long args[], int argcount);\nvoid vterm_state_savepen(VTermState *state, int save);\n\nenum {\n  C1_SS3 = 0x8f,\n  C1_DCS = 0x90,\n  C1_CSI = 0x9b,\n  C1_ST  = 0x9c,\n  C1_OSC = 0x9d,\n};\n\nvoid vterm_state_push_output_sprintf_CSI(VTermState *vts, const char *format, ...);\n\nvoid vterm_screen_free(VTermScreen *screen);\n\nVTermEncoding *vterm_lookup_encoding(VTermEncodingType type, char designation);\n\nint vterm_unicode_width(uint32_t codepoint);\nint vterm_unicode_is_combining(uint32_t codepoint);\n\n#endif\n"
  },
  {
    "path": "t/02parser.test",
    "content": "INIT\nUTF8 0\nWANTPARSER\n\n!Basic text\nPUSH \"hello\"\n  text 0x68, 0x65, 0x6c, 0x6c, 0x6f\n\n!C0\nPUSH \"\\x03\"\n  control 3\n\nPUSH \"\\x1f\"\n  control 0x1f\n\n!C1 8bit\nPUSH \"\\x83\"\n  control 0x83\n\nPUSH \"\\x99\"\n  control 0x99\n\n!C1 7bit\nPUSH \"\\e\\x43\"\n  control 0x83\n\nPUSH \"\\e\\x59\"\n  control 0x99\n\n!High bytes\nPUSH \"\\xa0\\xcc\\xfe\"\n  text 0xa0, 0xcc, 0xfe\n\n!Mixed\nPUSH \"1\\n2\"\n  text 0x31\n  control 10\n  text 0x32\n\n!Escape\nPUSH \"\\e=\"\n  escape \"=\"\n\n!Escape 2-byte\nPUSH \"\\e(X\"\n  escape \"(X\"\n\n!Split write Escape\nPUSH \"\\e(\"\nPUSH \"Y\"\n  escape \"(Y\"\n\n!Escape cancels Escape, starts another\nPUSH \"\\e(\\e)Z\"\n  escape \")Z\"\n\n!CAN cancels Escape, returns to normal mode\nPUSH \"\\e(\\x{18}AB\"\n  text 0x41, 0x42\n\n!C0 in Escape interrupts and continues\nPUSH \"\\e(\\nX\"\n  control 10\n  escape \"(X\"\n\n!CSI 0 args\nPUSH \"\\e[a\"\n  csi 0x61 *\n\n!CSI 1 arg\nPUSH \"\\e[9b\"\n  csi 0x62 9\n\n!CSI 2 args\nPUSH \"\\e[3;4c\"\n  csi 0x63 3,4\n\n!CSI 1 arg 1 sub\nPUSH \"\\e[1:2c\"\n  csi 0x63 1+,2\n\n!CSI many digits\nPUSH \"\\e[678d\"\n  csi 0x64 678\n\n!CSI leading zero\nPUSH \"\\e[007e\"\n  csi 0x65 7\n\n!CSI qmark\nPUSH \"\\e[?2;7f\"\n  csi 0x66 L=3f 2,7\n\n!CSI greater\nPUSH \"\\e[>c\"\n  csi 0x63 L=3e *\n\n!CSI SP\nPUSH \"\\e[12 q\"\n  csi 0x71 12 I=20\n\n!Mixed CSI\nPUSH \"A\\e[8mB\"\n  text 0x41\n  csi 0x6d 8\n  text 0x42\n\n!Split write\nPUSH \"\\e\"\nPUSH \"[a\"\n  csi 0x61 *\nPUSH \"foo\\e[\"\n  text 0x66, 0x6f, 0x6f\nPUSH \"4b\"\n  csi 0x62 4\nPUSH \"\\e[12;\"\nPUSH \"3c\"\n  csi 0x63 12,3\n\n!Escape cancels CSI, starts Escape\nPUSH \"\\e[123\\e9\"\n  escape \"9\"\n\n!CAN cancels CSI, returns to normal mode\nPUSH \"\\e[12\\x{18}AB\"\n  text 0x41, 0x42\n\n!C0 in Escape interrupts and continues\nPUSH \"\\e[12\\n;3X\"\n  control 10\n  csi 0x58 12,3\n\n!OSC BEL\nPUSH \"\\e]1;Hello\\x07\"\n  osc [1 \"Hello\"]\n\n!OSC ST (7bit)\nPUSH \"\\e]1;Hello\\e\\\\\"\n  osc [1 \"Hello\"]\n\n!OSC ST (8bit)\nPUSH \"\\x{9d}1;Hello\\x9c\"\n  osc [1 \"Hello\"]\n\n!OSC in parts\nPUSH \"\\e]52;abc\"\n  osc [52 \"abc\"\nPUSH \"def\"\n  osc \"def\"\nPUSH \"ghi\\e\\\\\"\n  osc \"ghi\"]\n\n!OSC BEL without semicolon \nPUSH \"\\e]1234\\x07\"\n  osc [1234 ]\n\n!OSC ST without semicolon \nPUSH \"\\e]1234\\e\\\\\"\n  osc [1234 ]\n\n!Escape cancels OSC, starts Escape\nPUSH \"\\e]Something\\e9\"\n  escape \"9\"\n\n!CAN cancels OSC, returns to normal mode\nPUSH \"\\e]12\\x{18}AB\"\n  text 0x41, 0x42\n\n!C0 in OSC interrupts and continues\nPUSH \"\\e]2;\\nBye\\x07\"\n  osc [2 \"\"\n  control 10\n  osc \"Bye\"]\n\n!DCS BEL\nPUSH \"\\ePHello\\x07\"\n  dcs [\"Hello\"]\n\n!DCS ST (7bit)\nPUSH \"\\ePHello\\e\\\\\"\n  dcs [\"Hello\"]\n\n!DCS ST (8bit)\nPUSH \"\\x{90}Hello\\x9c\"\n  dcs [\"Hello\"]\n\n!Split write of 7bit ST\nPUSH \"\\ePABC\\e\"\n  dcs [\"ABC\"\nPUSH \"\\\\\"\n  dcs ]\n\n!Escape cancels DCS, starts Escape\nPUSH \"\\ePSomething\\e9\"\n  escape \"9\"\n\n!CAN cancels DCS, returns to normal mode\nPUSH \"\\eP12\\x{18}AB\"\n  text 0x41, 0x42\n\n!C0 in OSC interrupts and continues\nPUSH \"\\ePBy\\ne\\x07\"\n  dcs [\"By\"\n  control 10\n  dcs \"e\"]\n\n!APC BEL\nPUSH \"\\e_Hello\\x07\"\n  apc [\"Hello\"]\n\n!APC ST (7bit)\nPUSH \"\\e_Hello\\e\\\\\"\n  apc [\"Hello\"]\n\n!APC ST (8bit)\nPUSH \"\\x{9f}Hello\\x9c\"\n  apc [\"Hello\"]\n\n!PM BEL\nPUSH \"\\e^Hello\\x07\"\n  pm [\"Hello\"]\n\n!PM ST (7bit)\nPUSH \"\\e^Hello\\e\\\\\"\n  pm [\"Hello\"]\n\n!PM ST (8bit)\nPUSH \"\\x{9e}Hello\\x9c\"\n  pm [\"Hello\"]\n\n!SOS BEL\nPUSH \"\\eXHello\\x07\"\n  sos [\"Hello\"]\n\n!SOS ST (7bit)\nPUSH \"\\eXHello\\e\\\\\"\n  sos [\"Hello\"]\n\n!SOS ST (8bit)\nPUSH \"\\x{98}Hello\\x9c\"\n  sos [\"Hello\"]\n\n!SOS can contain any C0 or C1 code\nPUSH \"\\eXABC\\x01DEF\\e\\\\\"\n  sos [\"ABC\\x01DEF\"]\nPUSH \"\\eXABC\\x99DEF\\e\\\\\"\n  sos [\"ABC\\x{99}DEF\"]\n\n!NUL ignored\nPUSH \"\\x{00}\"\n\n!NUL ignored within CSI\nPUSH \"\\e[12\\x{00}3m\"\n  csi 0x6d 123\n\n!DEL ignored\nPUSH \"\\x{7f}\"\n\n!DEL ignored within CSI\nPUSH \"\\e[12\\x{7f}3m\"\n  csi 0x6d 123\n\n!DEL inside text\"\nPUSH \"AB\\x{7f}C\"\n  text 0x41,0x42\n  text 0x43\n"
  },
  {
    "path": "t/03encoding_utf8.test",
    "content": "INIT\nWANTENCODING\n\n!Low\nENCIN \"123\"\n  encout 0x31,0x32,0x33\n\n# We want to prove the UTF-8 parser correctly handles all the sequences.\n# Easy way to do this is to check it does low/high boundary cases, as that\n# leaves only two for each sequence length\n#\n# These ranges are therefore:\n#\n# Two bytes:\n# U+0080 = 000 10000000 =>    00010   000000\n#                       => 11000010 10000000 = C2 80\n# U+07FF = 111 11111111 =>    11111   111111\n#                       => 11011111 10111111 = DF BF\n#\n# Three bytes:\n# U+0800 = 00001000 00000000 =>     0000   100000   000000\n#                            => 11100000 10100000 10000000 = E0 A0 80\n# U+FFFD = 11111111 11111101 =>     1111   111111   111101\n#                            => 11101111 10111111 10111101 = EF BF BD\n# (We avoid U+FFFE and U+FFFF as they're invalid codepoints)\n#\n# Four bytes:\n# U+10000  = 00001 00000000 00000000 =>      000   010000   000000   000000\n#                                    => 11110000 10010000 10000000 10000000 = F0 90 80 80\n# U+1FFFFF = 11111 11111111 11111111 =>      111   111111   111111   111111\n#                                    => 11110111 10111111 10111111 10111111 = F7 BF BF BF\n\n!2 byte\nENCIN \"\\xC2\\x80\\xDF\\xBF\"\n  encout 0x0080, 0x07FF\n\n!3 byte\nENCIN \"\\xE0\\xA0\\x80\\xEF\\xBF\\xBD\"\n  encout 0x0800,0xFFFD\n\n!4 byte\nENCIN \"\\xF0\\x90\\x80\\x80\\xF7\\xBF\\xBF\\xBF\"\n  encout 0x10000,0x1fffff\n\n# Next up, we check some invalid sequences\n#  + Early termination (back to low bytes too soon)\n#  + Early restart (another sequence introduction before the previous one was finished)\n\n!Early termination\nENCIN \"\\xC2!\"\n  encout 0xfffd,0x21\n\nENCIN \"\\xE0!\\xE0\\xA0!\"\n  encout 0xfffd,0x21,0xfffd,0x21\n\nENCIN \"\\xF0!\\xF0\\x90!\\xF0\\x90\\x80!\"\n  encout 0xfffd,0x21,0xfffd,0x21,0xfffd,0x21\n\n!Early restart\nENCIN \"\\xC2\\xC2\\x90\"\n  encout 0xfffd,0x0090\n\nENCIN \"\\xE0\\xC2\\x90\\xE0\\xA0\\xC2\\x90\"\n  encout 0xfffd,0x0090,0xfffd,0x0090\n\nENCIN \"\\xF0\\xC2\\x90\\xF0\\x90\\xC2\\x90\\xF0\\x90\\x80\\xC2\\x90\"\n  encout 0xfffd,0x0090,0xfffd,0x0090,0xfffd,0x0090\n\n# Test the overlong sequences by giving an overlong encoding of U+0000 and\n# an encoding of the highest codepoint still too short\n#\n# Two bytes:\n# U+0000 = C0 80\n# U+007F = 000 01111111 =>    00001   111111 =>\n#                       => 11000001 10111111 => C1 BF\n#\n# Three bytes:\n# U+0000 = E0 80 80\n# U+07FF = 00000111 11111111 =>     0000   011111   111111\n#                            => 11100000 10011111 10111111 = E0 9F BF\n#\n# Four bytes:\n# U+0000 = F0 80 80 80\n# U+FFFF = 11111111 11111111 =>      000   001111   111111   111111\n#                            => 11110000 10001111 10111111 10111111 = F0 8F BF BF\n\n!Overlong\nENCIN \"\\xC0\\x80\\xC1\\xBF\"\n  encout 0xfffd,0xfffd\n\nENCIN \"\\xE0\\x80\\x80\\xE0\\x9F\\xBF\"\n  encout 0xfffd,0xfffd\n\nENCIN \"\\xF0\\x80\\x80\\x80\\xF0\\x8F\\xBF\\xBF\"\n  encout 0xfffd,0xfffd\n\n# UTF-16 surrogates U+D800 and U+DFFF\n!UTF-16 Surrogates\nENCIN \"\\xED\\xA0\\x80\\xED\\xBF\\xBF\"\n  encout 0xfffd,0xfffd\n\n!Split write\nENCIN \"\\xC2\"\nENCIN \"\\xA0\"\n  encout 0x000A0\n\nENCIN \"\\xE0\"\nENCIN \"\\xA0\\x80\"\n  encout 0x00800\nENCIN \"\\xE0\\xA0\"\nENCIN \"\\x80\"\n  encout 0x00800\n\nENCIN \"\\xF0\"\nENCIN \"\\x90\\x80\\x80\"\n  encout 0x10000\nENCIN \"\\xF0\\x90\"\nENCIN \"\\x80\\x80\"\n  encout 0x10000\nENCIN \"\\xF0\\x90\\x80\"\nENCIN \"\\x80\"\n  encout 0x10000\n"
  },
  {
    "path": "t/10state_putglyph.test",
    "content": "INIT\nUTF8 1\nWANTSTATE g\n\n!Low\nRESET\nPUSH \"ABC\"\n  putglyph 0x41 1 0,0\n  putglyph 0x42 1 0,1\n  putglyph 0x43 1 0,2\n\n!UTF-8 1 char\n# U+00C1 = 0xC3 0x81  name: LATIN CAPITAL LETTER A WITH ACUTE\n# U+00E9 = 0xC3 0xA9  name: LATIN SMALL LETTER E WITH ACUTE\nRESET\nPUSH \"\\xC3\\x81\\xC3\\xA9\"\n  putglyph 0xc1 1 0,0\n  putglyph 0xe9 1 0,1\n\n!UTF-8 split writes\nRESET\nPUSH \"\\xC3\"\nPUSH \"\\x81\"\n  putglyph 0xc1 1 0,0\n\n!UTF-8 wide char\n# U+FF10 = 0xEF 0xBC 0x90  name: FULLWIDTH DIGIT ZERO\nRESET\nPUSH \"\\xEF\\xBC\\x90 \"\n  putglyph 0xff10 2 0,0\n  putglyph 0x20 1 0,2\n\n!UTF-8 emoji wide char\n# U+1F600 = 0xF0 0x9F 0x98 0x80  name: GRINNING FACE\nRESET\nPUSH \"\\xF0\\x9F\\x98\\x80 \"\n  putglyph 0x1f600 2 0,0\n  putglyph 0x20 1 0,2\n\n!UTF-8 combining chars\n# U+0301 = 0xCC 0x81  name: COMBINING ACUTE\nRESET\nPUSH \"e\\xCC\\x81Z\"\n  putglyph 0x65,0x301 1 0,0\n  putglyph 0x5a 1 0,1\n\n!Combining across buffers\nRESET\nPUSH \"e\"\n  putglyph 0x65 1 0,0\nPUSH \"\\xCC\\x81Z\"\n  putglyph 0x65,0x301 1 0,0\n  putglyph 0x5a 1 0,1\n\n!Spare combining chars get truncated\nRESET\nPUSH \"e\" . \"\\xCC\\x81\" x 10\n  putglyph 0x65,0x301,0x301,0x301,0x301,0x301 1 0,0\n  # and nothing more\n\nRESET\nPUSH \"e\"\n  putglyph 0x65 1 0,0\nPUSH \"\\xCC\\x81\"\n  putglyph 0x65,0x301 1 0,0\nPUSH \"\\xCC\\x82\"\n  putglyph 0x65,0x301,0x302 1 0,0\n\n!DECSCA protected\nRESET\nPUSH \"A\\e[1\\\"qB\\e[2\\\"qC\"\n  putglyph 0x41 1 0,0\n  putglyph 0x42 1 0,1 prot\n  putglyph 0x43 1 0,2\n"
  },
  {
    "path": "t/11state_movecursor.test",
    "content": "INIT\nUTF8 1\nWANTSTATE\n\n!Implicit\nPUSH \"ABC\"\n  ?cursor = 0,3\n!Backspace\nPUSH \"\\b\"\n  ?cursor = 0,2\n!Horizontal Tab\nPUSH \"\\t\"\n  ?cursor = 0,8\n!Carriage Return\nPUSH \"\\r\"\n  ?cursor = 0,0\n!Linefeed\nPUSH \"\\n\"\n  ?cursor = 1,0\n\n!Backspace bounded by lefthand edge\nPUSH \"\\e[4;2H\"\n  ?cursor = 3,1\nPUSH \"\\b\"\n  ?cursor = 3,0\nPUSH \"\\b\"\n  ?cursor = 3,0\n\n!Backspace cancels phantom\nPUSH \"\\e[4;80H\"\n  ?cursor = 3,79\nPUSH \"X\"\n  ?cursor = 3,79\nPUSH \"\\b\"\n  ?cursor = 3,78\n\n!HT bounded by righthand edge\nPUSH \"\\e[1;78H\"\n  ?cursor = 0,77\nPUSH \"\\t\"\n  ?cursor = 0,79\nPUSH \"\\t\"\n  ?cursor = 0,79\n\nRESET\n\n!Index\nPUSH \"ABC\\eD\"\n  ?cursor = 1,3\n!Reverse Index\nPUSH \"\\eM\"\n  ?cursor = 0,3\n!Newline\nPUSH \"\\eE\"\n  ?cursor = 1,0\n\nRESET\n\n!Cursor Forward\nPUSH \"\\e[B\"\n  ?cursor = 1,0\nPUSH \"\\e[3B\"\n  ?cursor = 4,0\nPUSH \"\\e[0B\"\n  ?cursor = 5,0\n\n!Cursor Down\nPUSH \"\\e[C\"\n  ?cursor = 5,1\nPUSH \"\\e[3C\"\n  ?cursor = 5,4\nPUSH \"\\e[0C\"\n  ?cursor = 5,5\n\n!Cursor Up\nPUSH \"\\e[A\"\n  ?cursor = 4,5\nPUSH \"\\e[3A\"\n  ?cursor = 1,5\nPUSH \"\\e[0A\"\n  ?cursor = 0,5\n\n!Cursor Backward\nPUSH \"\\e[D\"\n  ?cursor = 0,4\nPUSH \"\\e[3D\"\n  ?cursor = 0,1\nPUSH \"\\e[0D\"\n  ?cursor = 0,0\n\n!Cursor Next Line\nPUSH \"   \"\n  ?cursor = 0,3\nPUSH \"\\e[E\"\n  ?cursor = 1,0\nPUSH \"   \"\n  ?cursor = 1,3\nPUSH \"\\e[2E\"\n  ?cursor = 3,0\nPUSH \"\\e[0E\"\n  ?cursor = 4,0\n\n!Cursor Previous Line\nPUSH \"   \"\n  ?cursor = 4,3\nPUSH \"\\e[F\"\n  ?cursor = 3,0\nPUSH \"   \"\n  ?cursor = 3,3\nPUSH \"\\e[2F\"\n  ?cursor = 1,0\nPUSH \"\\e[0F\"\n  ?cursor = 0,0\n\n!Cursor Horizonal Absolute\nPUSH \"\\n\"\n  ?cursor = 1,0\nPUSH \"\\e[20G\"\n  ?cursor = 1,19\nPUSH \"\\e[G\"\n  ?cursor = 1,0\n\n!Cursor Position\nPUSH \"\\e[10;5H\"\n  ?cursor = 9,4\nPUSH \"\\e[8H\"\n  ?cursor = 7,0\nPUSH \"\\e[H\"\n  ?cursor = 0,0\n\n!Cursor Position cancels phantom\nPUSH \"\\e[10;78H\"\n  ?cursor = 9,77\nPUSH \"ABC\"\n  ?cursor = 9,79\nPUSH \"\\e[10;80H\"\nPUSH \"C\"\n  ?cursor = 9,79\nPUSH \"X\"\n  ?cursor = 10,1\n\nRESET\n\n!Bounds Checking\nPUSH \"\\e[A\"\n  ?cursor = 0,0\nPUSH \"\\e[D\"\n  ?cursor = 0,0\nPUSH \"\\e[25;80H\"\n  ?cursor = 24,79\nPUSH \"\\e[B\"\n  ?cursor = 24,79\nPUSH \"\\e[C\"\n  ?cursor = 24,79\nPUSH \"\\e[E\"\n  ?cursor = 24,0\nPUSH \"\\e[H\"\n  ?cursor = 0,0\nPUSH \"\\e[F\"\n  ?cursor = 0,0\nPUSH \"\\e[999G\"\n  ?cursor = 0,79\nPUSH \"\\e[99;99H\"\n  ?cursor = 24,79\n\nRESET\n\n!Horizontal Position Absolute\nPUSH \"\\e[5`\"\n  ?cursor = 0,4\n\n!Horizontal Position Relative\nPUSH \"\\e[3a\"\n  ?cursor = 0,7\n\n!Horizontal Position Backward\nPUSH \"\\e[3j\"\n  ?cursor = 0,4\n\n!Horizontal and Vertical Position\nPUSH \"\\e[3;3f\"\n  ?cursor = 2,2\n\n!Vertical Position Absolute\nPUSH \"\\e[5d\"\n  ?cursor = 4,2\n\n!Vertical Position Relative\nPUSH \"\\e[2e\"\n  ?cursor = 6,2\n\n!Vertical Position Backward\nPUSH \"\\e[2k\"\n  ?cursor = 4,2\n\nRESET\n\n!Horizontal Tab\nPUSH \"\\t\"\n  ?cursor = 0,8\nPUSH \"   \"\n  ?cursor = 0,11\nPUSH \"\\t\"\n  ?cursor = 0,16\nPUSH \"       \"\n  ?cursor = 0,23\nPUSH \"\\t\"\n  ?cursor = 0,24\nPUSH \"        \"\n  ?cursor = 0,32\nPUSH \"\\t\"\n  ?cursor = 0,40\n\n!Cursor Horizontal Tab\nPUSH \"\\e[I\"\n  ?cursor = 0,48\nPUSH \"\\e[2I\"\n  ?cursor = 0,64\n\n!Cursor Backward Tab\nPUSH \"\\e[Z\"\n  ?cursor = 0,56\nPUSH \"\\e[2Z\"\n  ?cursor = 0,40\n"
  },
  {
    "path": "t/12state_scroll.test",
    "content": "INIT\nUTF8 1\nWANTSTATE s\n\n!Linefeed\nPUSH \"\\n\"x24\n  ?cursor = 24,0\nPUSH \"\\n\"\n  scrollrect 0..25,0..80 => +1,+0\n  ?cursor = 24,0\n\nRESET\n\n!Index\nPUSH \"\\e[25H\"\nPUSH \"\\eD\"\n  scrollrect 0..25,0..80 => +1,+0\n\nRESET\n\n!Reverse Index\nPUSH \"\\eM\"\n  scrollrect 0..25,0..80 => -1,+0\n\nRESET\n\n!Linefeed in DECSTBM\nPUSH \"\\e[1;10r\"\n  ?cursor = 0,0\nPUSH \"\\n\"x9\n  ?cursor = 9,0\nPUSH \"\\n\"\n  scrollrect 0..10,0..80 => +1,+0\n  ?cursor = 9,0\n\n!Linefeed outside DECSTBM\nPUSH \"\\e[20H\"\n  ?cursor = 19,0\nPUSH \"\\n\"\n  ?cursor = 20,0\n\n!Index in DECSTBM\nPUSH \"\\e[9;10r\"\nPUSH \"\\e[10H\"\nPUSH \"\\eM\"\n  ?cursor = 8,0\nPUSH \"\\eM\"\n  scrollrect 8..10,0..80 => -1,+0\n\n!Reverse Index in DECSTBM\nPUSH \"\\e[25H\"\n  ?cursor = 24,0\nPUSH \"\\n\"\n  # no scrollrect\n  ?cursor = 24,0\n\n!Linefeed in DECSTBM+DECSLRM\nPUSH \"\\e[?69h\"\nPUSH \"\\e[3;10r\\e[10;40s\"\nPUSH \"\\e[10;10H\\n\"\n  scrollrect 2..10,9..40 => +1,+0\n\n!IND/RI in DECSTBM+DECSLRM\nPUSH \"\\eD\"\n  scrollrect 2..10,9..40 => +1,+0\nPUSH \"\\e[3;10H\\eM\"\n  scrollrect 2..10,9..40 => -1,+0\n\n!DECRQSS on DECSTBM\nPUSH \"\\eP\\$qr\\e\\\\\"\n  output \"\\eP1\\$r3;10r\\e\\\\\"\n\n!DECRQSS on DECSLRM\nPUSH \"\\eP\\$qs\\e\\\\\"\n  output \"\\eP1\\$r10;40s\\e\\\\\"\n\n!Setting invalid DECSLRM with !DECVSSM is still rejected\nPUSH \"\\e[?69l\\e[;0s\\e[?69h\"\n\nRESET\n\n!Scroll Down\nPUSH \"\\e[S\"\n  scrollrect 0..25,0..80 => +1,+0\n  ?cursor = 0,0\nPUSH \"\\e[2S\"\n  scrollrect 0..25,0..80 => +2,+0\n  ?cursor = 0,0\nPUSH \"\\e[100S\"\n  scrollrect 0..25,0..80 => +25,+0\n\n!Scroll Up\nPUSH \"\\e[T\"\n  scrollrect 0..25,0..80 => -1,+0\n  ?cursor = 0,0\nPUSH \"\\e[2T\"\n  scrollrect 0..25,0..80 => -2,+0\n  ?cursor = 0,0\nPUSH \"\\e[100T\"\n  scrollrect 0..25,0..80 => -25,+0\n\n!SD/SU in DECSTBM\nPUSH \"\\e[5;20r\"\nPUSH \"\\e[S\"\n  scrollrect 4..20,0..80 => +1,+0\nPUSH \"\\e[T\"\n  scrollrect 4..20,0..80 => -1,+0\n\nRESET\n\n!SD/SU in DECSTBM+DECSLRM\nPUSH \"\\e[?69h\"\nPUSH \"\\e[3;10r\\e[10;40s\"\n  ?cursor = 0,0\nPUSH \"\\e[3;10H\"\n  ?cursor = 2,9\nPUSH \"\\e[S\"\n  scrollrect 2..10,9..40 => +1,+0\nPUSH \"\\e[?69l\"\nPUSH \"\\e[S\"\n  scrollrect 2..10,0..80 => +1,+0\n\n!Invalid boundaries\nRESET\n\nPUSH \"\\e[100;105r\\eD\"\nPUSH \"\\e[5;2r\\eD\"\n\nRESET\nWANTSTATE -s+Pme\n\n!Scroll Down move+erase emulation\nPUSH \"\\e[S\"\n  premove 0..1,0..80\n  moverect 1..25,0..80 -> 0..24,0..80\n  erase 24..25,0..80\n  ?cursor = 0,0\nPUSH \"\\e[2S\"\n  premove 0..2,0..80\n  moverect 2..25,0..80 -> 0..23,0..80\n  erase 23..25,0..80\n  ?cursor = 0,0\n\n!Scroll Up move+erase emulation\nPUSH \"\\e[T\"\n  premove 24..25,0..80\n  moverect 0..24,0..80 -> 1..25,0..80\n  erase 0..1,0..80\n  ?cursor = 0,0\nPUSH \"\\e[2T\"\n  premove 23..25,0..80\n  moverect 0..23,0..80 -> 2..25,0..80\n  erase 0..2,0..80\n  ?cursor = 0,0\n\n!DECSTBM resets cursor position\nPUSH \"\\e[5;5H\"\n  ?cursor = 4,4\nPUSH \"\\e[r\"\n  ?cursor = 0,0\n"
  },
  {
    "path": "t/13state_edit.test",
    "content": "INIT\nUTF8 1\nWANTSTATE seb\n\n!ICH\nRESET\n  erase 0..25,0..80\n  ?cursor = 0,0\nPUSH \"ACD\"\nPUSH \"\\e[2D\"\n  ?cursor = 0,1\nPUSH \"\\e[@\"\n  scrollrect 0..1,1..80 => +0,-1\n  ?cursor = 0,1\nPUSH \"B\"\n  ?cursor = 0,2\nPUSH \"\\e[3@\"\n  scrollrect 0..1,2..80 => +0,-3\n\n!ICH with DECSLRM\nPUSH \"\\e[?69h\"\nPUSH \"\\e[;50s\"\nPUSH \"\\e[20G\\e[@\"\n  scrollrect 0..1,19..50 => +0,-1\n\n!ICH outside DECSLRM\nPUSH \"\\e[70G\\e[@\"\n  # nothing happens\n\n!DCH\nRESET\n  erase 0..25,0..80\n  ?cursor = 0,0\nPUSH \"ABBC\"\nPUSH \"\\e[3D\"\n  ?cursor = 0,1\nPUSH \"\\e[P\"\n  scrollrect 0..1,1..80 => +0,+1\n  ?cursor = 0,1\nPUSH \"\\e[3P\"\n  scrollrect 0..1,1..80 => +0,+3\n  ?cursor = 0,1\n\n!DCH with DECSLRM\nPUSH \"\\e[?69h\"\nPUSH \"\\e[;50s\"\nPUSH \"\\e[20G\\e[P\"\n  scrollrect 0..1,19..50 => +0,+1\n\n!DCH outside DECSLRM\nPUSH \"\\e[70G\\e[P\"\n  # nothing happens\n\n!ECH\nRESET\n  erase 0..25,0..80\n  ?cursor = 0,0\nPUSH \"ABC\"\nPUSH \"\\e[2D\"\n  ?cursor = 0,1\nPUSH \"\\e[X\"\n  erase 0..1,1..2\n  ?cursor = 0,1\nPUSH \"\\e[3X\"\n  erase 0..1,1..4\n  ?cursor = 0,1\n# ECH more columns than there are should be bounded\nPUSH \"\\e[100X\"\n  erase 0..1,1..80\n\n!IL\nRESET\n  erase 0..25,0..80\n  ?cursor = 0,0\nPUSH \"A\\r\\nC\"\n  ?cursor = 1,1\nPUSH \"\\e[L\"\n  scrollrect 1..25,0..80 => -1,+0\n  # TODO: ECMA-48 says we should move to line home, but neither xterm nor\n  # xfce4-terminal do this\n  ?cursor = 1,1\nPUSH \"\\rB\"\n  ?cursor = 1,1\nPUSH \"\\e[3L\"\n  scrollrect 1..25,0..80 => -3,+0\n\n!IL with DECSTBM\nPUSH \"\\e[5;15r\"\nPUSH \"\\e[5H\\e[L\"\n  scrollrect 4..15,0..80 => -1,+0\n\n!IL outside DECSTBM\nPUSH \"\\e[20H\\e[L\"\n  # nothing happens\n\n!IL with DECSTBM+DECSLRM\nPUSH \"\\e[?69h\"\nPUSH \"\\e[10;50s\"\nPUSH \"\\e[5;10H\\e[L\"\n  scrollrect 4..15,9..50 => -1,+0\n\n!DL\nRESET\n  erase 0..25,0..80\n  ?cursor = 0,0\nPUSH \"A\\r\\nB\\r\\nB\\r\\nC\"\n  ?cursor = 3,1\nPUSH \"\\e[2H\"\n  ?cursor = 1,0\nPUSH \"\\e[M\"\n  scrollrect 1..25,0..80 => +1,+0\n  ?cursor = 1,0\nPUSH \"\\e[3M\"\n  scrollrect 1..25,0..80 => +3,+0\n  ?cursor = 1,0\n\n!DL with DECSTBM\nPUSH \"\\e[5;15r\"\nPUSH \"\\e[5H\\e[M\"\n  scrollrect 4..15,0..80 => +1,+0\n\n!DL outside DECSTBM\nPUSH \"\\e[20H\\e[M\"\n  # nothing happens\n\n!DL with DECSTBM+DECSLRM\nPUSH \"\\e[?69h\"\nPUSH \"\\e[10;50s\"\nPUSH \"\\e[5;10H\\e[M\"\n  scrollrect 4..15,9..50 => +1,+0\n\n!DECIC\nRESET\n  erase 0..25,0..80\nPUSH \"\\e[20G\\e[5'}\"\n  scrollrect 0..25,19..80 => +0,-5\n\n!DECIC with DECSTBM+DECSLRM\nPUSH \"\\e[?69h\"\nPUSH \"\\e[4;20r\\e[20;60s\"\nPUSH \"\\e[4;20H\\e[3'}\"\n  scrollrect 3..20,19..60 => +0,-3\n\n!DECIC outside DECSLRM\nPUSH \"\\e[70G\\e['}\"\n  # nothing happens\n\n!DECDC\nRESET\n  erase 0..25,0..80\nPUSH \"\\e[20G\\e[5'~\"\n  scrollrect 0..25,19..80 => +0,+5\n\n!DECDC with DECSTBM+DECSLRM\nPUSH \"\\e[?69h\"\nPUSH \"\\e[4;20r\\e[20;60s\"\nPUSH \"\\e[4;20H\\e[3'~\"\n  scrollrect 3..20,19..60 => +0,+3\n\n!DECDC outside DECSLRM\nPUSH \"\\e[70G\\e['~\"\n  # nothing happens\n\n!EL 0\nRESET\n  erase 0..25,0..80\n  ?cursor = 0,0\nPUSH \"ABCDE\"\nPUSH \"\\e[3D\"\n  ?cursor = 0,2\nPUSH \"\\e[0K\"\n  erase 0..1,2..80\n  ?cursor = 0,2\n\n!EL 1\nRESET\n  erase 0..25,0..80\n  ?cursor = 0,0\nPUSH \"ABCDE\"\nPUSH \"\\e[3D\"\n  ?cursor = 0,2\nPUSH \"\\e[1K\"\n  erase 0..1,0..3\n  ?cursor = 0,2\n\n!EL 2\nRESET\n  erase 0..25,0..80\n  ?cursor = 0,0\nPUSH \"ABCDE\"\nPUSH \"\\e[3D\"\n  ?cursor = 0,2\nPUSH \"\\e[2K\"\n  erase 0..1,0..80\n  ?cursor = 0,2\n\n!SEL\nRESET\n  erase 0..25,0..80\n  ?cursor = 0,0\nPUSH \"\\e[11G\"\n  ?cursor = 0,10\nPUSH \"\\e[?0K\"\n  erase 0..1,10..80 selective\n  ?cursor = 0,10\nPUSH \"\\e[?1K\"\n  erase 0..1,0..11 selective\n  ?cursor = 0,10\nPUSH \"\\e[?2K\"\n  erase 0..1,0..80 selective\n  ?cursor = 0,10\n\n!ED 0\nRESET\n  erase 0..25,0..80\n  ?cursor = 0,0\nPUSH \"\\e[2;2H\"\n  ?cursor = 1,1\nPUSH \"\\e[0J\"\n  erase 1..2,1..80\n  erase 2..25,0..80\n  ?cursor = 1,1\n\n!ED 1\nRESET\n  erase 0..25,0..80\n  ?cursor = 0,0\nPUSH \"\\e[2;2H\"\n  ?cursor = 1,1\nPUSH \"\\e[1J\"\n  erase 0..1,0..80\n  erase 1..2,0..2\n  ?cursor = 1,1\n\n!ED 2\nRESET\n  erase 0..25,0..80\n  ?cursor = 0,0\nPUSH \"\\e[2;2H\"\n  ?cursor = 1,1\nPUSH \"\\e[2J\"\n  erase 0..25,0..80\n  ?cursor = 1,1\n\n!ED 3\nPUSH \"\\e[3J\"\n  sb_clear\n\n!SED\nRESET\n  erase 0..25,0..80\nPUSH \"\\e[5;5H\"\n  ?cursor = 4,4\nPUSH \"\\e[?0J\"\n  erase 4..5,4..80 selective\n  erase 5..25,0..80 selective\n  ?cursor = 4,4\nPUSH \"\\e[?1J\"\n  erase 0..4,0..80 selective\n  erase 4..5,0..5 selective\n  ?cursor = 4,4\nPUSH \"\\e[?2J\"\n  erase 0..25,0..80 selective\n  ?cursor = 4,4\n\n!DECRQSS on DECSCA\nPUSH \"\\e[2\\\"q\"\nPUSH \"\\eP\\$q\\\"q\\e\\\\\"\n  output \"\\eP1\\$r2\\\"q\\e\\\\\"\n\nWANTSTATE -s+Pm\n\n!ICH move+erase emuation\nRESET\n  erase 0..25,0..80\n  ?cursor = 0,0\nPUSH \"ACD\"\nPUSH \"\\e[2D\"\n  ?cursor = 0,1\nPUSH \"\\e[@\"\n  premove 0..1,79..80\n  moverect 0..1,1..79 -> 0..1,2..80\n  erase 0..1,1..2\n  ?cursor = 0,1\nPUSH \"B\"\n  ?cursor = 0,2\nPUSH \"\\e[3@\"\n  premove 0..1,77..80\n  moverect 0..1,2..77 -> 0..1,5..80\n  erase 0..1,2..5\n\n!DCH move+erase emulation\nRESET\n  erase 0..25,0..80\n  ?cursor = 0,0\nPUSH \"ABBC\"\nPUSH \"\\e[3D\"\n  ?cursor = 0,1\nPUSH \"\\e[P\"\n  premove 0..1,1..2\n  moverect 0..1,2..80 -> 0..1,1..79\n  erase 0..1,79..80\n  ?cursor = 0,1\nPUSH \"\\e[3P\"\n  premove 0..1,1..4\n  moverect 0..1,4..80 -> 0..1,1..77\n  erase 0..1,77..80\n  ?cursor = 0,1\n"
  },
  {
    "path": "t/14state_encoding.test",
    "content": "INIT\nWANTSTATE g\n\n!Default\nRESET\nPUSH \"#\"\n  putglyph 0x23 1 0,0\n\n!Designate G0=UK\nRESET\nPUSH \"\\e(A\"\nPUSH \"#\"\n  putglyph 0x00a3 1 0,0\n\n!Designate G0=DEC drawing\nRESET\nPUSH \"\\e(0\"\nPUSH \"a\"\n  putglyph 0x2592 1 0,0\n\n!Designate G1 + LS1\nRESET\nPUSH \"\\e)0\"\nPUSH \"a\"\n  putglyph 0x61 1 0,0\nPUSH \"\\x0e\"\nPUSH \"a\"\n  putglyph 0x2592 1 0,1\n!LS0\nPUSH \"\\x0f\"\nPUSH \"a\"\n  putglyph 0x61 1 0,2\n\n!Designate G2 + LS2\nPUSH \"\\e*0\"\nPUSH \"a\"\n  putglyph 0x61 1 0,3\nPUSH \"\\en\"\nPUSH \"a\"\n  putglyph 0x2592 1 0,4\nPUSH \"\\x0f\"\nPUSH \"a\"\n  putglyph 0x61 1 0,5\n\n!Designate G3 + LS3\nPUSH \"\\e+0\"\nPUSH \"a\"\n  putglyph 0x61 1 0,6\nPUSH \"\\eo\"\nPUSH \"a\"\n  putglyph 0x2592 1 0,7\nPUSH \"\\x0f\"\nPUSH \"a\"\n  putglyph 0x61 1 0,8\n\n!SS2\nPUSH \"a\\x{8e}aa\"\n  putglyph 0x61 1 0,9\n  putglyph 0x2592 1 0,10\n  putglyph 0x61 1 0,11\n\n!SS3\nPUSH \"a\\x{8f}aa\"\n  putglyph 0x61 1 0,12\n  putglyph 0x2592 1 0,13\n  putglyph 0x61 1 0,14\n\n!LS1R\nRESET\nPUSH \"\\e~\"\nPUSH \"\\xe1\"\n  putglyph 0x61 1 0,0\nPUSH \"\\e)0\"\nPUSH \"\\xe1\"\n  putglyph 0x2592 1 0,1\n\n!LS2R\nRESET\nPUSH \"\\e}\"\nPUSH \"\\xe1\"\n  putglyph 0x61 1 0,0\nPUSH \"\\e*0\"\nPUSH \"\\xe1\"\n  putglyph 0x2592 1 0,1\n\n!LS3R\nRESET\nPUSH \"\\e|\"\nPUSH \"\\xe1\"\n  putglyph 0x61 1 0,0\nPUSH \"\\e+0\"\nPUSH \"\\xe1\"\n  putglyph 0x2592 1 0,1\n\nUTF8 1\n\n!Mixed US-ASCII and UTF-8\n# U+0108 == 0xc4 0x88\nRESET\nPUSH \"\\e(B\"\nPUSH \"AB\\xc4\\x88D\"\n  putglyph 0x0041 1 0,0\n  putglyph 0x0042 1 0,1\n  putglyph 0x0108 1 0,2\n  putglyph 0x0044 1 0,3\n"
  },
  {
    "path": "t/15state_mode.test",
    "content": "INIT\nUTF8 1\nWANTSTATE gme\n\n!Insert/Replace Mode\nRESET\n  erase 0..25,0..80\n  ?cursor = 0,0\nPUSH \"AC\\e[DB\"\n  putglyph 0x41 1 0,0\n  putglyph 0x43 1 0,1\n  putglyph 0x42 1 0,1\nPUSH \"\\e[4h\"\nPUSH \"\\e[G\"\nPUSH \"AC\\e[DB\"\n  moverect 0..1,0..79 -> 0..1,1..80\n  erase 0..1,0..1\n  putglyph 0x41 1 0,0\n  moverect 0..1,1..79 -> 0..1,2..80\n  erase 0..1,1..2\n  putglyph 0x43 1 0,1\n  moverect 0..1,1..79 -> 0..1,2..80\n  erase 0..1,1..2\n  putglyph 0x42 1 0,1\n\n!Insert mode only happens once for UTF-8 combining\nPUSH \"e\"\n  moverect 0..1,2..79 -> 0..1,3..80\n  erase 0..1,2..3\n  putglyph 0x65 1 0,2\nPUSH \"\\xCC\\x81\"\n  putglyph 0x65,0x301 1 0,2\n\n!Newline/Linefeed mode\nRESET\n  erase 0..25,0..80\n  ?cursor = 0,0\nPUSH \"\\e[5G\\n\"\n  ?cursor = 1,4\nPUSH \"\\e[20h\"\nPUSH \"\\e[5G\\n\"\n  ?cursor = 2,0\n\n!DEC origin mode\nRESET\n  erase 0..25,0..80\n  ?cursor = 0,0\nPUSH \"\\e[5;15r\"\nPUSH \"\\e[H\"\n  ?cursor = 0,0\nPUSH \"\\e[3;3H\"\n  ?cursor = 2,2\nPUSH \"\\e[?6h\"\nPUSH \"\\e[H\"\n  ?cursor = 4,0\nPUSH \"\\e[3;3H\"\n  ?cursor = 6,2\n\n!DECRQM on DECOM\nPUSH \"\\e[?6h\"\nPUSH \"\\e[?6\\$p\"\n  output \"\\e[?6;1\\$y\"\nPUSH \"\\e[?6l\"\nPUSH \"\\e[?6\\$p\"\n  output \"\\e[?6;2\\$y\"\n\n!Origin mode with DECSLRM\nPUSH \"\\e[?6h\"\nPUSH \"\\e[?69h\"\nPUSH \"\\e[20;60s\"\nPUSH \"\\e[H\"\n  ?cursor = 4,19\n\nPUSH \"\\e[?69l\"\n\n!Origin mode bounds cursor to scrolling region\nPUSH \"\\e[H\"\nPUSH \"\\e[10A\"\n  ?cursor = 4,0\nPUSH \"\\e[20B\"\n  ?cursor = 14,0\n\n!Origin mode without scroll region\nPUSH \"\\e[?6l\"\nPUSH \"\\e[r\\e[?6h\"\n  ?cursor = 0,0\n"
  },
  {
    "path": "t/16state_resize.test",
    "content": "INIT\nWANTSTATE g\n\n!Placement\nRESET\nPUSH \"AB\\e[79GCDE\"\n  putglyph 0x41 1 0,0\n  putglyph 0x42 1 0,1\n  putglyph 0x43 1 0,78\n  putglyph 0x44 1 0,79\n  putglyph 0x45 1 1,0\n\n!Resize\nRESET\nRESIZE 27,85\nPUSH \"AB\\e[79GCDE\"\n  putglyph 0x41 1 0,0\n  putglyph 0x42 1 0,1\n  putglyph 0x43 1 0,78\n  putglyph 0x44 1 0,79\n  putglyph 0x45 1 0,80\n  ?cursor = 0,81\n\n!Resize without reset\nRESIZE 28,90\n  ?cursor = 0,81\nPUSH \"FGHI\"\n  putglyph 0x46 1 0,81\n  putglyph 0x47 1 0,82\n  putglyph 0x48 1 0,83\n  putglyph 0x49 1 0,84\n  ?cursor = 0,85\n\n!Resize shrink moves cursor\nRESIZE 25,80\n  ?cursor = 0,79\n\n!Resize grow doesn't cancel phantom\nRESET\nPUSH \"\\e[79GAB\"\n  putglyph 0x41 1 0,78\n  putglyph 0x42 1 0,79\n  ?cursor = 0,79\nRESIZE 30,100\n  ?cursor = 0,80\nPUSH \"C\"\n  putglyph 0x43 1 0,80\n  ?cursor = 0,81\n"
  },
  {
    "path": "t/17state_mouse.test",
    "content": "INIT\nWANTSTATE p\n\n!DECRQM on with mouse off\nPUSH \"\\e[?1000\\$p\"\n  output \"\\e[?1000;2\\$y\"\nPUSH \"\\e[?1002\\$p\"\n  output \"\\e[?1002;2\\$y\"\nPUSH \"\\e[?1003\\$p\"\n  output \"\\e[?1003;2\\$y\"\n\n!Mouse in simple button report mode\nRESET\n  settermprop 1 true\n  settermprop 2 true\n  settermprop 7 1\nPUSH \"\\e[?1000h\"\n  settermprop 8 1\n\n!Press 1\nMOUSEMOVE 0,0 0\nMOUSEBTN d 1 0\n  output \"\\e[M\\x20\\x21\\x21\"\n\n!Release 1\nMOUSEBTN u 1 0\n  output \"\\e[M\\x23\\x21\\x21\"\n\n!Ctrl-Press 1\nMOUSEBTN d 1 C\n  output \"\\e[M\\x30\\x21\\x21\"\nMOUSEBTN u 1 C\n  output \"\\e[M\\x33\\x21\\x21\"\n\n!Button 2\nMOUSEBTN d 2 0\n  output \"\\e[M\\x21\\x21\\x21\"\nMOUSEBTN u 2 0\n  output \"\\e[M\\x23\\x21\\x21\"\n\n!Position\nMOUSEMOVE 10,20 0\nMOUSEBTN d 1 0\n  output \"\\e[M\\x20\\x35\\x2b\"\n\nMOUSEBTN u 1 0\n  output \"\\e[M\\x23\\x35\\x2b\"\nMOUSEMOVE 10,21 0\n  # no output\n\n!Wheel events\nMOUSEBTN d 4 0\n  output \"\\e[M\\x60\\x36\\x2b\"\nMOUSEBTN d 4 0\n  output \"\\e[M\\x60\\x36\\x2b\"\nMOUSEBTN d 5 0\n  output \"\\e[M\\x61\\x36\\x2b\"\nMOUSEBTN d 6 0\n  output \"\\e[M\\x62\\x36\\x2b\"\nMOUSEBTN d 7 0\n  output \"\\e[M\\x63\\x36\\x2b\"\n\n!DECRQM on mouse button mode\nPUSH \"\\e[?1000\\$p\"\n  output \"\\e[?1000;1\\$y\"\nPUSH \"\\e[?1002\\$p\"\n  output \"\\e[?1002;2\\$y\"\nPUSH \"\\e[?1003\\$p\"\n  output \"\\e[?1003;2\\$y\"\n\n!Drag events\nRESET\n  settermprop 1 true\n  settermprop 2 true\n  settermprop 7 1\nPUSH \"\\e[?1002h\"\n  settermprop 8 2\n\nMOUSEMOVE 5,5 0\nMOUSEBTN d 1 0\n  output \"\\e[M\\x20\\x26\\x26\"\nMOUSEMOVE 5,6 0\n  output \"\\e[M\\x40\\x27\\x26\"\nMOUSEMOVE 6,6 0\n  output \"\\e[M\\x40\\x27\\x27\"\nMOUSEMOVE 6,6 0\n  # no output\nMOUSEBTN u 1 0\n  output \"\\e[M\\x23\\x27\\x27\"\nMOUSEMOVE 6,7\n  # no output\n\n!DECRQM on mouse drag mode\nPUSH \"\\e[?1000\\$p\"\n  output \"\\e[?1000;2\\$y\"\nPUSH \"\\e[?1002\\$p\"\n  output \"\\e[?1002;1\\$y\"\nPUSH \"\\e[?1003\\$p\"\n  output \"\\e[?1003;2\\$y\"\n\n!Non-drag motion events\nPUSH \"\\e[?1003h\"\n  settermprop 8 3\n\nMOUSEMOVE 6,8 0\n  output \"\\e[M\\x43\\x29\\x27\"\n\n!DECRQM on mouse motion mode\nPUSH \"\\e[?1000\\$p\"\n  output \"\\e[?1000;2\\$y\"\nPUSH \"\\e[?1002\\$p\"\n  output \"\\e[?1002;2\\$y\"\nPUSH \"\\e[?1003\\$p\"\n  output \"\\e[?1003;1\\$y\"\n\n!Bounds checking\nMOUSEMOVE 300,300 0\n  output \"\\e[M\\x43\\xff\\xff\"\nMOUSEBTN d 1 0\n  output \"\\e[M\\x20\\xff\\xff\"\nMOUSEBTN u 1 0\n  output \"\\e[M\\x23\\xff\\xff\"\n\n!DECRQM on standard encoding mode\nPUSH \"\\e[?1005\\$p\"\n  output \"\\e[?1005;2\\$y\"\nPUSH \"\\e[?1006\\$p\"\n  output \"\\e[?1006;2\\$y\"\nPUSH \"\\e[?1015\\$p\"\n  output \"\\e[?1015;2\\$y\"\n\n!UTF-8 extended encoding mode\n# 300 + 32 + 1 = 333 = U+014d = \\xc5\\x8d\nPUSH \"\\e[?1005h\"\nMOUSEBTN d 1 0\n  output \"\\e[M\\x20\\xc5\\x8d\\xc5\\x8d\"\nMOUSEBTN u 1 0\n  output \"\\e[M\\x23\\xc5\\x8d\\xc5\\x8d\"\n\n!DECRQM on UTF-8 extended encoding mode\nPUSH \"\\e[?1005\\$p\"\n  output \"\\e[?1005;1\\$y\"\nPUSH \"\\e[?1006\\$p\"\n  output \"\\e[?1006;2\\$y\"\nPUSH \"\\e[?1015\\$p\"\n  output \"\\e[?1015;2\\$y\"\n\n!SGR extended encoding mode\nPUSH \"\\e[?1006h\"\nMOUSEBTN d 1 0\n  output \"\\e[<0;301;301M\"\nMOUSEBTN u 1 0\n  output \"\\e[<0;301;301m\"\n\n!DECRQM on SGR extended encoding mode\nPUSH \"\\e[?1005\\$p\"\n  output \"\\e[?1005;2\\$y\"\nPUSH \"\\e[?1006\\$p\"\n  output \"\\e[?1006;1\\$y\"\nPUSH \"\\e[?1015\\$p\"\n  output \"\\e[?1015;2\\$y\"\n\n!rxvt extended encoding mode\nPUSH \"\\e[?1015h\"\nMOUSEBTN d 1 0\n  output \"\\e[0;301;301M\"\nMOUSEBTN u 1 0\n  output \"\\e[3;301;301M\"\n\n!DECRQM on rxvt extended encoding mode\nPUSH \"\\e[?1005\\$p\"\n  output \"\\e[?1005;2\\$y\"\nPUSH \"\\e[?1006\\$p\"\n  output \"\\e[?1006;2\\$y\"\nPUSH \"\\e[?1015\\$p\"\n  output \"\\e[?1015;1\\$y\"\n\n!Mouse disabled reports nothing\nRESET\n  settermprop 1 true\n  settermprop 2 true\n  settermprop 7 1\nMOUSEMOVE 0,0 0\nMOUSEBTN d 1 0\nMOUSEBTN u 1 0\n\n!DECSM can set multiple modes at once\nPUSH \"\\e[?1002;1006h\"\n  settermprop 8 2\nMOUSEBTN d 1 0\n  output \"\\e[<0;1;1M\"\n"
  },
  {
    "path": "t/18state_termprops.test",
    "content": "INIT\nWANTSTATE p\n\nRESET\n  settermprop 1 true\n  settermprop 2 true\n  settermprop 7 1\n\n!Cursor visibility\nPUSH \"\\e[?25h\"\n  settermprop 1 true\nPUSH \"\\e[?25\\$p\"\n  output \"\\e[?25;1\\$y\"\nPUSH \"\\e[?25l\"\n  settermprop 1 false\nPUSH \"\\e[?25\\$p\"\n  output \"\\e[?25;2\\$y\"\n\n!Cursor blink\nPUSH \"\\e[?12h\"\n  settermprop 2 true\nPUSH \"\\e[?12\\$p\"\n  output \"\\e[?12;1\\$y\"\nPUSH \"\\e[?12l\"\n  settermprop 2 false\nPUSH \"\\e[?12\\$p\"\n  output \"\\e[?12;2\\$y\"\n\n!Cursor shape\nPUSH \"\\e[3 q\"\n  settermprop 2 true\n  settermprop 7 2\n\n!Title\nPUSH \"\\e]2;Here is my title\\a\"\n  settermprop 4 [\"Here is my title\"]\n\n!Title split write\nPUSH \"\\e]2;Here is\"\n  settermprop 4 [\"Here is\"\nPUSH \" another title\\a\"\n  settermprop 4 \" another title\"]\n"
  },
  {
    "path": "t/20state_wrapping.test",
    "content": "INIT\nUTF8 1\nWANTSTATE gm\n\n!79th Column\nPUSH \"\\e[75G\"\nPUSH \"A\"x5\n  putglyph 0x41 1 0,74\n  putglyph 0x41 1 0,75\n  putglyph 0x41 1 0,76\n  putglyph 0x41 1 0,77\n  putglyph 0x41 1 0,78\n  ?cursor = 0,79\n\n!80th Column Phantom\nPUSH \"A\"\n  putglyph 0x41 1 0,79\n  ?cursor = 0,79\n\n!Line Wraparound\nPUSH \"B\"\n  putglyph 0x42 1 1,0\n  ?cursor = 1,1\n\n!Line Wraparound during combined write\nPUSH \"\\e[78G\"\nPUSH \"BBBCC\"\n  putglyph 0x42 1 1,77\n  putglyph 0x42 1 1,78\n  putglyph 0x42 1 1,79\n  putglyph 0x43 1 2,0\n  putglyph 0x43 1 2,1\n  ?cursor = 2,2\n\n!DEC Auto Wrap Mode\nRESET\nPUSH \"\\e[?7l\"\nPUSH \"\\e[75G\"\nPUSH \"D\"x6\n  putglyph 0x44 1 0,74\n  putglyph 0x44 1 0,75\n  putglyph 0x44 1 0,76\n  putglyph 0x44 1 0,77\n  putglyph 0x44 1 0,78\n  putglyph 0x44 1 0,79\n  ?cursor = 0,79\nPUSH \"D\"\n  putglyph 0x44 1 0,79\n  ?cursor = 0,79\nPUSH \"\\e[?7h\"\n\n!80th column causes linefeed on wraparound\nPUSH \"\\e[25;78HABC\"\n  putglyph 0x41 1 24,77\n  putglyph 0x42 1 24,78\n  putglyph 0x43 1 24,79\n  ?cursor = 24,79\nPUSH \"D\"\n  moverect 1..25,0..80 -> 0..24,0..80\n  putglyph 0x44 1 24,0\n\n!80th column phantom linefeed phantom cancelled by explicit cursor move\nPUSH \"\\e[25;78HABC\"\n  putglyph 0x41 1 24,77\n  putglyph 0x42 1 24,78\n  putglyph 0x43 1 24,79\n  ?cursor = 24,79\nPUSH \"\\e[25;1HD\"\n  putglyph 0x44 1 24,0\n"
  },
  {
    "path": "t/21state_tabstops.test",
    "content": "INIT\nWANTSTATE g\n\n!Initial\nRESET\nPUSH \"\\tX\"\n  putglyph 0x58 1 0,8\nPUSH \"\\tX\"\n  putglyph 0x58 1 0,16\n  ?cursor = 0,17\n\n!HTS\nPUSH \"\\e[5G\\eH\"\nPUSH \"\\e[G\\tX\"\n  putglyph 0x58 1 0,4\n  ?cursor = 0,5\n\n!TBC 0\nPUSH \"\\e[9G\\e[g\"\nPUSH \"\\e[G\\tX\\tX\"\n  putglyph 0x58 1 0,4\n  putglyph 0x58 1 0,16\n  ?cursor = 0,17\n\n!TBC 3\nPUSH \"\\e[3g\\e[50G\\eH\\e[G\"\n  ?cursor = 0,0\nPUSH \"\\tX\"\n  putglyph 0x58 1 0,49\n  ?cursor = 0,50\n\n!Tabstops after resize\nRESET\nRESIZE 30,100\n# Should be 100/8 = 12 tabstops\nPUSH \"\\tX\"\n  putglyph 0x58 1 0,8\nPUSH \"\\tX\"\n  putglyph 0x58 1 0,16\nPUSH \"\\tX\"\n  putglyph 0x58 1 0,24\nPUSH \"\\tX\"\n  putglyph 0x58 1 0,32\nPUSH \"\\tX\"\n  putglyph 0x58 1 0,40\nPUSH \"\\tX\"\n  putglyph 0x58 1 0,48\nPUSH \"\\tX\"\n  putglyph 0x58 1 0,56\nPUSH \"\\tX\"\n  putglyph 0x58 1 0,64\nPUSH \"\\tX\"\n  putglyph 0x58 1 0,72\nPUSH \"\\tX\"\n  putglyph 0x58 1 0,80\nPUSH \"\\tX\"\n  putglyph 0x58 1 0,88\nPUSH \"\\tX\"\n  putglyph 0x58 1 0,96\n  ?cursor = 0,97\n"
  },
  {
    "path": "t/22state_save.test",
    "content": "INIT\nWANTSTATE p\n\nRESET\n  settermprop 1 true\n  settermprop 2 true\n  settermprop 7 1\n\n!Set up state\nPUSH \"\\e[2;2H\"\n  ?cursor = 1,1\nPUSH \"\\e[1m\"\n  ?pen bold = on\n\n!Save\nPUSH \"\\e[?1048h\"\n\n!Change state\nPUSH \"\\e[5;5H\"\n  ?cursor = 4,4\nPUSH \"\\e[4 q\"\n  settermprop 2 false\n  settermprop 7 2\nPUSH \"\\e[22;4m\"\n  ?pen bold = off\n  ?pen underline = 1\n\n!Restore\nPUSH \"\\e[?1048l\"\n  settermprop 1 true\n  settermprop 2 true\n  settermprop 7 1\n  ?cursor = 1,1\n  ?pen bold = on\n  ?pen underline = 0\n\n!Save/restore using DECSC/DECRC\nPUSH \"\\e[2;2H\\e7\"\n  ?cursor = 1,1\n\nPUSH \"\\e[5;5H\"\n  ?cursor = 4,4\nPUSH \"\\e8\"\n  settermprop 1 true\n  settermprop 2 true\n  settermprop 7 1\n  ?cursor = 1,1\n\n!Save twice, restore twice happens on both edge transitions\nPUSH \"\\e[2;10H\\e[?1048h\\e[6;10H\\e[?1048h\"\nPUSH \"\\e[H\"\n  ?cursor = 0,0\nPUSH \"\\e[?1048l\"\n  settermprop 1 true\n  settermprop 2 true\n  settermprop 7 1\n  ?cursor = 5,9\nPUSH \"\\e[H\"\n  ?cursor = 0,0\nPUSH \"\\e[?1048l\"\n  settermprop 1 true\n  settermprop 2 true\n  settermprop 7 1\n  ?cursor = 5,9\n"
  },
  {
    "path": "t/25state_input.test",
    "content": "INIT\nWANTSTATE\n\n!Unmodified ASCII\nINCHAR 0 41\n  output \"A\"\nINCHAR 0 61\n  output \"a\"\n\n!Ctrl modifier on ASCII letters\nINCHAR C 41\n  output \"\\e[65;5u\"\nINCHAR C 61\n  output \"\\x01\"\n\n!Alt modifier on ASCII letters\nINCHAR A 41\n  output \"\\eA\"\nINCHAR A 61\n  output \"\\ea\"\n\n!Ctrl-Alt modifier on ASCII letters\nINCHAR CA 41\n  output \"\\e[65;7u\"\nINCHAR CA 61\n  output \"\\e\\x01\"\n\n!Special handling of Ctrl-I\nINCHAR 0 49\n  output \"I\"\nINCHAR 0 69\n  output \"i\"\nINCHAR C 49\n  output \"\\e[73;5u\"\nINCHAR C 69\n  output \"\\e[105;5u\"\nINCHAR A 49\n  output \"\\eI\"\nINCHAR A 69\n  output \"\\ei\"\nINCHAR CA 49\n  output \"\\e[73;7u\"\nINCHAR CA 69\n  output \"\\e[105;7u\"\n\n!Special handling of Space\nINCHAR 0 20\n  output \" \"\nINCHAR S 20\n  output \"\\e[32;2u\"\nINCHAR C 20\n  output \"\\0\"\nINCHAR SC 20\n  output \"\\e[32;6u\"\nINCHAR A 20\n  output \"\\e \"\nINCHAR SA 20\n  output \"\\e[32;4u\"\nINCHAR CA 20\n  output \"\\e\\0\"\nINCHAR SCA 20\n  output \"\\e[32;8u\"\n\n!Cursor keys in reset (cursor) mode\nINKEY 0 Up\n  output \"\\e[A\"\nINKEY S Up\n  output \"\\e[1;2A\"\nINKEY C Up\n  output \"\\e[1;5A\"\nINKEY SC Up\n  output \"\\e[1;6A\"\nINKEY A Up\n  output \"\\e[1;3A\"\nINKEY SA Up\n  output \"\\e[1;4A\"\nINKEY CA Up\n  output \"\\e[1;7A\"\nINKEY SCA Up\n  output \"\\e[1;8A\"\n\n!Cursor keys in application mode\nPUSH \"\\e[?1h\"\n# Plain \"Up\" should be SS3 A now\nINKEY 0 Up\n  output \"\\eOA\"\n# Modified keys should still use CSI\nINKEY S Up\n  output \"\\e[1;2A\"\nINKEY C Up\n  output \"\\e[1;5A\"\n\n!Shift-Tab should be different\nINKEY 0 Tab\n  output \"\\x09\"\nINKEY S Tab\n  output \"\\e[Z\"\nINKEY C Tab\n  output \"\\e[9;5u\"\nINKEY A Tab\n  output \"\\e\\x09\"\nINKEY CA Tab\n  output \"\\e[9;7u\"\n\n!Enter in linefeed mode\nINKEY 0 Enter\n  output \"\\x0d\"\n\n!Enter in newline mode\nPUSH \"\\e[20h\"\nINKEY 0 Enter\n  output \"\\x0d\\x0a\"\n\n!Unmodified F1 is SS3 P\nINKEY 0 F1\n  output \"\\eOP\"\n\n!Modified F1 is CSI P\nINKEY S F1\n  output \"\\e[1;2P\"\nINKEY A F1\n  output \"\\e[1;3P\"\nINKEY C F1\n  output \"\\e[1;5P\"\n\n!Keypad in DECKPNM\nINKEY 0 KP0\n  output \"0\"\n\n!Keypad in DECKPAM\nPUSH \"\\e=\"\nINKEY 0 KP0\n  output \"\\eOp\"\n\n!Bracketed paste mode off\nPASTE START\nPASTE END\n\n!Bracketed paste mode on\nPUSH \"\\e[?2004h\"\nPASTE START\n  output \"\\e[200~\"\nPASTE END\n  output \"\\e[201~\"\n\n!Focus reporting disabled\nFOCUS IN\nFOCUS OUT\n\n!Focus reporting enabled\nWANTSTATE +p\nPUSH \"\\e[?1004h\"\n  settermprop 9 true\nFOCUS IN\n  output \"\\e[I\"\nFOCUS OUT\n  output \"\\e[O\"\n"
  },
  {
    "path": "t/26state_query.test",
    "content": "INIT\nWANTSTATE\n\n!DA\nRESET\nPUSH \"\\e[c\"\n  output \"\\e[?1;2c\"\n\n!XTVERSION\nRESET\nPUSH \"\\e[>q\"\n  output \"\\eP>|libvterm(0.3)\\e\\\\\"\n\n!DSR\nRESET\nPUSH \"\\e[5n\"\n  output \"\\e[0n\"\n\n!CPR\nPUSH \"\\e[6n\"\n  output \"\\e[1;1R\"\nPUSH \"\\e[10;10H\\e[6n\"\n  output \"\\e[10;10R\"\n\n!DECCPR\nPUSH \"\\e[?6n\"\n  output \"\\e[?10;10R\"\n\n!DECRQSS on DECSCUSR\nPUSH \"\\e[3 q\"\nPUSH \"\\eP\\$q q\\e\\\\\"\n  output \"\\eP1\\$r3 q\\e\\\\\"\n\n!DECRQSS on SGR\nPUSH \"\\e[1;5;7m\"\nPUSH \"\\eP\\$qm\\e\\\\\"\n  output \"\\eP1\\$r1;5;7m\\e\\\\\"\n\n!DECRQSS on SGR ANSI colours\nPUSH \"\\e[0;31;42m\"\nPUSH \"\\eP\\$qm\\e\\\\\"\n  output \"\\eP1\\$r31;42m\\e\\\\\"\n\n!DECRQSS on SGR ANSI hi-bright colours\nPUSH \"\\e[0;93;104m\"\nPUSH \"\\eP\\$qm\\e\\\\\"\n  output \"\\eP1\\$r93;104m\\e\\\\\"\n\n!DECRQSS on SGR 256-palette colours\nPUSH \"\\e[0;38:5:56;48:5:78m\"\nPUSH \"\\eP\\$qm\\e\\\\\"\n  output \"\\eP1\\$r38:5:56;48:5:78m\\e\\\\\"\n\n!DECRQSS on SGR RGB8 colours\nPUSH \"\\e[0;38:2:24:68:112;48:2:13:57:101m\"\nPUSH \"\\eP\\$qm\\e\\\\\"\n  output \"\\eP1\\$r38:2:24:68:112;48:2:13:57:101m\\e\\\\\"\n\n!S8C1T on DSR\nPUSH \"\\e G\"\nPUSH \"\\e[5n\"\n  output \"\\x{9b}0n\"\nPUSH \"\\e F\"\n"
  },
  {
    "path": "t/27state_reset.test",
    "content": "INIT\nWANTSTATE\n\nRESET\n\n!RIS homes cursor\nPUSH \"\\e[5;5H\"\n  ?cursor = 4,4\nWANTSTATE +m\nPUSH \"\\ec\"\n  ?cursor = 0,0\nWANTSTATE -m\n\n!RIS cancels scrolling region\nPUSH \"\\e[5;10r\"\nWANTSTATE +s\nPUSH \"\\ec\\e[25H\\n\"\n  scrollrect 0..25,0..80 => +1,+0\nWANTSTATE -s\n\n!RIS erases screen\nPUSH \"ABCDE\"\nWANTSTATE +e\nPUSH \"\\ec\"\n  erase 0..25,0..80\nWANTSTATE -e\n\n!RIS clears tabstops\nPUSH \"\\e[5G\\eH\\e[G\\t\"\n  ?cursor = 0,4\nPUSH \"\\ec\\t\"\n  ?cursor = 0,8\n"
  },
  {
    "path": "t/28state_dbl_wh.test",
    "content": "INIT\nWANTSTATE g\n\n!Single Width, Single Height\nRESET\nPUSH \"\\e#5\"\nPUSH \"Hello\"\n  putglyph 0x48 1 0,0\n  putglyph 0x65 1 0,1\n  putglyph 0x6c 1 0,2\n  putglyph 0x6c 1 0,3\n  putglyph 0x6f 1 0,4\n\n!Double Width, Single Height\nRESET\nPUSH \"\\e#6\"\nPUSH \"Hello\"\n  putglyph 0x48 1 0,0 dwl\n  putglyph 0x65 1 0,1 dwl\n  putglyph 0x6c 1 0,2 dwl\n  putglyph 0x6c 1 0,3 dwl\n  putglyph 0x6f 1 0,4 dwl\n  ?cursor = 0,5\nPUSH \"\\e[40GAB\"\n  putglyph 0x41 1 0,39 dwl\n  putglyph 0x42 1 1,0\n  ?cursor = 1,1\n\n!Double Height\nRESET\nPUSH \"\\e#3\"\nPUSH \"Hello\"\n  putglyph 0x48 1 0,0 dwl dhl-top\n  putglyph 0x65 1 0,1 dwl dhl-top\n  putglyph 0x6c 1 0,2 dwl dhl-top\n  putglyph 0x6c 1 0,3 dwl dhl-top\n  putglyph 0x6f 1 0,4 dwl dhl-top\n  ?cursor = 0,5\nPUSH \"\\r\\n\\e#4\"\nPUSH \"Hello\"\n  putglyph 0x48 1 1,0 dwl dhl-bottom\n  putglyph 0x65 1 1,1 dwl dhl-bottom\n  putglyph 0x6c 1 1,2 dwl dhl-bottom\n  putglyph 0x6c 1 1,3 dwl dhl-bottom\n  putglyph 0x6f 1 1,4 dwl dhl-bottom\n  ?cursor = 1,5\n\n!Double Width scrolling\nRESET\nPUSH \"\\e[20H\\e#6ABC\"\n  putglyph 0x41 1 19,0 dwl\n  putglyph 0x42 1 19,1 dwl\n  putglyph 0x43 1 19,2 dwl\nPUSH \"\\e[25H\\n\"\nPUSH \"\\e[19;4HDE\"\n  putglyph 0x44 1 18,3 dwl\n  putglyph 0x45 1 18,4 dwl\nPUSH \"\\e[H\\eM\"\nPUSH \"\\e[20;6HFG\"\n  putglyph 0x46 1 19,5 dwl\n  putglyph 0x47 1 19,6 dwl\n"
  },
  {
    "path": "t/29state_fallback.test",
    "content": "INIT\nWANTSTATE f\nRESET\n\n!Unrecognised control \nPUSH \"\\x03\"\n  control 03\n\n!Unrecognised CSI\nPUSH \"\\e[?15;2z\"\n  csi 0x7a L=3f 15,2\n\n!Unrecognised OSC\nPUSH \"\\e]27;Something\\e\\\\\"\n  osc [27 \"Something\"]\n\n!Unrecognised DCS\nPUSH \"\\ePz123\\e\\\\\"\n  dcs [\"z123\"]\n\n!Unrecognised APC\nPUSH \"\\e_z123\\e\\\\\"\n  apc [\"z123\"]\n\n!Unrecognised PM\nPUSH \"\\e^z123\\e\\\\\"\n  pm [\"z123\"]\n\n!Unrecognised SOS\nPUSH \"\\eXz123\\e\\\\\"\n  sos [\"z123\"]\n"
  },
  {
    "path": "t/30state_pen.test",
    "content": "INIT\nUTF8 1\nWANTSTATE\n\n!Reset\nPUSH \"\\e[m\"\n  ?pen bold = off\n  ?pen underline = 0\n  ?pen italic = off\n  ?pen blink = off\n  ?pen reverse = off\n  ?pen font = 0\n  ?pen foreground = rgb(240,240,240,is_default_fg)\n  ?pen background = rgb(0,0,0,is_default_bg)\n\n!Bold\nPUSH \"\\e[1m\"\n  ?pen bold = on\nPUSH \"\\e[22m\"\n  ?pen bold = off\nPUSH \"\\e[1m\\e[m\"\n  ?pen bold = off\n\n!Underline\nPUSH \"\\e[4m\"\n  ?pen underline = 1\nPUSH \"\\e[21m\"\n  ?pen underline = 2\nPUSH \"\\e[24m\"\n  ?pen underline = 0\nPUSH \"\\e[4m\\e[4:0m\"\n  ?pen underline = 0\nPUSH \"\\e[4:1m\"\n  ?pen underline = 1\nPUSH \"\\e[4:2m\"\n  ?pen underline = 2\nPUSH \"\\e[4:3m\"\n  ?pen underline = 3\nPUSH \"\\e[4m\\e[m\"\n  ?pen underline = 0\n\n!Italic\nPUSH \"\\e[3m\"\n  ?pen italic = on\nPUSH \"\\e[23m\"\n  ?pen italic = off\nPUSH \"\\e[3m\\e[m\"\n  ?pen italic = off\n\n!Blink\nPUSH \"\\e[5m\"\n  ?pen blink = on\nPUSH \"\\e[25m\"\n  ?pen blink = off\nPUSH \"\\e[5m\\e[m\"\n  ?pen blink = off\n\n!Reverse\nPUSH \"\\e[7m\"\n  ?pen reverse = on\nPUSH \"\\e[27m\"\n  ?pen reverse = off\nPUSH \"\\e[7m\\e[m\"\n  ?pen reverse = off\n\n!Font Selection\nPUSH \"\\e[11m\"\n  ?pen font = 1\nPUSH \"\\e[19m\"\n  ?pen font = 9\nPUSH \"\\e[10m\"\n  ?pen font = 0\nPUSH \"\\e[11m\\e[m\"\n  ?pen font = 0\n\n!Foreground\nPUSH \"\\e[31m\"\n  ?pen foreground = idx(1)\nPUSH \"\\e[32m\"\n  ?pen foreground = idx(2)\nPUSH \"\\e[34m\"\n  ?pen foreground = idx(4)\nPUSH \"\\e[91m\"\n  ?pen foreground = idx(9)\nPUSH \"\\e[38:2:10:20:30m\"\n  ?pen foreground = rgb(10,20,30)\nPUSH \"\\e[38:5:1m\"\n  ?pen foreground = idx(1)\nPUSH \"\\e[39m\"\n  ?pen foreground = rgb(240,240,240,is_default_fg)\n\n!Background\nPUSH \"\\e[41m\"\n  ?pen background = idx(1)\nPUSH \"\\e[42m\"\n  ?pen background = idx(2)\nPUSH \"\\e[44m\"\n  ?pen background = idx(4)\nPUSH \"\\e[101m\"\n  ?pen background = idx(9)\nPUSH \"\\e[48:2:10:20:30m\"\n  ?pen background = rgb(10,20,30)\nPUSH \"\\e[48:5:1m\"\n  ?pen background = idx(1)\nPUSH \"\\e[49m\"\n  ?pen background = rgb(0,0,0,is_default_bg)\n\n!Bold+ANSI colour == highbright\nPUSH \"\\e[m\\e[1;37m\"\n  ?pen bold = on\n  ?pen foreground = idx(15)\nPUSH \"\\e[m\\e[37;1m\"\n  ?pen bold = on\n  ?pen foreground = idx(15)\n\n!Super/Subscript\nPUSH \"\\e[73m\"\n  ?pen small = on\n  ?pen baseline = raise\nPUSH \"\\e[74m\"\n  ?pen small = on\n  ?pen baseline = lower\nPUSH \"\\e[75m\"\n  ?pen small = off\n  ?pen baseline = normal\n\n!DECSTR resets pen attributes\nPUSH \"\\e[1;4m\"\n  ?pen bold = on\n  ?pen underline = 1\nPUSH \"\\e[!p\"\n  ?pen bold = off\n  ?pen underline = 0\n"
  },
  {
    "path": "t/31state_rep.test",
    "content": "INIT\nUTF8 1\nWANTSTATE g\n\n!REP no argument\nRESET\nPUSH \"a\\e[b\"\n  putglyph 0x61 1 0,0\n  putglyph 0x61 1 0,1\n\n!REP zero (zero should be interpreted as one)\nRESET\nPUSH \"a\\e[0b\"\n  putglyph 0x61 1 0,0\n  putglyph 0x61 1 0,1\n\n!REP lowercase a times two\nRESET\nPUSH \"a\\e[2b\"\n  putglyph 0x61 1 0,0\n  putglyph 0x61 1 0,1\n  putglyph 0x61 1 0,2\n\n!REP with UTF-8 1 char\n# U+00E9 = 0xC3 0xA9  name: LATIN SMALL LETTER E WITH ACUTE\nRESET\nPUSH \"\\xC3\\xA9\\e[b\"\n  putglyph 0xe9 1 0,0\n  putglyph 0xe9 1 0,1\n\n!REP with UTF-8 wide char\n# U+00E9 = 0xC3 0xA9  name: LATIN SMALL LETTER E WITH ACUTE\nRESET\nPUSH \"\\xEF\\xBC\\x90\\e[b\"\n  putglyph 0xff10 2 0,0\n  putglyph 0xff10 2 0,2\n\n!REP with UTF-8 combining character\nRESET\nPUSH \"e\\xCC\\x81\\e[b\"\n  putglyph 0x65,0x301 1 0,0\n  putglyph 0x65,0x301 1 0,1\n\n!REP till end of line\nRESET\nPUSH \"a\\e[1000bb\"\n  putglyph 0x61 1 0,0\n  putglyph 0x61 1 0,1\n  putglyph 0x61 1 0,2\n  putglyph 0x61 1 0,3\n  putglyph 0x61 1 0,4\n  putglyph 0x61 1 0,5\n  putglyph 0x61 1 0,6\n  putglyph 0x61 1 0,7\n  putglyph 0x61 1 0,8\n  putglyph 0x61 1 0,9\n  putglyph 0x61 1 0,10\n  putglyph 0x61 1 0,11\n  putglyph 0x61 1 0,12\n  putglyph 0x61 1 0,13\n  putglyph 0x61 1 0,14\n  putglyph 0x61 1 0,15\n  putglyph 0x61 1 0,16\n  putglyph 0x61 1 0,17\n  putglyph 0x61 1 0,18\n  putglyph 0x61 1 0,19\n  putglyph 0x61 1 0,20\n  putglyph 0x61 1 0,21\n  putglyph 0x61 1 0,22\n  putglyph 0x61 1 0,23\n  putglyph 0x61 1 0,24\n  putglyph 0x61 1 0,25\n  putglyph 0x61 1 0,26\n  putglyph 0x61 1 0,27\n  putglyph 0x61 1 0,28\n  putglyph 0x61 1 0,29\n  putglyph 0x61 1 0,30\n  putglyph 0x61 1 0,31\n  putglyph 0x61 1 0,32\n  putglyph 0x61 1 0,33\n  putglyph 0x61 1 0,34\n  putglyph 0x61 1 0,35\n  putglyph 0x61 1 0,36\n  putglyph 0x61 1 0,37\n  putglyph 0x61 1 0,38\n  putglyph 0x61 1 0,39\n  putglyph 0x61 1 0,40\n  putglyph 0x61 1 0,41\n  putglyph 0x61 1 0,42\n  putglyph 0x61 1 0,43\n  putglyph 0x61 1 0,44\n  putglyph 0x61 1 0,45\n  putglyph 0x61 1 0,46\n  putglyph 0x61 1 0,47\n  putglyph 0x61 1 0,48\n  putglyph 0x61 1 0,49\n  putglyph 0x61 1 0,50\n  putglyph 0x61 1 0,51\n  putglyph 0x61 1 0,52\n  putglyph 0x61 1 0,53\n  putglyph 0x61 1 0,54\n  putglyph 0x61 1 0,55\n  putglyph 0x61 1 0,56\n  putglyph 0x61 1 0,57\n  putglyph 0x61 1 0,58\n  putglyph 0x61 1 0,59\n  putglyph 0x61 1 0,60\n  putglyph 0x61 1 0,61\n  putglyph 0x61 1 0,62\n  putglyph 0x61 1 0,63\n  putglyph 0x61 1 0,64\n  putglyph 0x61 1 0,65\n  putglyph 0x61 1 0,66\n  putglyph 0x61 1 0,67\n  putglyph 0x61 1 0,68\n  putglyph 0x61 1 0,69\n  putglyph 0x61 1 0,70\n  putglyph 0x61 1 0,71\n  putglyph 0x61 1 0,72\n  putglyph 0x61 1 0,73\n  putglyph 0x61 1 0,74\n  putglyph 0x61 1 0,75\n  putglyph 0x61 1 0,76\n  putglyph 0x61 1 0,77\n  putglyph 0x61 1 0,78\n  putglyph 0x61 1 0,79\n  putglyph 0x62 1 1,0\n\n"
  },
  {
    "path": "t/32state_flow.test",
    "content": "INIT\nWANTSTATE\n\n# Many of these test cases inspired by\n#   https://blueprints.launchpad.net/libvterm/+spec/reflow-cases\n\n!Spillover text marks continuation on second line\nRESET\nPUSH \"A\"x100\nPUSH \"\\r\\n\"\n  ?lineinfo 0 =\n  ?lineinfo 1 = cont\n\n!CRLF in column 80 does not mark continuation\nRESET\nPUSH \"B\"x80\nPUSH \"\\r\\n\"\nPUSH \"B\"x20\nPUSH \"\\r\\n\"\n  ?lineinfo 0 =\n  ?lineinfo 1 =\n\n!EL cancels continuation of following line\nRESET\nPUSH \"D\"x100\n  ?lineinfo 1 = cont\nPUSH \"\\eM\\e[79G\\e[K\"\n  ?lineinfo 1 =\n"
  },
  {
    "path": "t/40state_selection.test",
    "content": "INIT\nUTF8 1\nWANTSTATE\n\n!Set clipboard; final chunk len 4\nPUSH \"\\e]52;c;SGVsbG8s\\e\\\\\"\n  selection-set mask=0001 [\"Hello,\"]\n\n!Set clipboard; final chunk len 3\nPUSH \"\\e]52;c;SGVsbG8sIHc=\\e\\\\\"\n  selection-set mask=0001 [\"Hello, w\"]\n\n!Set clipboard; final chunk len 2\nPUSH \"\\e]52;c;SGVsbG8sIHdvcmxkCg==\\e\\\\\"\n  selection-set mask=0001 [\"Hello, world\\n\"]\n\n!Set clipboard; split between chunks\nPUSH \"\\e]52;c;SGVs\"\n  selection-set mask=0001 [\"Hel\"\nPUSH \"bG8s\\e\\\\\"\n  selection-set mask=0001 \"lo,\"]\n\n!Set clipboard; split within chunk\nPUSH \"\\e]52;c;SGVsbG\"\n  selection-set mask=0001 [\"Hel\"\nPUSH \"8s\\e\\\\\"\n  selection-set mask=0001 \"lo,\"]\n\n!Set clipboard; empty first chunk\nPUSH \"\\e]52;c;\"\nPUSH \"SGVsbG8s\\e\\\\\"\n  selection-set mask=0001 [\"Hello,\"]\n\n!Set clipboard; empty final chunk\nPUSH \"\\e]52;c;SGVsbG8s\"\n  selection-set mask=0001 [\"Hello,\"\nPUSH \"\\e\\\\\"\n  selection-set mask=0001 ]\n\n!Set clipboard; longer than buffer\nPUSH \"\\e]52;c;\" . \"LS0t\"x10 . \"\\e\\\\\"\n  selection-set mask=0001 [\"-\"x15\n  selection-set mask=0001 \"-\"x15]\n\n!Clear clipboard\nPUSH \"\\e]52;c;\\e\\\\\"\n  selection-set mask=0001 []\n\n!Set invalid data clears and ignores\nPUSH \"\\e]52;c;SGVs*SGVsbG8s\\e\\\\\"\n  selection-set mask=0001 []\n\n!Query clipboard\nPUSH \"\\e]52;c;?\\e\\\\\"\n  selection-query mask=0001\n\n!Send clipboard; final chunk len 4\nSELECTION 1 [\"Hello,\"]\n  output \"\\e]52;c;\"\n  output \"SGVsbG8s\"\n  output \"\\e\\\\\"\n\n!Send clipboard; final chunk len 3\nSELECTION 1 [\"Hello, w\"]\n  output \"\\e]52;c;\"\n  output \"SGVsbG8s\"\n  output \"IHc=\\e\\\\\"\n\n!Send clipboard; final chunk len 2\nSELECTION 1 [\"Hello, world\\n\"]\n  output \"\\e]52;c;\"\n  output \"SGVsbG8sIHdvcmxk\"\n  output \"Cg==\\e\\\\\"\n\n!Send clipboard; split between chunks\nSELECTION 1 [\"Hel\"\n  output \"\\e]52;c;\"\n  output \"SGVs\"\nSELECTION 1  \"lo,\"]\n  output \"bG8s\"\n  output \"\\e\\\\\"\n\n!Send clipboard; split within chunk\nSELECTION 1 [\"Hello\"\n  output \"\\e]52;c;\"\n  output \"SGVs\"\nSELECTION 1 \",\"]\n  output \"bG8s\"\n  output \"\\e\\\\\"\n"
  },
  {
    "path": "t/60screen_ascii.test",
    "content": "INIT\nWANTSCREEN ac\n\n!Get\nRESET\nPUSH \"ABC\"\n  movecursor 0,3\n  ?screen_chars 0,0,1,3 = \"ABC\"\n  ?screen_chars 0,0,1,80 = \"ABC\"\n  ?screen_text 0,0,1,3 = 0x41,0x42,0x43\n  ?screen_text 0,0,1,80 = 0x41,0x42,0x43\n  ?screen_cell 0,0 = {0x41} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)\n  ?screen_cell 0,1 = {0x42} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)\n  ?screen_cell 0,2 = {0x43} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)\n  ?screen_row 0 = \"ABC\"\n  ?screen_eol 0,0 = 0\n  ?screen_eol 0,2 = 0\n  ?screen_eol 0,3 = 1\nPUSH \"\\e[H\"\n  movecursor 0,0\n  ?screen_row 0 = \"ABC\"\n  ?screen_text 0,0,1,80 = 0x41,0x42,0x43\nPUSH \"E\"\n  movecursor 0,1\n  ?screen_row 0 = \"EBC\"\n  ?screen_text 0,0,1,80 = 0x45,0x42,0x43\n\nWANTSCREEN -c\n\n!Erase\nRESET\nPUSH \"ABCDE\\e[H\\e[K\"\n  ?screen_row 0 = \"\"\n  ?screen_text 0,0,1,80 = \n\n!Copycell\nRESET\nPUSH \"ABC\\e[H\\e[@\"\nPUSH \"1\"\n  ?screen_row 0 = \"1ABC\"\n\nRESET\nPUSH \"ABC\\e[H\\e[P\"\n  ?screen_chars 0,0,1,1 = \"B\"\n  ?screen_chars 0,1,1,2 = \"C\"\n  ?screen_chars 0,0,1,80 = \"BC\"\n\n!Space padding\nRESET\nPUSH \"Hello\\e[CWorld\"\n  ?screen_row 0 = \"Hello World\"\n  ?screen_text 0,0,1,80 = 0x48,0x65,0x6c,0x6c,0x6f,0x20,0x57,0x6f,0x72,0x6c,0x64\n\n!Linefeed padding\nRESET\nPUSH \"Hello\\r\\nWorld\"\n  ?screen_chars 0,0,2,80 = \"Hello\\nWorld\"\n  ?screen_text 0,0,2,80 = 0x48,0x65,0x6c,0x6c,0x6f,0x0a,0x57,0x6f,0x72,0x6c,0x64\n\n!Altscreen\nRESET\nPUSH \"P\"\n  ?screen_row 0 = \"P\"\nPUSH \"\\e[?1049h\"\n  ?screen_row 0 = \"\"\nPUSH \"\\e[2K\\e[HA\"\n  ?screen_row 0 = \"A\"\nPUSH \"\\e[?1049l\"\n  ?screen_row 0 = \"P\"\n"
  },
  {
    "path": "t/61screen_unicode.test",
    "content": "INIT\nUTF8 1\nWANTSCREEN\n\n!Single width UTF-8\n# U+00C1 = 0xC3 0x81  name: LATIN CAPITAL LETTER A WITH ACUTE\n# U+00E9 = 0xC3 0xA9  name: LATIN SMALL LETTER E WITH ACUTE\nRESET\nPUSH \"\\xC3\\x81\\xC3\\xA9\"\n  ?screen_row 0 = 0xc1,0xe9\n  ?screen_text 0,0,1,80 = 0xc3,0x81,0xc3,0xa9\n  ?screen_cell 0,0 = {0xc1} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)\n\n!Wide char\n# U+FF10 = 0xEF 0xBC 0x90  name: FULLWIDTH DIGIT ZERO\nRESET\nPUSH \"0123\\e[H\"\nPUSH \"\\xEF\\xBC\\x90\"\n  ?screen_row 0 = 0xff10,0x32,0x33\n  ?screen_text 0,0,1,80 = 0xef,0xbc,0x90,0x32,0x33\n  ?screen_cell 0,0 = {0xff10} width=2 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)\n\n!Combining char\n# U+0301 = 0xCC 0x81  name: COMBINING ACUTE\nRESET\nPUSH \"0123\\e[H\"\nPUSH \"e\\xCC\\x81\"\n  ?screen_row 0 = 0x65,0x301,0x31,0x32,0x33\n  ?screen_text 0,0,1,80 = 0x65,0xcc,0x81,0x31,0x32,0x33\n  ?screen_cell 0,0 = {0x65,0x301} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)\n\n!10 combining accents should not crash\nRESET\nPUSH \"e\\xCC\\x81\\xCC\\x82\\xCC\\x83\\xCC\\x84\\xCC\\x85\\xCC\\x86\\xCC\\x87\\xCC\\x88\\xCC\\x89\\xCC\\x8A\"\n  ?screen_cell 0,0 = {0x65,0x301,0x302,0x303,0x304,0x305} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)\n\n!40 combining accents in two split writes of 20 should not crash\nRESET\nPUSH \"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\"\nPUSH  \"\\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\"\n  ?screen_cell 0,0 = {0x65,0x301,0x301,0x301,0x301,0x301} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)\n\n!Outputing CJK doublewidth in 80th column should wraparound to next line and not crash\"\nRESET\nPUSH \"\\e[80G\\xEF\\xBC\\x90\"\n  ?screen_cell 0,79 = {} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)\n  ?screen_cell 1,0 = {0xff10} width=2 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)\n"
  },
  {
    "path": "t/62screen_damage.test",
    "content": "INIT\nWANTSCREEN aDb\n\n!Putglyph\nRESET\n  damage 0..25,0..80\nPUSH \"123\"\n  damage 0..1,0..1 = 0<31>\n  damage 0..1,1..2 = 0<32>\n  damage 0..1,2..3 = 0<33>\n\n!Erase\nPUSH \"\\e[H\"\nPUSH \"\\e[3X\"\n  damage 0..1,0..3\n\n!Scroll damages entire line in two chunks\nPUSH \"\\e[H\\e[5@\"\n  damage 0..1,5..80\n  damage 0..1,0..5\n\n!Scroll down damages entire screen in two chunks\nPUSH \"\\e[T\"\n  damage 1..25,0..80\n  damage 0..1,0..80\n\n!Altscreen damages entire area\nPUSH \"\\e[?1049h\"\n  damage 0..25,0..80\nPUSH \"\\e[?1049l\"\n  damage 0..25,0..80\n\nWANTSCREEN m\n\n!Scroll invokes moverect but not damage\nPUSH \"\\e[5@\"\n  moverect 0..1,0..75 -> 0..1,5..80\n  damage 0..1,0..5\n\nWANTSCREEN -m\n\n!Merge to cells\nRESET\n  damage 0..25,0..80\nDAMAGEMERGE CELL\n\nPUSH \"A\"\n  damage 0..1,0..1 = 0<41>\nPUSH \"B\"\n  damage 0..1,1..2 = 0<42>\nPUSH \"C\"\n  damage 0..1,2..3 = 0<43>\n\n!Merge entire rows\nRESET\n  damage 0..25,0..80\nDAMAGEMERGE ROW\n\nPUSH \"ABCDE\\r\\nEFGH\"\n  damage 0..1,0..5 = 0<41 42 43 44 45>\nDAMAGEFLUSH\n  damage 1..2,0..4 = 1<45 46 47 48>\nPUSH \"\\e[3;6r\\e[6H\\eD\"\n  damage 2..5,0..80\nDAMAGEFLUSH\n  damage 5..6,0..80\n\n!Merge entire screen\nRESET\n  damage 0..25,0..80\nDAMAGEMERGE SCREEN\n\nPUSH \"ABCDE\\r\\nEFGH\"\nDAMAGEFLUSH\n  damage 0..2,0..5 = 0<41 42 43 44 45> 1<45 46 47 48>\nPUSH \"\\e[3;6r\\e[6H\\eD\"\nDAMAGEFLUSH\n  damage 2..6,0..80\n\n!Merge entire screen with moverect\nWANTSCREEN m\n\nRESET\n  damage 0..25,0..80\nDAMAGEMERGE SCREEN\n\nPUSH \"ABCDE\\r\\nEFGH\"\nPUSH \"\\e[3;6r\\e[6H\\eD\"\n  damage 0..2,0..5 = 0<41 42 43 44 45> 1<45 46 47 48>\n  moverect 3..6,0..80 -> 2..5,0..80\nDAMAGEFLUSH\n  damage 5..6,0..80\n\n!Merge scroll\nRESET\n  damage 0..25,0..80\nDAMAGEMERGE SCROLL\n\nPUSH \"\\e[H1\\r\\n2\\r\\n3\"\nPUSH \"\\e[25H\\n\\n\\n\"\n  sb_pushline 80 = 31\n  sb_pushline 80 = 32\n  sb_pushline 80 = 33\nDAMAGEFLUSH\n  moverect 3..25,0..80 -> 0..22,0..80\n  damage 0..25,0..80\n\n!Merge scroll with damage\nPUSH \"\\e[25H\"\nPUSH \"ABCDE\\r\\nEFGH\\r\\n\"\n  sb_pushline 80 =\n  sb_pushline 80 =\nDAMAGEFLUSH\n  moverect 2..25,0..80 -> 0..23,0..80\n  damage 22..25,0..80 = 22<41 42 43 44 45> 23<45 46 47 48>\n\n!Merge scroll with damage past region\nPUSH \"\\e[3;6r\\e[6H1\\r\\n2\\r\\n3\\r\\n4\\r\\n5\"\nDAMAGEFLUSH\n  damage 2..6,0..80 = 2<32> 3<33> 4<34> 5<35>\n\n!Damage entirely outside scroll region\nPUSH \"\\e[HABC\\e[3;6r\\e[6H\\r\\n6\"\n  damage 0..1,0..3 = 0<41 42 43>\nDAMAGEFLUSH\n  moverect 3..6,0..80 -> 2..5,0..80\n  damage 5..6,0..80 = 5<36>\n\n!Damage overlapping scroll region\nPUSH \"\\e[H\\e[2J\"\nDAMAGEFLUSH\n  damage 0..25,0..80\n\nPUSH \"\\e[HABCD\\r\\nEFGH\\r\\nIJKL\\e[2;5r\\e[5H\\r\\nMNOP\"\nDAMAGEFLUSH\n  moverect 2..5,0..80 -> 1..4,0..80\n  damage 0..5,0..80 = 0<41 42 43 44> 1<49 4A 4B 4C>\n  ## TODO: is this right?\n\n!Merge scroll*2 with damage\nRESET\n  damage 0..25,0..80\nDAMAGEMERGE SCROLL\n\nPUSH \"\\e[25H\\r\\nABCDE\\b\\b\\b\\e[2P\\r\\n\"\n  sb_pushline 80 =\n  moverect 1..25,0..80 -> 0..24,0..80\n  damage 24..25,0..80 = 24<41 42 43 44 45>\n  sb_pushline 80 =\n  moverect 24..25,4..80 -> 24..25,2..78\n  damage 24..25,78..80\nDAMAGEFLUSH\n  moverect 1..25,0..80 -> 0..24,0..80\n  damage 24..25,0..80\n  ?screen_row 23 = \"ABE\"\n"
  },
  {
    "path": "t/63screen_resize.test",
    "content": "INIT\nWANTSTATE\nWANTSCREEN\n\n!Resize wider preserves cells\nRESET\nRESIZE 25,80\nPUSH \"AB\\r\\nCD\"\n  ?screen_chars 0,0,1,80 = \"AB\"\n  ?screen_chars 1,0,2,80 = \"CD\"\nRESIZE 25,100\n  ?screen_chars 0,0,1,100 = \"AB\"\n  ?screen_chars 1,0,2,100 = \"CD\"\n\n!Resize wider allows print in new area\nRESET\nRESIZE 25,80\nPUSH \"AB\\e[79GCD\"\n  ?screen_chars 0,0,1,2 = \"AB\"\n  ?screen_chars 0,78,1,80 = \"CD\"\nRESIZE 25,100\n  ?screen_chars 0,0,1,2 = \"AB\"\n  ?screen_chars 0,78,1,80 = \"CD\"\nPUSH \"E\"\n  ?screen_chars 0,78,1,81 = \"CDE\"\n\n!Resize shorter with blanks just truncates\nRESET\nRESIZE 25,80\nPUSH \"Top\\e[10HLine 10\"\n  ?screen_row 0 = \"Top\"\n  ?screen_row 9 = \"Line 10\"\n  ?cursor = 9,7\nRESIZE 20,80\n  ?screen_row 0 = \"Top\"\n  ?screen_row 9 = \"Line 10\"\n  ?cursor = 9,7\n\n!Resize shorter with content must scroll\nRESET\nRESIZE 25,80\nPUSH \"Top\\e[25HLine 25\\e[15H\"\n  ?screen_row 0 = \"Top\"\n  ?screen_row 24 = \"Line 25\"\n  ?cursor = 14,0\nWANTSCREEN b\nRESIZE 20,80\n  sb_pushline 80 = 54 6F 70\n  sb_pushline 80 =\n  sb_pushline 80 =\n  sb_pushline 80 =\n  sb_pushline 80 =\n  ?screen_row 0  = \"\"\n  ?screen_row 19 = \"Line 25\"\n  ?cursor = 9,0\n\n!Resize shorter does not lose line with cursor\n# See also https://github.com/neovim/libvterm/commit/1b745d29d45623aa8d22a7b9288c7b0e331c7088\nRESET\nWANTSCREEN -b\nRESIZE 25,80\nWANTSCREEN b\nPUSH \"\\e[24HLine 24\\r\\nLine 25\\r\\n\"\n  sb_pushline 80 =\n  ?screen_row 23 = \"Line 25\"\n  ?cursor = 24,0\nRESIZE 24,80\n  sb_pushline 80 =\n  ?screen_row 22 = \"Line 25\"\n  ?cursor = 23,0\n\n!Resize shorter does not send the cursor to a negative row\n# See also https://github.com/vim/vim/pull/6141\nRESET\nWANTSCREEN -b\nRESIZE 25,80\nWANTSCREEN b\nPUSH \"\\e[24HLine 24\\r\\nLine 25\\e[H\"\n  ?cursor = 0,0\nRESIZE 20,80\n  sb_pushline 80 =\n  sb_pushline 80 =\n  sb_pushline 80 =\n  sb_pushline 80 =\n  sb_pushline 80 =\n  ?cursor = 0,0\n\n!Resize taller attempts to pop scrollback\nRESET\nWANTSCREEN -b\nRESIZE 25,80\nPUSH \"Line 1\\e[25HBottom\\e[15H\"\n  ?screen_row 0  = \"Line 1\"\n  ?screen_row 24 = \"Bottom\"\n  ?cursor = 14,0\nWANTSCREEN b\nRESIZE 30,80\n  sb_popline 80\n  sb_popline 80\n  sb_popline 80\n  sb_popline 80\n  sb_popline 80\n  ?screen_row 0  = \"ABCDE\"\n  ?screen_row 5  = \"Line 1\"\n  ?screen_row 29 = \"Bottom\"\n  ?cursor = 19,0\nWANTSCREEN -b\n\n!Resize can operate on altscreen\nRESET\nWANTSCREEN a\nRESIZE 25,80\nPUSH \"Main screen\\e[?1049h\\e[HAlt screen\"\nRESIZE 30,80\n  ?screen_row 0 = \"Alt screen\"\nPUSH \"\\e[?1049l\"\n  ?screen_row 0 = \"Main screen\"\n"
  },
  {
    "path": "t/64screen_pen.test",
    "content": "INIT\nWANTSCREEN\n\nRESET\n\n!Plain\nPUSH \"A\"\n  ?screen_cell 0,0 = {0x41} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)\n\n!Bold\nPUSH \"\\e[1mB\"\n  ?screen_cell 0,1 = {0x42} width=1 attrs={B} fg=rgb(240,240,240) bg=rgb(0,0,0)\n\n!Italic\nPUSH \"\\e[3mC\"\n  ?screen_cell 0,2 = {0x43} width=1 attrs={BI} fg=rgb(240,240,240) bg=rgb(0,0,0)\n\n!Underline\nPUSH \"\\e[4mD\"\n  ?screen_cell 0,3 = {0x44} width=1 attrs={BU1I} fg=rgb(240,240,240) bg=rgb(0,0,0)\n\n!Reset\nPUSH \"\\e[mE\"\n  ?screen_cell 0,4 = {0x45} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)\n\n!Font\nPUSH \"\\e[11mF\\e[m\"\n  ?screen_cell 0,5 = {0x46} width=1 attrs={F1} fg=rgb(240,240,240) bg=rgb(0,0,0)\n\n!Foreground\nPUSH \"\\e[31mG\\e[m\"\n  ?screen_cell 0,6 = {0x47} width=1 attrs={} fg=rgb(224,0,0) bg=rgb(0,0,0)\n\n!Background\nPUSH \"\\e[42mH\\e[m\"\n  ?screen_cell 0,7 = {0x48} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,224,0)\n\n!Super/subscript\nPUSH \"x\\e[74m0\\e[73m2\\e[m\"\n  ?screen_cell 0,8  = {0x78} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)\n  ?screen_cell 0,9  = {0x30} width=1 attrs={S_} fg=rgb(240,240,240) bg=rgb(0,0,0)\n  ?screen_cell 0,10 = {0x32} width=1 attrs={S^} fg=rgb(240,240,240) bg=rgb(0,0,0)\n\n!EL sets only colours to end of line, not other attrs\nPUSH \"\\e[H\\e[7;33;44m\\e[K\"\n  ?screen_cell 0,0  = {} width=1 attrs={} fg=rgb(224,224,0) bg=rgb(0,0,224)\n  ?screen_cell 0,79 = {} width=1 attrs={} fg=rgb(224,224,0) bg=rgb(0,0,224)\n\n!DECSCNM xors reverse for entire screen\nPUSH \"R\\e[?5h\"\n  ?screen_cell 0,0  = {0x52} width=1 attrs={} fg=rgb(224,224,0) bg=rgb(0,0,224)\n  ?screen_cell 1,0  = {} width=1 attrs={R} fg=rgb(240,240,240) bg=rgb(0,0,0)\nPUSH \"\\e[?5\\$p\"\n  output \"\\e[?5;1\\$y\"\nPUSH \"\\e[?5l\"\n  ?screen_cell 0,0  = {0x52} width=1 attrs={R} fg=rgb(224,224,0) bg=rgb(0,0,224)\n  ?screen_cell 1,0  = {} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)\nPUSH \"\\e[?5\\$p\"\n  output \"\\e[?5;2\\$y\"\n\n!Set default colours\nRESET\nPUSH \"ABC\\e[31mDEF\\e[m\"\n  ?screen_cell 0,0  = {0x41} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)\n  ?screen_cell 0,3  = {0x44} width=1 attrs={} fg=rgb(224,0,0) bg=rgb(0,0,0)\nSETDEFAULTCOL rgb(252,253,254)\n  ?screen_cell 0,0  = {0x41} width=1 attrs={} fg=rgb(252,253,254) bg=rgb(0,0,0)\n  ?screen_cell 0,3  = {0x44} width=1 attrs={} fg=rgb(224,0,0) bg=rgb(0,0,0)\nSETDEFAULTCOL rgb(250,250,250) rgb(10,20,30)\n  ?screen_cell 0,0  = {0x41} width=1 attrs={} fg=rgb(250,250,250) bg=rgb(10,20,30)\n  ?screen_cell 0,3  = {0x44} width=1 attrs={} fg=rgb(224,0,0) bg=rgb(10,20,30)\n"
  },
  {
    "path": "t/65screen_protect.test",
    "content": "INIT\nWANTSCREEN \n\n!Selective erase\nRESET\nPUSH \"A\\e[1\\\"qB\\e[\\\"qC\"\n  ?screen_row 0 = \"ABC\"\nPUSH \"\\e[G\\e[?J\"\n  ?screen_row 0 = \" B\"\n\n!Non-selective erase\nRESET\nPUSH \"A\\e[1\\\"qB\\e[\\\"qC\"\n  ?screen_row 0 = \"ABC\"\nPUSH \"\\e[G\\e[J\"\n  ?screen_row 0 = \"\"\n"
  },
  {
    "path": "t/66screen_extent.test",
    "content": "INIT\nWANTSCREEN \n\n!Bold extent\nRESET\nPUSH \"AB\\e[1mCD\\e[mE\"\n  ?screen_attrs_extent 0,0 = 0,0-1,1\n  ?screen_attrs_extent 0,1 = 0,0-1,1\n  ?screen_attrs_extent 0,2 = 0,2-1,3\n  ?screen_attrs_extent 0,3 = 0,2-1,3\n  ?screen_attrs_extent 0,4 = 0,4-1,79\n"
  },
  {
    "path": "t/67screen_dbl_wh.test",
    "content": "INIT\nWANTSCREEN\n\nRESET\n\n!Single Width, Single Height\nRESET\nPUSH \"\\e#5\"\nPUSH \"abcde\"\n  ?screen_cell 0,0 = {0x61} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)\n\n!Double Width, Single Height\nRESET\nPUSH \"\\e#6\"\nPUSH \"abcde\"\n  ?screen_cell 0,0 = {0x61} width=1 attrs={} dwl fg=rgb(240,240,240) bg=rgb(0,0,0)\n\n!Double Height\nRESET\nPUSH \"\\e#3\"\nPUSH \"abcde\"\nPUSH \"\\r\\n\\e#4\"\nPUSH \"abcde\"\n  ?screen_cell 0,0 = {0x61} width=1 attrs={} dwl dhl-top fg=rgb(240,240,240) bg=rgb(0,0,0)\n  ?screen_cell 1,0 = {0x61} width=1 attrs={} dwl dhl-bottom fg=rgb(240,240,240) bg=rgb(0,0,0)\n\n!Late change\nRESET\nPUSH \"abcde\"\n  ?screen_cell 0,0 = {0x61} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)\nPUSH \"\\e#6\"\n  ?screen_cell 0,0 = {0x61} width=1 attrs={} dwl fg=rgb(240,240,240) bg=rgb(0,0,0)\n\n!DWL doesn't spill over on scroll\nRESET\nPUSH \"\\e[25H\\e#6Final\\r\\n\"\n  ?screen_cell 23,0 = {0x46} width=1 attrs={} dwl fg=rgb(240,240,240) bg=rgb(0,0,0)\n  ?screen_cell 24,0 = {} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)\n"
  },
  {
    "path": "t/68screen_termprops.test",
    "content": "INIT\nWANTSCREEN p\n\nRESET\n  settermprop 1 true\n  settermprop 2 true\n  settermprop 7 1\n\n!Cursor visibility\nPUSH \"\\e[?25h\"\n  settermprop 1 true\nPUSH \"\\e[?25l\"\n  settermprop 1 false\n\n!Title\nPUSH \"\\e]2;Here is my title\\a\"\n  settermprop 4 [\"Here is my title\"]\n"
  },
  {
    "path": "t/69screen_pushline.test",
    "content": "INIT\nWANTSTATE\nWANTSCREEN b\n\nRESET\n\n!Spillover text marks continuation on second line\nPUSH \"A\"x85\nPUSH \"\\r\\n\"\n  ?lineinfo 0 =\n  ?lineinfo 1 = cont\n\n!Continuation mark sent to sb_pushline\nPUSH \"\\n\"x23\n  sb_pushline 80 = 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\nPUSH \"\\n\"\n  sb_pushline 80 cont = 41 41 41 41 41\n"
  },
  {
    "path": "t/69screen_reflow.test",
    "content": "INIT\n# Run these tests on a much smaller default screen, so debug output is\n# nowhere near as noisy\nRESIZE 5,10\nWANTSTATE\nWANTSCREEN r\nRESET\n\n!Resize wider reflows wide lines\nRESET\nPUSH \"A\"x12\n  ?screen_row 0 = \"AAAAAAAAAA\"\n  ?screen_row 1 = \"AA\"\n  ?lineinfo 1 = cont\n  ?cursor = 1,2\nRESIZE 5,15\n  ?screen_row 0 = \"AAAAAAAAAAAA\"\n  ?screen_row 1 = \n  ?lineinfo 1 =\n  ?cursor = 0,12\nRESIZE 5,20\n  ?screen_row 0 = \"AAAAAAAAAAAA\"\n  ?screen_row 1 = \n  ?lineinfo 1 =\n  ?cursor = 0,12\n\n!Resize narrower can create continuation lines\nRESET\nRESIZE 5,10\nPUSH \"ABCDEFGHI\"\n  ?screen_row 0 = \"ABCDEFGHI\"\n  ?screen_row 1 = \"\"\n  ?lineinfo 1 =\n  ?cursor = 0,9\nRESIZE 5,8\n  ?screen_row 0 = \"ABCDEFGH\"\n  ?screen_row 1 = \"I\"\n  ?lineinfo 1 = cont\n  ?cursor = 1,1\nRESIZE 5,6\n  ?screen_row 0 = \"ABCDEF\"\n  ?screen_row 1 = \"GHI\"\n  ?lineinfo 1 = cont\n  ?cursor = 1,3\n\n!Shell wrapped prompt behaviour\nRESET\nRESIZE 5,10\nPUSH \"PROMPT GOES HERE\\r\\n> \\r\\n\\r\\nPROMPT GOES HERE\\r\\n> \"\n  ?screen_row 0 = \"> \"\n  ?screen_row 1 = \"\"\n  ?screen_row 2 = \"PROMPT GOE\"\n  ?screen_row 3 = \"S HERE\"\n  ?lineinfo 3 = cont\n  ?screen_row 4 = \"> \"\n  ?cursor = 4,2\nRESIZE 5,11\n  ?screen_row 0 = \"> \"\n  ?screen_row 1 = \"\"\n  ?screen_row 2 = \"PROMPT GOES\"\n  ?screen_row 3 = \" HERE\"\n  ?lineinfo 3 = cont\n  ?screen_row 4 = \"> \"\n  ?cursor = 4,2\nRESIZE 5,12\n  ?screen_row 0 = \"> \"\n  ?screen_row 1 = \"\"\n  ?screen_row 2 = \"PROMPT GOES \"\n  ?screen_row 3 = \"HERE\"\n  ?lineinfo 3 = cont\n  ?screen_row 4 = \"> \"\n  ?cursor = 4,2\nRESIZE 5,16\n  ?screen_row 0 = \"> \"\n  ?screen_row 1 = \"\"\n  ?screen_row 2 = \"PROMPT GOES HERE\"\n  ?lineinfo 3 =\n  ?screen_row 3 = \"> \"\n  ?cursor = 3,2\n\n!Cursor goes missing\n# For more context: https://github.com/neovim/neovim/pull/21124\nRESET\nRESIZE 5,5\nRESIZE 3,1\nPUSH \"\\x1b[2;1Habc\\r\\n\\x1b[H\"\nRESIZE 1,1\n  ?cursor = 0,0\n"
  },
  {
    "path": "t/90vttest_01-movement-1.test",
    "content": "INIT\nWANTSTATE\nWANTSCREEN\n\nRESET\n\nPUSH \"\\e#8\"\n\nPUSH \"\\e[9;10H\\e[1J\"\nPUSH \"\\e[18;60H\\e[0J\\e[1K\"\nPUSH \"\\e[9;71H\\e[0K\"\n\n$SEQ 10 16: PUSH \"\\e[\\#;10H\\e[1K\\e[\\#;71H\\e[0K\"\n\nPUSH \"\\e[17;30H\\e[2K\"\n\n$SEQ 1 80: PUSH \"\\e[24;\\#f*\\e[1;\\#f*\"\n\nPUSH \"\\e[2;2H\"\n\n$REP 22: PUSH \"+\\e[1D\\eD\"\n\nPUSH \"\\e[23;79H\"\n$REP 22: PUSH \"+\\e[1D\\eM\"\n\nPUSH \"\\e[2;1H\"\n$SEQ 2 23: PUSH \"*\\e[\\#;80H*\\e[10D\\eE\"\n\nPUSH \"\\e[2;10H\\e[42D\\e[2C\"\n$REP 76: PUSH \"+\\e[0C\\e[2D\\e[1C\"\n\nPUSH \"\\e[23;70H\\e[42C\\e[2D\"\n\n$REP 76: PUSH \"+\\e[1D\\e[1C\\e[0D\\b\"\n\nPUSH \"\\e[1;1H\"\nPUSH \"\\e[10A\"\nPUSH \"\\e[1A\"\nPUSH \"\\e[0A\"\nPUSH \"\\e[24;80H\"\nPUSH \"\\e[10B\"\nPUSH \"\\e[1B\"\nPUSH \"\\e[0B\"\nPUSH \"\\e[10;12H\"\n\n$REP 58: PUSH \" \"\nPUSH \"\\e[1B\\e[58D\"\n\n$REP 58: PUSH \" \"\nPUSH \"\\e[1B\\e[58D\"\n\n$REP 58: PUSH \" \"\nPUSH \"\\e[1B\\e[58D\"\n\n$REP 58: PUSH \" \"\nPUSH \"\\e[1B\\e[58D\"\n\n$REP 58: PUSH \" \"\nPUSH \"\\e[1B\\e[58D\"\n\n$REP 58: PUSH \" \"\nPUSH \"\\e[1B\\e[58D\"\n\nPUSH \"\\e[5A\\e[1CThe screen should be cleared,  and have an unbroken bor-\"\nPUSH \"\\e[12;13Hder of *'s and +'s around the edge,   and exactly in the\"\nPUSH \"\\e[13;13Hmiddle  there should be a frame of E's around this  text\"\nPUSH \"\\e[14;13Hwith  one (1) free position around it.    Push <RETURN>\"\n\n# And the result is...\n\n!Output\n            ?screen_row  0 = \"********************************************************************************\"\n            ?screen_row  1 = \"*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*\"\n$SEQ  2  7: ?screen_row \\# = \"*+                                                                            +*\"\n            ?screen_row  8 = \"*+        EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE        +*\"\n            ?screen_row  9 = \"*+        E                                                          E        +*\"\n            ?screen_row 10 = \"*+        E The screen should be cleared,  and have an unbroken bor- E        +*\"\n            ?screen_row 11 = \"*+        E der of *'s and +'s around the edge,   and exactly in the E        +*\"\n            ?screen_row 12 = \"*+        E middle  there should be a frame of E's around this  text E        +*\"\n            ?screen_row 13 = \"*+        E with  one (1) free position around it.    Push <RETURN>  E        +*\"\n            ?screen_row 14 = \"*+        E                                                          E        +*\"\n            ?screen_row 15 = \"*+        EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE        +*\"\n$SEQ 16 21: ?screen_row \\# = \"*+                                                                            +*\"\n            ?screen_row 22 = \"*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*\"\n            ?screen_row 23 = \"********************************************************************************\"\n\n?cursor = 13,67\n"
  },
  {
    "path": "t/90vttest_01-movement-2.test",
    "content": "INIT\nWANTSTATE\nWANTSCREEN\n\nRESET\n\nPUSH \"\\e[3;21r\"\nPUSH \"\\e[?6h\"\n\nPUSH \"\\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\"\nPUSH \"\\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\"\nPUSH \"\\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\"\nPUSH \"\\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\"\nPUSH \"\\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\"\nPUSH \"\\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\"\nPUSH \"\\e[19;1HY\\e[19;80Hy\\x0a\\e[18;80HyZ\\e[19;80HZ\\b z\\x0a\"\n\n!Output\n\n?screen_row  2 = \"I                                                                              i\"\n?screen_row  3 = \"J                                                                              j\"\n?screen_row  4 = \"K                                                                              k\"\n?screen_row  5 = \"L                                                                              l\"\n?screen_row  6 = \"M                                                                              m\"\n?screen_row  7 = \"N                                                                              n\"\n?screen_row  8 = \"O                                                                              o\"\n?screen_row  9 = \"P                                                                              p\"\n?screen_row 10 = \"Q                                                                              q\"\n?screen_row 11 = \"R                                                                              r\"\n?screen_row 12 = \"S                                                                              s\"\n?screen_row 13 = \"T                                                                              t\"\n?screen_row 14 = \"U                                                                              u\"\n?screen_row 15 = \"V                                                                              v\"\n?screen_row 16 = \"W                                                                              w\"\n?screen_row 17 = \"X                                                                              x\"\n?screen_row 18 = \"Y                                                                              y\"\n?screen_row 19 = \"Z                                                                              z\"\n?screen_row 20 = \"\"\n\n?cursor = 20,79\n"
  },
  {
    "path": "t/90vttest_01-movement-3.test",
    "content": "# Test of cursor-control characters inside ESC sequences\nINIT\nWANTSTATE\nWANTSCREEN\n\nRESET\n\nPUSH \"A B C D E F G H I\"\nPUSH \"\\x0d\\x0a\"\nPUSH \"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\"\nPUSH \"\\x0d\\x0a\"\nPUSH \"A \\e[\\x0d2CB\\e[\\x0d4CC\\e[\\x0d6CD\\e[\\x0d8CE\\e[\\x0d10CF\\e[\\x0d12CG\\e[\\x0d14CH\\e[\\x0d16CI\"\nPUSH \"\\x0d\\x0a\"\nPUSH \"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\"\n\n!Output\n\n$SEQ 0 2: ?screen_row \\# = \"A B C D E F G H I\"\n          ?screen_row  3 = \"A B C D E F G H I \"\n\n?cursor = 3,18\n"
  },
  {
    "path": "t/90vttest_01-movement-4.test",
    "content": "# Test of leading zeroes in ESC sequences\nINIT\nWANTSCREEN\n\nRESET\n\nPUSH \"\\e[00000000004;000000001HT\"\nPUSH \"\\e[00000000004;000000002Hh\"\nPUSH \"\\e[00000000004;000000003Hi\"\nPUSH \"\\e[00000000004;000000004Hs\"\nPUSH \"\\e[00000000004;000000005H \"\nPUSH \"\\e[00000000004;000000006Hi\"\nPUSH \"\\e[00000000004;000000007Hs\"\nPUSH \"\\e[00000000004;000000008H \"\nPUSH \"\\e[00000000004;000000009Ha\"\nPUSH \"\\e[00000000004;0000000010H \"\nPUSH \"\\e[00000000004;0000000011Hc\"\nPUSH \"\\e[00000000004;0000000012Ho\"\nPUSH \"\\e[00000000004;0000000013Hr\"\nPUSH \"\\e[00000000004;0000000014Hr\"\nPUSH \"\\e[00000000004;0000000015He\"\nPUSH \"\\e[00000000004;0000000016Hc\"\nPUSH \"\\e[00000000004;0000000017Ht\"\nPUSH \"\\e[00000000004;0000000018H \"\nPUSH \"\\e[00000000004;0000000019Hs\"\nPUSH \"\\e[00000000004;0000000020He\"\nPUSH \"\\e[00000000004;0000000021Hn\"\nPUSH \"\\e[00000000004;0000000022Ht\"\nPUSH \"\\e[00000000004;0000000023He\"\nPUSH \"\\e[00000000004;0000000024Hn\"\nPUSH \"\\e[00000000004;0000000025Hc\"\nPUSH \"\\e[00000000004;0000000026He\"\n\n!Output\n\n?screen_row 3 = \"This is a correct sentence\"\n"
  },
  {
    "path": "t/90vttest_02-screen-1.test",
    "content": "# Test of WRAP AROUND mode setting.\nINIT\nWANTSCREEN\n\nRESET\n\nPUSH \"\\e[?7h\"\n$REP 170: PUSH \"*\"\n\nPUSH \"\\e[?7l\\e[3;1H\"\n$REP 177: PUSH \"*\"\n\nPUSH \"\\e[?7h\\e[5;1HOK\"\n\n!Output\n$SEQ 0 2: ?screen_row \\# = \"********************************************************************************\"\n          ?screen_row  3 = \"\"\n          ?screen_row  4 = \"OK\"\n"
  },
  {
    "path": "t/90vttest_02-screen-2.test",
    "content": "# TAB setting/resetting\nINIT\nWANTSTATE\nWANTSCREEN\n\nRESET\n\nPUSH \"\\e[2J\\e[3g\"\n\nPUSH \"\\e[1;1H\"\n$REP 26: PUSH \"\\e[3C\\eH\"\n\nPUSH \"\\e[1;4H\"\n$REP 13: PUSH \"\\e[0g\\e[6C\"\n\nPUSH \"\\e[1;7H\"\nPUSH \"\\e[1g\\e[2g\"\n\nPUSH \"\\e[1;1H\"\n$REP 13: PUSH \"\\t*\"\n\nPUSH \"\\e[2;2H\"\n$REP 13: PUSH \"     *\"\n\n!Output\n?screen_row 0 = \"      *     *     *     *     *     *     *     *     *     *     *     *     *\"\n?screen_row 1 = \"      *     *     *     *     *     *     *     *     *     *     *     *     *\"\n\n?cursor = 1,79\n"
  },
  {
    "path": "t/90vttest_02-screen-3.test",
    "content": "# Origin mode\nINIT\nWANTSCREEN\n\nRESET\n\nPUSH \"\\e[?6h\"\nPUSH \"\\e[23;24r\"\nPUSH \"\\n\"\nPUSH \"Bottom\"\nPUSH \"\\e[1;1H\"\nPUSH \"Above\"\n\n!Output\n?screen_row 22 = \"Above\"\n?screen_row 23 = \"Bottom\"\n"
  },
  {
    "path": "t/90vttest_02-screen-4.test",
    "content": "# Origin mode (2)\nINIT\nWANTSCREEN\n\nRESET\n\nPUSH \"\\e[?6l\"\nPUSH \"\\e[23;24r\"\nPUSH \"\\e[24;1H\"\nPUSH \"Bottom\"\nPUSH \"\\e[1;1H\"\nPUSH \"Top\"\n\n!Output\n?screen_row 23 = \"Bottom\"\n?screen_row 0  = \"Top\"\n\n"
  },
  {
    "path": "t/92lp1640917.test",
    "content": "INIT\nWANTSTATE \n\n!Mouse reporting should not break by idempotent DECSM 1002\nPUSH \"\\e[?1002h\"\nMOUSEMOVE 0,0 0\nMOUSEBTN d 1 0\n  output \"\\e[M\\x20\\x21\\x21\"\nMOUSEMOVE 1,0 0\n  output \"\\e[M\\x40\\x21\\x22\"\nPUSH \"\\e[?1002h\"\nMOUSEMOVE 2,0 0\n  output \"\\e[M\\x40\\x21\\x23\"\n"
  },
  {
    "path": "t/harness.c",
    "content": "#include \"vterm.h\"\n#include \"../src/vterm_internal.h\" // We pull in some internal bits too\n\n#include <assert.h>\n#include <stdio.h>\n#include <string.h>\n\n#define streq(a,b) (!strcmp(a,b))\n#define strstartswith(a,b) (!strncmp(a,b,strlen(b)))\n\nstatic size_t inplace_hex2bytes(char *s)\n{\n  char *inpos = s, *outpos = s;\n\n  while(*inpos) {\n    unsigned int ch;\n    if(sscanf(inpos, \"%2x\", &ch) < 1)\n      break;\n    *outpos = ch;\n    outpos += 1; inpos += 2;\n  }\n\n  return outpos - s;\n}\n\nstatic VTermModifier strpe_modifiers(char **strp)\n{\n  VTermModifier state = 0;\n\n  while((*strp)[0]) {\n    switch(((*strp)++)[0]) {\n      case 'S': state |= VTERM_MOD_SHIFT; break;\n      case 'C': state |= VTERM_MOD_CTRL;  break;\n      case 'A': state |= VTERM_MOD_ALT;   break;\n      default: return state;\n    }\n  }\n\n  return state;\n}\n\nstatic VTermKey strp_key(char *str)\n{\n  static struct {\n    char *name;\n    VTermKey key;\n  } keys[] = {\n    { \"Up\",    VTERM_KEY_UP },\n    { \"Tab\",   VTERM_KEY_TAB },\n    { \"Enter\", VTERM_KEY_ENTER },\n    { \"KP0\",   VTERM_KEY_KP_0 },\n    { \"F1\",    VTERM_KEY_FUNCTION(1) },\n    { NULL,    VTERM_KEY_NONE },\n  };\n\n  for(int i = 0; keys[i].name; i++) {\n    if(streq(str, keys[i].name))\n      return keys[i].key;\n  }\n\n  return VTERM_KEY_NONE;\n}\n\nstatic void print_color(const VTermColor *col)\n{\n  if (VTERM_COLOR_IS_RGB(col)) {\n    printf(\"rgb(%d,%d,%d\", col->rgb.red, col->rgb.green, col->rgb.blue);\n  }\n  else if (VTERM_COLOR_IS_INDEXED(col)) {\n    printf(\"idx(%d\", col->indexed.idx);\n  }\n  else {\n    printf(\"invalid(%d\", col->type);\n  }\n  if (VTERM_COLOR_IS_DEFAULT_FG(col)) {\n    printf(\",is_default_fg\");\n  }\n  if (VTERM_COLOR_IS_DEFAULT_BG(col)) {\n    printf(\",is_default_bg\");\n  }\n  printf(\")\");\n}\n\nstatic VTermColor strpe_color(char **strp)\n{\n  uint8_t r, g, b, idx;\n  int len = 0;\n  VTermColor col;\n\n  if(sscanf(*strp, \"rgb(%hhu,%hhu,%hhu)%n\", &r, &g, &b, &len) == 3 && len > 0) {\n    *strp += len;\n    vterm_color_rgb(&col, r, g, b);\n  }\n  else if(sscanf(*strp, \"idx(%hhu)%n\", &idx, &len) == 1 && len > 0) {\n    *strp += len;\n    vterm_color_indexed(&col, idx);\n  }\n  else\n    vterm_color_rgb(&col, 127, 127, 127);\n\n  return col;\n}\n\nstatic VTerm *vt;\nstatic VTermState *state;\nstatic VTermScreen *screen;\n\nstatic VTermEncodingInstance encoding;\n\nstatic void term_output(const char *s, size_t len, void *user)\n{\n  printf(\"output \");\n  for(int i = 0; i < len; i++)\n    printf(\"%x%s\", (unsigned char)s[i], i < len-1 ? \",\" : \"\\n\");\n}\n\nstatic void printhex(const char *s, size_t len)\n{\n  while(len--)\n    printf(\"%02x\", (uint8_t)(s++)[0]);\n}\n\nstatic int parser_text(const char bytes[], size_t len, void *user)\n{\n  printf(\"text \");\n  int i;\n  for(i = 0; i < len; i++) {\n    unsigned char b = bytes[i];\n    if(b < 0x20 || b == 0x7f || (b >= 0x80 && b < 0xa0))\n      break;\n    printf(i ? \",%x\" : \"%x\", b);\n  }\n  printf(\"\\n\");\n\n  return i;\n}\n\nstatic int parser_control(unsigned char control, void *user)\n{\n  printf(\"control %02x\\n\", control);\n\n  return 1;\n}\n\nstatic int parser_escape(const char bytes[], size_t len, void *user)\n{\n  if(bytes[0] >= 0x20 && bytes[0] < 0x30) {\n    if(len < 2)\n      return -1;\n    len = 2;\n  }\n  else {\n    len = 1;\n  }\n\n  printf(\"escape \");\n  printhex(bytes, len);\n  printf(\"\\n\");\n\n  return len;\n}\n\nstatic int parser_csi(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user)\n{\n  printf(\"csi %02x\", command);\n\n  if(leader && leader[0]) {\n    printf(\" L=\");\n    for(int i = 0; leader[i]; i++)\n      printf(\"%02x\", leader[i]);\n  }\n\n  for(int i = 0; i < argcount; i++) {\n    char sep = i ? ',' : ' ';\n\n    if(args[i] == CSI_ARG_MISSING)\n      printf(\"%c*\", sep);\n    else\n      printf(\"%c%ld%s\", sep, CSI_ARG(args[i]), CSI_ARG_HAS_MORE(args[i]) ? \"+\" : \"\");\n  }\n\n  if(intermed && intermed[0]) {\n    printf(\" I=\");\n    for(int i = 0; intermed[i]; i++)\n      printf(\"%02x\", intermed[i]);\n  }\n\n  printf(\"\\n\");\n\n  return 1;\n}\n\nstatic int parser_osc(int command, VTermStringFragment frag, void *user)\n{\n  printf(\"osc \");\n\n  if(frag.initial) {\n    if(command == -1)\n      printf(\"[\");\n    else\n      printf(\"[%d;\", command);\n  }\n\n  printhex(frag.str, frag.len);\n\n  if(frag.final)\n    printf(\"]\");\n\n  printf(\"\\n\");\n\n  return 1;\n}\n\nstatic int parser_dcs(const char *command, size_t commandlen, VTermStringFragment frag, void *user)\n{\n  printf(\"dcs \");\n\n  if(frag.initial) {\n    printf(\"[\");\n    for(int i = 0; i < commandlen; i++)\n      printf(\"%02x\", command[i]);\n  }\n\n  printhex(frag.str, frag.len);\n\n  if(frag.final)\n    printf(\"]\");\n\n  printf(\"\\n\");\n\n  return 1;\n}\n\nstatic int parser_apc(VTermStringFragment frag, void *user)\n{\n  printf(\"apc \");\n\n  if(frag.initial)\n    printf(\"[\");\n\n  printhex(frag.str, frag.len);\n\n  if(frag.final)\n    printf(\"]\");\n\n  printf(\"\\n\");\n\n  return 1;\n}\n\nstatic int parser_pm(VTermStringFragment frag, void *user)\n{\n  printf(\"pm \");\n\n  if(frag.initial)\n    printf(\"[\");\n\n  printhex(frag.str, frag.len);\n\n  if(frag.final)\n    printf(\"]\");\n\n  printf(\"\\n\");\n\n  return 1;\n}\n\nstatic int parser_sos(VTermStringFragment frag, void *user)\n{\n  printf(\"sos \");\n\n  if(frag.initial)\n    printf(\"[\");\n\n  printhex(frag.str, frag.len);\n\n  if(frag.final)\n    printf(\"]\");\n\n  printf(\"\\n\");\n\n  return 1;\n}\n\nstatic VTermParserCallbacks parser_cbs = {\n  .text    = parser_text,\n  .control = parser_control,\n  .escape  = parser_escape,\n  .csi     = parser_csi,\n  .osc     = parser_osc,\n  .dcs     = parser_dcs,\n  .apc     = parser_apc,\n  .pm      = parser_pm,\n  .sos     = parser_sos,\n};\n\nstatic VTermStateFallbacks fallbacks = {\n  .control = parser_control,\n  .csi     = parser_csi,\n  .osc     = parser_osc,\n  .dcs     = parser_dcs,\n  .apc     = parser_apc,\n  .pm      = parser_pm,\n  .sos     = parser_sos,\n};\n\n/* These callbacks are shared by State and Screen */\n\nstatic int want_movecursor = 0;\nstatic VTermPos state_pos;\nstatic int movecursor(VTermPos pos, VTermPos oldpos, int visible, void *user)\n{\n  state_pos = pos;\n\n  if(want_movecursor)\n    printf(\"movecursor %d,%d\\n\", pos.row, pos.col);\n\n  return 1;\n}\n\nstatic int want_premove = 0;\nstatic int premove(VTermRect rect, void *user)\n{\n  if(!want_premove)\n    return 0;\n\n  printf(\"premove %d..%d,%d..%d\\n\",\n      rect.start_row, rect.end_row, rect.start_col, rect.end_col);\n\n  return 1;\n}\n\nstatic int want_scrollrect = 0;\nstatic int scrollrect(VTermRect rect, int downward, int rightward, void *user)\n{\n  if(!want_scrollrect)\n    return 0;\n\n  printf(\"scrollrect %d..%d,%d..%d => %+d,%+d\\n\",\n      rect.start_row, rect.end_row, rect.start_col, rect.end_col,\n      downward, rightward);\n\n  return 1;\n}\n\nstatic int want_moverect = 0;\nstatic int moverect(VTermRect dest, VTermRect src, void *user)\n{\n  if(!want_moverect)\n    return 0;\n\n  printf(\"moverect %d..%d,%d..%d -> %d..%d,%d..%d\\n\",\n      src.start_row,  src.end_row,  src.start_col,  src.end_col,\n      dest.start_row, dest.end_row, dest.start_col, dest.end_col);\n\n  return 1;\n}\n\nstatic int want_settermprop = 0;\nstatic int settermprop(VTermProp prop, VTermValue *val, void *user)\n{\n  if(!want_settermprop)\n    return 1;\n\n  VTermValueType type = vterm_get_prop_type(prop);\n  switch(type) {\n  case VTERM_VALUETYPE_BOOL:\n    printf(\"settermprop %d %s\\n\", prop, val->boolean ? \"true\" : \"false\");\n    return 1;\n  case VTERM_VALUETYPE_INT:\n    printf(\"settermprop %d %d\\n\", prop, val->number);\n    return 1;\n  case VTERM_VALUETYPE_STRING:\n    printf(\"settermprop %d %s\\\"%.*s\\\"%s\\n\", prop,\n        val->string.initial ? \"[\" : \"\", val->string.len, val->string.str, val->string.final ? \"]\" : \"\");\n    return 1;\n  case VTERM_VALUETYPE_COLOR:\n    printf(\"settermprop %d \", prop);\n    print_color(&val->color);\n    printf(\"\\n\");\n    return 1;\n\n  case VTERM_N_VALUETYPES:\n    return 0;\n  }\n\n  return 0;\n}\n\n/* These callbacks are for State */\n\nstatic int want_state_putglyph = 0;\nstatic int state_putglyph(VTermGlyphInfo *info, VTermPos pos, void *user)\n{\n  if(!want_state_putglyph)\n    return 1;\n\n  printf(\"putglyph \");\n  for(int i = 0; i < VTERM_MAX_CHARS_PER_CELL && info->chars[i]; i++)\n    printf(i ? \",%x\" : \"%x\", info->chars[i]);\n  printf(\" %d %d,%d\", info->width, pos.row, pos.col);\n  if(info->protected_cell)\n    printf(\" prot\");\n  if(info->dwl)\n    printf(\" dwl\");\n  if(info->dhl)\n    printf(\" dhl-%s\", info->dhl == 1 ? \"top\" : info->dhl == 2 ? \"bottom\" : \"?\" );\n  printf(\"\\n\");\n\n  return 1;\n}\n\nstatic int want_state_erase = 0;\nstatic int state_erase(VTermRect rect, int selective, void *user)\n{\n  if(!want_state_erase)\n    return 1;\n\n  printf(\"erase %d..%d,%d..%d%s\\n\",\n      rect.start_row, rect.end_row, rect.start_col, rect.end_col,\n      selective ? \" selective\" : \"\");\n\n  return 1;\n}\n\nstatic struct {\n  int bold;\n  int underline;\n  int italic;\n  int blink;\n  int reverse;\n  int conceal;\n  int strike;\n  int font;\n  int small;\n  int baseline;\n  VTermColor foreground;\n  VTermColor background;\n} state_pen;\nstatic int state_setpenattr(VTermAttr attr, VTermValue *val, void *user)\n{\n  switch(attr) {\n  case VTERM_ATTR_BOLD:\n    state_pen.bold = val->boolean;\n    break;\n  case VTERM_ATTR_UNDERLINE:\n    state_pen.underline = val->number;\n    break;\n  case VTERM_ATTR_ITALIC:\n    state_pen.italic = val->boolean;\n    break;\n  case VTERM_ATTR_BLINK:\n    state_pen.blink = val->boolean;\n    break;\n  case VTERM_ATTR_REVERSE:\n    state_pen.reverse = val->boolean;\n    break;\n  case VTERM_ATTR_CONCEAL:\n    state_pen.conceal = val->boolean;\n    break;\n  case VTERM_ATTR_STRIKE:\n    state_pen.strike = val->boolean;\n    break;\n  case VTERM_ATTR_FONT:\n    state_pen.font = val->number;\n    break;\n  case VTERM_ATTR_SMALL:\n    state_pen.small = val->boolean;\n    break;\n  case VTERM_ATTR_BASELINE:\n    state_pen.baseline = val->number;\n    break;\n  case VTERM_ATTR_FOREGROUND:\n    state_pen.foreground = val->color;\n    break;\n  case VTERM_ATTR_BACKGROUND:\n    state_pen.background = val->color;\n    break;\n\n  case VTERM_N_ATTRS:\n    return 0;\n  }\n\n  return 1;\n}\n\nstatic int state_setlineinfo(int row, const VTermLineInfo *newinfo, const VTermLineInfo *oldinfo, void *user)\n{\n  return 1;\n}\n\nstatic int want_state_scrollback = 0;\nstatic int state_sb_clear(void *user) {\n  if(!want_state_scrollback)\n    return 1;\n\n  printf(\"sb_clear\\n\");\n  return 0;\n}\n\nVTermStateCallbacks state_cbs = {\n  .putglyph    = state_putglyph,\n  .movecursor  = movecursor,\n  .premove     = premove,\n  .scrollrect  = scrollrect,\n  .moverect    = moverect,\n  .erase       = state_erase,\n  .setpenattr  = state_setpenattr,\n  .settermprop = settermprop,\n  .setlineinfo = state_setlineinfo,\n  .sb_clear    = state_sb_clear,\n};\n\nstatic int selection_set(VTermSelectionMask mask, VTermStringFragment frag, void *user)\n{\n  printf(\"selection-set mask=%04X \", mask);\n  if(frag.initial)\n    printf(\"[\");\n  printhex(frag.str, frag.len);\n  if(frag.final)\n    printf(\"]\");\n  printf(\"\\n\");\n\n  return 1;\n}\n\nstatic int selection_query(VTermSelectionMask mask, void *user)\n{\n  printf(\"selection-query mask=%04X\\n\", mask);\n\n  return 1;\n}\n\nVTermSelectionCallbacks selection_cbs = {\n  .set   = selection_set,\n  .query = selection_query,\n};\n\nstatic int want_screen_damage = 0;\nstatic int want_screen_damage_cells = 0;\nstatic int screen_damage(VTermRect rect, void *user)\n{\n  if(!want_screen_damage)\n    return 1;\n\n  printf(\"damage %d..%d,%d..%d\",\n      rect.start_row, rect.end_row, rect.start_col, rect.end_col);\n\n  if(want_screen_damage_cells) {\n    bool equals = false;\n\n    for(int row = rect.start_row; row < rect.end_row; row++) {\n      int eol = rect.end_col;\n      while(eol > rect.start_col) {\n        VTermScreenCell cell;\n        vterm_screen_get_cell(screen, (VTermPos) { .row = row, .col = eol-1 }, &cell);\n        if(cell.chars[0])\n          break;\n\n        eol--;\n      }\n\n      if(eol == rect.start_col)\n        break;\n\n      if(!equals)\n        printf(\" =\"), equals = true;\n\n      printf(\" %d<\", row);\n      for(int col = rect.start_col; col < eol; col++) {\n        VTermScreenCell cell;\n        vterm_screen_get_cell(screen, (VTermPos) { .row = row, .col = col }, &cell);\n        printf(col == rect.start_col ? \"%02X\" : \" %02X\", cell.chars[0]);\n      }\n      printf(\">\");\n    }\n  }\n\n  printf(\"\\n\");\n\n  return 1;\n}\n\nstatic int want_screen_scrollback = 0;\nstatic int screen_sb_pushline4(int cols, const VTermScreenCell *cells, bool continuation, void *user)\n{\n  if(!want_screen_scrollback)\n    return 1;\n\n  int eol = cols;\n  while(eol && !cells[eol-1].chars[0])\n    eol--;\n\n  printf(\"sb_pushline %d%s =\", cols, continuation ? \" cont\" : \"\");\n  for(int c = 0; c < eol; c++)\n    printf(\" %02X\", cells[c].chars[0]);\n  printf(\"\\n\");\n\n  return 1;\n}\n\nstatic int screen_sb_popline(int cols, VTermScreenCell *cells, void *user)\n{\n  if(!want_screen_scrollback)\n    return 0;\n\n  // All lines of scrollback contain \"ABCDE\"\n  for(int col = 0; col < cols; col++) {\n    if(col < 5)\n      cells[col].chars[0] = 'A' + col;\n    else\n      cells[col].chars[0] = 0;\n\n    cells[col].width = 1;\n  }\n\n  printf(\"sb_popline %d\\n\", cols);\n  return 1;\n}\n\nstatic int screen_sb_clear(void *user)\n{\n  if(!want_screen_scrollback)\n    return 1;\n\n  printf(\"sb_clear\\n\");\n  return 0;\n}\n\nVTermScreenCallbacks screen_cbs = {\n  .damage       = screen_damage,\n  .moverect     = moverect,\n  .movecursor   = movecursor,\n  .settermprop  = settermprop,\n  .sb_popline   = screen_sb_popline,\n  .sb_clear     = screen_sb_clear,\n  .sb_pushline4 = screen_sb_pushline4,\n};\n\nint main(int argc, char **argv)\n{\n  char line[1024] = {0};\n  int flag;\n\n  int err;\n\n  setvbuf(stdout, NULL, _IONBF, 0);\n\n  while(fgets(line, sizeof line, stdin)) {\n    err = 0;\n\n    char *nl;\n    if((nl = strchr(line, '\\n')))\n      *nl = '\\0';\n\n    if(streq(line, \"INIT\")) {\n      if(!vt)\n        vt = vterm_new(25, 80);\n\n      vterm_output_set_callback(vt, term_output, NULL);\n    }\n\n    else if(streq(line, \"WANTPARSER\")) {\n      assert(vt);\n      vterm_parser_set_callbacks(vt, &parser_cbs, NULL);\n    }\n\n    else if(strstartswith(line, \"WANTSTATE\") && (line[9] == '\\0' || line[9] == ' ')) {\n      assert(vt);\n      if(!state) {\n        state = vterm_obtain_state(vt);\n        vterm_state_set_callbacks(state, &state_cbs, NULL);\n        vterm_state_callbacks_has_premove(state);\n        /* In some tests we want to check the behaviour of overflowing the\n         * buffer, so make it nicely small\n         */\n        vterm_state_set_selection_callbacks(state, &selection_cbs, NULL, NULL, 16);\n        vterm_state_set_bold_highbright(state, 1);\n        vterm_state_reset(state, 1);\n      }\n\n      int i = 9;\n      int sense = 1;\n      while(line[i] == ' ')\n        i++;\n      for( ; line[i]; i++)\n        switch(line[i]) {\n        case '+':\n          sense = 1;\n          break;\n        case '-':\n          sense = 0;\n          break;\n        case 'g':\n          want_state_putglyph = sense;\n          break;\n        case 's':\n          want_scrollrect = sense;\n          break;\n        case 'P':\n          want_premove = sense;\n          break;\n        case 'm':\n          want_moverect = sense;\n          break;\n        case 'e':\n          want_state_erase = sense;\n          break;\n        case 'p':\n          want_settermprop = sense;\n          break;\n        case 'f':\n          vterm_state_set_unrecognised_fallbacks(state, sense ? &fallbacks : NULL, NULL);\n          break;\n        case 'b':\n          want_state_scrollback = sense;\n          break;\n        default:\n          fprintf(stderr, \"Unrecognised WANTSTATE flag '%c'\\n\", line[i]);\n        }\n    }\n\n    else if(strstartswith(line, \"WANTSCREEN\") && (line[10] == '\\0' || line[10] == ' ')) {\n      assert(vt);\n      if(!screen)\n        screen = vterm_obtain_screen(vt);\n      vterm_screen_set_callbacks(screen, &screen_cbs, NULL);\n      vterm_screen_callbacks_has_pushline4(screen);\n\n      int i = 10;\n      int sense = 1;\n      while(line[i] == ' ')\n        i++;\n      for( ; line[i]; i++)\n        switch(line[i]) {\n        case '-':\n          sense = 0;\n          break;\n        case 'a':\n          vterm_screen_enable_altscreen(screen, 1);\n          break;\n        case 'd':\n          want_screen_damage = sense;\n          break;\n        case 'D':\n          want_screen_damage = sense;\n          want_screen_damage_cells = sense;\n          break;\n        case 'm':\n          want_moverect = sense;\n          break;\n        case 'c':\n          want_movecursor = sense;\n          break;\n        case 'p':\n          want_settermprop = 1;\n          break;\n        case 'b':\n          want_screen_scrollback = sense;\n          break;\n        case 'r':\n          vterm_screen_enable_reflow(screen, sense);\n          break;\n        default:\n          fprintf(stderr, \"Unrecognised WANTSCREEN flag '%c'\\n\", line[i]);\n        }\n    }\n\n    else if(sscanf(line, \"UTF8 %d\", &flag)) {\n      vterm_set_utf8(vt, flag);\n    }\n\n    else if(streq(line, \"RESET\")) {\n      if(state) {\n        vterm_state_reset(state, 1);\n        vterm_state_get_cursorpos(state, &state_pos);\n      }\n      if(screen) {\n        vterm_screen_reset(screen, 1);\n      }\n    }\n\n    else if(strstartswith(line, \"RESIZE \")) {\n      int rows, cols;\n      char *linep = line + 7;\n      while(linep[0] == ' ')\n        linep++;\n      sscanf(linep, \"%d, %d\", &rows, &cols);\n      vterm_set_size(vt, rows, cols);\n    }\n\n    else if(strstartswith(line, \"PUSH \")) {\n      char *bytes = line + 5;\n      size_t len = inplace_hex2bytes(bytes);\n      assert(len);\n\n      size_t written = vterm_input_write(vt, bytes, len);\n      if(written < len)\n        fprintf(stderr, \"! short write\\n\");\n    }\n\n    else if(streq(line, \"WANTENCODING\")) {\n      /* This isn't really external API but it's hard to get this out any\n       * other way\n       */\n      encoding.enc = vterm_lookup_encoding(ENC_UTF8, 'u');\n      if(encoding.enc->init)\n        (*encoding.enc->init)(encoding.enc, encoding.data);\n    }\n\n    else if(strstartswith(line, \"ENCIN \")) {\n      char *bytes = line + 6;\n      size_t len = inplace_hex2bytes(bytes);\n      assert(len);\n\n      uint32_t cp[len];\n      int cpi = 0;\n      size_t pos = 0;\n\n      (*encoding.enc->decode)(encoding.enc, encoding.data,\n          cp, &cpi, len, bytes, &pos, len);\n\n      if(cpi > 0) {\n        printf(\"encout \");\n        for(int i = 0; i < cpi; i++) {\n          printf(i ? \",%x\" : \"%x\", cp[i]);\n        }\n        printf(\"\\n\");\n      }\n    }\n\n    else if(strstartswith(line, \"INCHAR \")) {\n      char *linep = line + 7;\n      unsigned int c = 0;\n      while(linep[0] == ' ')\n        linep++;\n      VTermModifier mod = strpe_modifiers(&linep);\n      sscanf(linep, \" %x\", &c);\n\n      vterm_keyboard_unichar(vt, c, mod);\n    }\n\n    else if(strstartswith(line, \"INKEY \")) {\n      char *linep = line + 6;\n      while(linep[0] == ' ')\n        linep++;\n      VTermModifier mod = strpe_modifiers(&linep);\n      while(linep[0] == ' ')\n        linep++;\n      VTermKey key = strp_key(linep);\n\n      vterm_keyboard_key(vt, key, mod);\n    }\n\n    else if(strstartswith(line, \"PASTE \")) {\n      char *linep = line + 6;\n      if(streq(linep, \"START\"))\n        vterm_keyboard_start_paste(vt);\n      else if(streq(linep, \"END\"))\n        vterm_keyboard_end_paste(vt);\n      else\n        goto abort_line;\n    }\n\n    else if(strstartswith(line, \"FOCUS \")) {\n      assert(state);\n      char *linep = line + 6;\n      if(streq(linep, \"IN\"))\n        vterm_state_focus_in(state);\n      else if(streq(linep, \"OUT\"))\n        vterm_state_focus_out(state);\n      else\n        goto abort_line;\n    }\n\n    else if(strstartswith(line, \"MOUSEMOVE \")) {\n      char *linep = line + 10;\n      int row, col, len;\n      while(linep[0] == ' ')\n        linep++;\n      sscanf(linep, \"%d,%d%n\", &row, &col, &len);\n      linep += len;\n      while(linep[0] == ' ')\n        linep++;\n      VTermModifier mod = strpe_modifiers(&linep);\n      vterm_mouse_move(vt, row, col, mod);\n    }\n\n    else if(strstartswith(line, \"MOUSEBTN \")) {\n      char *linep = line + 9;\n      char press;\n      int button, len;\n      while(linep[0] == ' ')\n        linep++;\n      sscanf(linep, \"%c %d%n\", &press, &button, &len);\n      linep += len;\n      while(linep[0] == ' ')\n        linep++;\n      VTermModifier mod = strpe_modifiers(&linep);\n      vterm_mouse_button(vt, button, (press == 'd' || press == 'D'), mod);\n    }\n\n    else if(strstartswith(line, \"SELECTION \")) {\n      assert(state);\n      char *linep = line + 10;\n      unsigned int mask;\n      int len;\n      VTermStringFragment frag = { 0 };\n      sscanf(linep, \"%x%n\", &mask, &len);\n      linep += len;\n      while(linep[0] == ' ')\n        linep++;\n      if(linep[0] == '[') {\n        frag.initial = true;\n        linep++;\n        while(linep[0] == ' ')\n          linep++;\n      }\n      frag.len = inplace_hex2bytes(linep);\n      frag.str = linep;\n      assert(frag.len);\n\n      linep += frag.len * 2;\n      while(linep[0] == ' ')\n        linep++;\n      if(linep[0] == ']') {\n        frag.final = true;\n      }\n      vterm_state_send_selection(state, mask, frag);\n    }\n\n    else if(strstartswith(line, \"DAMAGEMERGE \")) {\n      assert(screen);\n      char *linep = line + 12;\n      while(linep[0] == ' ')\n        linep++;\n      if(streq(linep, \"CELL\"))\n        vterm_screen_set_damage_merge(screen, VTERM_DAMAGE_CELL);\n      else if(streq(linep, \"ROW\"))\n        vterm_screen_set_damage_merge(screen, VTERM_DAMAGE_ROW);\n      else if(streq(linep, \"SCREEN\"))\n        vterm_screen_set_damage_merge(screen, VTERM_DAMAGE_SCREEN);\n      else if(streq(linep, \"SCROLL\"))\n        vterm_screen_set_damage_merge(screen, VTERM_DAMAGE_SCROLL);\n    }\n\n    else if(strstartswith(line, \"DAMAGEFLUSH\")) {\n      assert(screen);\n      vterm_screen_flush_damage(screen);\n    }\n\n    else if(strstartswith(line, \"SETDEFAULTCOL \")) {\n      assert(screen);\n      char *linep = line + 14;\n      while(linep[0] == ' ')\n        linep++;\n      VTermColor fg = strpe_color(&linep);\n      if(linep[0]) {\n        while(linep[0] == ' ')\n          linep++;\n        VTermColor bg = strpe_color(&linep);\n\n        vterm_screen_set_default_colors(screen, &fg, &bg);\n      }\n      else\n        vterm_screen_set_default_colors(screen, &fg, NULL);\n    }\n\n    else if(line[0] == '?') {\n      if(streq(line, \"?cursor\")) {\n        assert(state);\n        VTermPos pos;\n        vterm_state_get_cursorpos(state, &pos);\n        if(pos.row != state_pos.row)\n          printf(\"! row mismatch: state=%d,%d event=%d,%d\\n\",\n              pos.row, pos.col, state_pos.row, state_pos.col);\n        else if(pos.col != state_pos.col)\n          printf(\"! col mismatch: state=%d,%d event=%d,%d\\n\",\n              pos.row, pos.col, state_pos.row, state_pos.col);\n        else\n          printf(\"%d,%d\\n\", state_pos.row, state_pos.col);\n      }\n      else if(strstartswith(line, \"?pen \")) {\n        assert(state);\n        char *linep = line + 5;\n        while(linep[0] == ' ')\n          linep++;\n\n        VTermValue val;\n#define BOOLSTR(v) ((v) ? \"on\" : \"off\")\n\n        if(streq(linep, \"bold\")) {\n          vterm_state_get_penattr(state, VTERM_ATTR_BOLD, &val);\n          if(val.boolean != state_pen.bold)\n            printf(\"! pen bold mismatch; state=%s, event=%s\\n\",\n                BOOLSTR(val.boolean), BOOLSTR(state_pen.bold));\n          else\n            printf(\"%s\\n\", BOOLSTR(state_pen.bold));\n        }\n        else if(streq(linep, \"underline\")) {\n          vterm_state_get_penattr(state, VTERM_ATTR_UNDERLINE, &val);\n          if(val.boolean != state_pen.underline)\n            printf(\"! pen underline mismatch; state=%d, event=%d\\n\",\n                val.boolean, state_pen.underline);\n          else\n            printf(\"%d\\n\", state_pen.underline);\n        }\n        else if(streq(linep, \"italic\")) {\n          vterm_state_get_penattr(state, VTERM_ATTR_ITALIC, &val);\n          if(val.boolean != state_pen.italic)\n            printf(\"! pen italic mismatch; state=%s, event=%s\\n\",\n                BOOLSTR(val.boolean), BOOLSTR(state_pen.italic));\n          else\n            printf(\"%s\\n\", BOOLSTR(state_pen.italic));\n        }\n        else if(streq(linep, \"blink\")) {\n          vterm_state_get_penattr(state, VTERM_ATTR_BLINK, &val);\n          if(val.boolean != state_pen.blink)\n            printf(\"! pen blink mismatch; state=%s, event=%s\\n\",\n                BOOLSTR(val.boolean), BOOLSTR(state_pen.blink));\n          else\n            printf(\"%s\\n\", BOOLSTR(state_pen.blink));\n        }\n        else if(streq(linep, \"reverse\")) {\n          vterm_state_get_penattr(state, VTERM_ATTR_REVERSE, &val);\n          if(val.boolean != state_pen.reverse)\n            printf(\"! pen reverse mismatch; state=%s, event=%s\\n\",\n                BOOLSTR(val.boolean), BOOLSTR(state_pen.reverse));\n          else\n            printf(\"%s\\n\", BOOLSTR(state_pen.reverse));\n        }\n        else if(streq(linep, \"font\")) {\n          vterm_state_get_penattr(state, VTERM_ATTR_FONT, &val);\n          if(val.boolean != state_pen.font)\n            printf(\"! pen font mismatch; state=%d, event=%d\\n\",\n                val.boolean, state_pen.font);\n          else\n            printf(\"%d\\n\", state_pen.font);\n        }\n        else if(streq(linep, \"small\")) {\n          vterm_state_get_penattr(state, VTERM_ATTR_SMALL, &val);\n          if(val.boolean != state_pen.small)\n            printf(\"! pen small mismatch; state=%s, event=%s\\n\",\n                BOOLSTR(val.boolean), BOOLSTR(state_pen.small));\n          else\n            printf(\"%s\\n\", BOOLSTR(state_pen.small));\n        }\n        else if(streq(linep, \"baseline\")) {\n          vterm_state_get_penattr(state, VTERM_ATTR_BASELINE, &val);\n          if(val.number != state_pen.baseline)\n            printf(\"! pen baseline mismatch: state=%d, event=%d\\n\",\n                val.number, state_pen.baseline);\n          else\n            printf(\"%s\\n\", state_pen.baseline == VTERM_BASELINE_RAISE ? \"raise\"\n                         : state_pen.baseline == VTERM_BASELINE_LOWER ? \"lower\"\n                         : \"normal\");\n        }\n        else if(streq(linep, \"foreground\")) {\n          print_color(&state_pen.foreground);\n          printf(\"\\n\");\n        }\n        else if(streq(linep, \"background\")) {\n          print_color(&state_pen.background);\n          printf(\"\\n\");\n        }\n        else\n          printf(\"?\\n\");\n      }\n      else if(strstartswith(line, \"?lineinfo \")) {\n        assert(state);\n        char *linep = line + 10;\n        int row;\n        const VTermLineInfo *info;\n        while(linep[0] == ' ')\n          linep++;\n        if(sscanf(linep, \"%d\", &row) < 1) {\n          printf(\"! lineinfo unrecognised input\\n\");\n          goto abort_line;\n        }\n        info = vterm_state_get_lineinfo(state, row);\n        if(info->doublewidth)\n          printf(\"dwl \");\n        if(info->doubleheight)\n          printf(\"dhl \");\n        if(info->continuation)\n          printf(\"cont \");\n        printf(\"\\n\");\n      }\n      else if(strstartswith(line, \"?screen_chars \")) {\n        assert(screen);\n        char *linep = line + 13;\n        VTermRect rect;\n        size_t len;\n        while(linep[0] == ' ')\n          linep++;\n        if(sscanf(linep, \"%d,%d,%d,%d\", &rect.start_row, &rect.start_col, &rect.end_row, &rect.end_col) == 4)\n          ; // fine\n        else if(sscanf(linep, \"%d\", &rect.start_row) == 1) {\n          rect.end_row = rect.start_row + 1;\n          rect.start_col = 0;\n          vterm_get_size(vt, NULL, &rect.end_col);\n        }\n        else {\n          printf(\"! screen_chars unrecognised input\\n\");\n          goto abort_line;\n        }\n        len = vterm_screen_get_chars(screen, NULL, 0, rect);\n        if(len == (size_t)-1)\n          printf(\"! screen_chars error\\n\");\n        else if(len == 0)\n          printf(\"\\n\");\n        else {\n          uint32_t *chars = malloc(sizeof(uint32_t) * len);\n          vterm_screen_get_chars(screen, chars, len, rect);\n          for(size_t i = 0; i < len; i++) {\n            printf(\"0x%02x%s\", chars[i], i < len-1 ? \",\" : \"\\n\");\n          }\n          free(chars);\n        }\n      }\n      else if(strstartswith(line, \"?screen_text \")) {\n        assert(screen);\n        char *linep = line + 12;\n        VTermRect rect;\n        size_t len;\n        while(linep[0] == ' ')\n          linep++;\n        if(sscanf(linep, \"%d,%d,%d,%d\", &rect.start_row, &rect.start_col, &rect.end_row, &rect.end_col) < 4) {\n          printf(\"! screen_text unrecognised input\\n\");\n          goto abort_line;\n        }\n        len = vterm_screen_get_text(screen, NULL, 0, rect);\n        if(len == (size_t)-1)\n          printf(\"! screen_text error\\n\");\n        else if(len == 0)\n          printf(\"\\n\");\n        else {\n          /* Put an overwrite guard at both ends of the buffer */\n          unsigned char *buffer = malloc(len + 4);\n          unsigned char *text = buffer + 2;\n          text[-2] = 0x55; text[-1] = 0xAA;\n          text[len] = 0x55; text[len+1] = 0xAA;\n\n          vterm_screen_get_text(screen, (char *)text, len, rect);\n\n          if(text[-2] != 0x55 || text[-1] != 0xAA)\n            printf(\"! screen_get_text buffer overrun left [%02x,%02x]\\n\", text[-2], text[-1]);\n          else if(text[len] != 0x55 || text[len+1] != 0xAA)\n            printf(\"! screen_get_text buffer overrun right [%02x,%02x]\\n\", text[len], text[len+1]);\n          else\n            for(size_t i = 0; i < len; i++) {\n              printf(\"0x%02x%s\", text[i], i < len-1 ? \",\" : \"\\n\");\n            }\n\n          free(buffer);\n        }\n      }\n      else if(strstartswith(line, \"?screen_cell \")) {\n        assert(screen);\n        char *linep = line + 12;\n        VTermPos pos;\n        while(linep[0] == ' ')\n          linep++;\n        if(sscanf(linep, \"%d,%d\\n\", &pos.row, &pos.col) < 2) {\n          printf(\"! screen_cell unrecognised input\\n\");\n          goto abort_line;\n        }\n        VTermScreenCell cell;\n        if(!vterm_screen_get_cell(screen, pos, &cell))\n          goto abort_line;\n        printf(\"{\");\n        for(int i = 0; i < VTERM_MAX_CHARS_PER_CELL && cell.chars[i]; i++) {\n          printf(\"%s0x%x\", i ? \",\" : \"\", cell.chars[i]);\n        }\n        printf(\"} width=%d attrs={\", cell.width);\n        if(cell.attrs.bold)      printf(\"B\");\n        if(cell.attrs.underline) printf(\"U%d\", cell.attrs.underline);\n        if(cell.attrs.italic)    printf(\"I\");\n        if(cell.attrs.blink)     printf(\"K\");\n        if(cell.attrs.reverse)   printf(\"R\");\n        if(cell.attrs.font)      printf(\"F%d\", cell.attrs.font);\n        if(cell.attrs.small)     printf(\"S\");\n        if(cell.attrs.baseline)  printf(\n            cell.attrs.baseline == VTERM_BASELINE_RAISE ? \"^\" :\n                                                          \"_\");\n        printf(\"} \");\n        if(cell.attrs.dwl)       printf(\"dwl \");\n        if(cell.attrs.dhl)       printf(\"dhl-%s \", cell.attrs.dhl == 2 ? \"bottom\" : \"top\");\n        printf(\"fg=\");\n        vterm_screen_convert_color_to_rgb(screen, &cell.fg);\n        print_color(&cell.fg);\n        printf(\" bg=\");\n        vterm_screen_convert_color_to_rgb(screen, &cell.bg);\n        print_color(&cell.bg);\n        printf(\"\\n\");\n      }\n      else if(strstartswith(line, \"?screen_eol \")) {\n        assert(screen);\n        char *linep = line + 12;\n        while(linep[0] == ' ')\n          linep++;\n        VTermPos pos;\n        if(sscanf(linep, \"%d,%d\\n\", &pos.row, &pos.col) < 2) {\n          printf(\"! screen_eol unrecognised input\\n\");\n          goto abort_line;\n        }\n        printf(\"%d\\n\", vterm_screen_is_eol(screen, pos));\n      }\n      else if(strstartswith(line, \"?screen_attrs_extent \")) {\n        assert(screen);\n        char *linep = line + 21;\n        while(linep[0] == ' ')\n          linep++;\n        VTermPos pos;\n        if(sscanf(linep, \"%d,%d\\n\", &pos.row, &pos.col) < 2) {\n          printf(\"! screen_attrs_extent unrecognised input\\n\");\n          goto abort_line;\n        }\n        VTermRect rect = {\n          .start_col = 0,\n          .end_col   = -1,\n        };\n        if(!vterm_screen_get_attrs_extent(screen, &rect, pos, ~0)) {\n          printf(\"! screen_attrs_extent failed\\n\");\n          goto abort_line;\n        }\n        printf(\"%d,%d-%d,%d\\n\", rect.start_row, rect.start_col, rect.end_row, rect.end_col);\n      }\n      else\n        printf(\"?\\n\");\n\n      memset(line, 0, sizeof line);\n      continue;\n    }\n\n    else\n      abort_line: err = 1;\n\n    size_t outlen = vterm_output_get_buffer_current(vt);\n    if(outlen > 0) {\n      char outbuff[outlen];\n      vterm_output_read(vt, outbuff, outlen);\n\n      term_output(outbuff, outlen, NULL);\n    }\n\n    printf(err ? \"?\\n\" : \"DONE\\n\");\n  }\n\n  vterm_free(vt);\n\n  return 0;\n}\n"
  },
  {
    "path": "t/run-test.pl",
    "content": "#!/usr/bin/perl\n\nuse strict;\nuse warnings;\nuse Getopt::Long;\nuse IO::Handle;\nuse IPC::Open2 qw( open2 );\nuse POSIX qw( WIFEXITED WEXITSTATUS WIFSIGNALED WTERMSIG );\n\nmy $VALGRIND = 0;\nmy $EXECUTABLE = \"t/.libs/harness\";\nGetOptions(\n   'valgrind|v+' => \\$VALGRIND,\n   'executable|e=s' => \\$EXECUTABLE,\n   'fail-early|F' => \\(my $FAIL_EARLY),\n) or exit 1;\n\nmy ( $hin, $hout, $hpid );\n{\n   local $ENV{LD_LIBRARY_PATH} = \".libs\";\n   my @command = $EXECUTABLE;\n   unshift @command, \"valgrind\", \"--quiet\", \"--error-exitcode=126\" if $VALGRIND;\n\n   $hpid = open2 $hout, $hin, @command or die \"Cannot open2 harness - $!\";\n}\n\nmy $exitcode = 0;\n\nmy $command;\nmy @expect;\n\nmy $linenum = 0;\n\nsub do_onetest\n{\n   $hin->print( \"$command\\n\" );\n   undef $command;\n\n   my $fail_printed = 0;\n\n   while( my $outline = <$hout> ) {\n      last if $outline eq \"DONE\\n\" or $outline eq \"?\\n\";\n\n      chomp $outline;\n\n      if( !@expect ) {\n         print \"# line $linenum: Test failed\\n\" unless $fail_printed++;\n         print \"#    expected nothing more\\n\" .\n               \"#   Actual:   $outline\\n\";\n         next;\n      }\n\n      my $expectation = shift @expect;\n\n      next if $expectation eq $outline;\n\n      print \"# line $linenum: Test failed\\n\" unless $fail_printed++;\n      print \"#   Expected: $expectation\\n\" .\n            \"#   Actual:   $outline\\n\";\n   }\n\n   if( @expect ) {\n      print \"# line $linenum: Test failed\\n\" unless $fail_printed++;\n      print \"#   Expected: $_\\n\" .\n            \"#    didn't happen\\n\" for @expect;\n   }\n\n   $exitcode = 1 if $fail_printed;\n   exit $exitcode if $exitcode and $FAIL_EARLY;\n}\n\nsub do_line\n{\n   my ( $line ) = @_;\n\n   if( $line =~ m/^!(.*)/ ) {\n      do_onetest if defined $command;\n      print \"> $1\\n\";\n   }\n\n   # Commands have capitals\n   elsif( $line =~ m/^([A-Z]+)/ ) {\n      # Some convenience formatting\n      if( $line =~ m/^(PUSH|ENCIN) (.*)$/ ) {\n         # we're evil\n         my $string = eval($2);\n         $line = \"$1 \" . unpack \"H*\", $string;\n      }\n      elsif( $line =~ m/^(SELECTION \\d+) +(\\[?)(.*?)(\\]?)$/ ) {\n         # we're evil\n         my $string = eval($3);\n         $line = \"$1 $2 \" . unpack( \"H*\", $string ) . \" $4\";\n      }\n\n      do_onetest if defined $command;\n\n      $command = $line;\n      undef @expect;\n   }\n   # Expectations have lowercase\n   elsif( $line =~ m/^([a-z]+)/ ) {\n      # Convenience formatting\n      if( $line =~ m/^(text|encout) (.*)$/ ) {\n         $line = \"$1 \" . join \",\", map sprintf(\"%x\", $_), eval($2);\n      }\n      elsif( $line =~ m/^(output) (.*)$/ ) {\n         $line = \"$1 \" . join \",\", map sprintf(\"%x\", $_), unpack \"C*\", eval($2);\n      }\n      elsif( $line =~ m/^control (.*)$/ ) {\n         $line = sprintf \"control %02x\", eval($1);\n      }\n      elsif( $line =~ m/^csi (\\S+) (.*)$/ ) {\n         $line = sprintf \"csi %02x %s\", eval($1), $2; # TODO\n      }\n      elsif( $line =~ m/^(osc) (\\[\\d+)? *(.*?)(\\]?)$/ ) {\n         my ( $cmd, $initial, $data, $final ) = ( $1, $2, $3, $4 );\n         $initial //= \"\";\n         $initial .= \";\" if $initial =~ m/\\d+/;\n\n         $line = \"$cmd $initial\" . join( \"\", map sprintf(\"%02x\", $_), unpack \"C*\", length $data ? eval($data) : \"\" ) . \"$final\";\n      }\n      elsif( $line =~ m/^(escape|dcs|apc|pm|sos) (\\[?)(.*?)(\\]?)$/ ) {\n         $line = \"$1 $2\" . join( \"\", map sprintf(\"%02x\", $_), unpack \"C*\", length $3 ? eval($3) : \"\" ) . \"$4\";\n      }\n      elsif( $line =~ m/^putglyph (\\S+) (.*)$/ ) {\n         $line = \"putglyph \" . join( \",\", map sprintf(\"%x\", $_), eval($1) ) . \" $2\";\n      }\n      elsif( $line =~ m/^(?:movecursor|scrollrect|premove|moverect|erase|damage|sb_pushline|sb_popline|sb_clear|settermprop|setmousefunc|selection-query) ?/ ) {\n         # no conversion\n      }\n      elsif( $line =~ m/^(selection-set) (.*?) (\\[?)(.*?)(\\]?)$/ ) {\n         $line = \"$1 $2 $3\" . join( \"\", map sprintf(\"%02x\", $_), unpack \"C*\", eval($4) ) . \"$5\";\n      }\n      else {\n         warn \"Unrecognised test expectation '$line'\\n\";\n      }\n\n      push @expect, $line;\n   }\n   # ?screen_row assertion is emulated here\n   elsif( $line =~ s/^\\?screen_row\\s+(\\d+)\\s*=\\s*// ) {\n      my $row = $1;\n      my $want;\n\n      if( $line =~ m/^\"/ ) {\n         $want = eval($line);\n      }\n      else {\n         # Turn 0xDD,0xDD,... directly into bytes\n         $want = pack \"C*\", map { hex } split m/,/, $line;\n      }\n\n      do_onetest if defined $command;\n\n      $hin->print( \"\\?screen_chars $row\\n\" );\n      my $response = <$hout>;\n      chomp $response;\n\n      $response = pack \"C*\", map { hex } split m/,/, $response;\n      if( $response ne $want ) {\n         print \"# line $linenum: Assert ?screen_row $row failed:\\n\" .\n               \"# Expected: $want\\n\" .\n               \"# Actual:   $response\\n\";\n         $exitcode = 1;\n         exit $exitcode if $exitcode and $FAIL_EARLY;\n      }\n   }\n   # Assertions start with '?'\n   elsif( $line =~ s/^\\?([a-z]+.*?=)\\s*// ) {\n      do_onetest if defined $command;\n\n      my ( $assertion ) = $1 =~ m/^(.*)\\s+=/;\n      my $expectation = $line;\n\n      $hin->print( \"\\?$assertion\\n\" );\n      my $response = <$hout>; defined $response or wait, die \"Test harness failed - $?\\n\";\n      chomp $response; $response =~ s/^\\s+|\\s+$//g;\n\n      # Some convenience formatting\n      if( $assertion =~ m/^screen_chars/ and $expectation =~ m/^\"/ ) {\n         $expectation = join \",\", map sprintf(\"0x%02x\", ord $_), split m//, eval($expectation);\n      }\n\n      if( $response ne $expectation ) {\n         print \"# line $linenum: Assert $assertion failed:\\n\" .\n               \"# Expected: $expectation\\n\" .\n               \"# Actual:   $response\\n\";\n         $exitcode = 1;\n         exit $exitcode if $exitcode and $FAIL_EARLY;\n      }\n   }\n   # Test controls start with '$'\n   elsif( $line =~ s/\\$SEQ\\s+(\\d+)\\s+(\\d+):\\s*// ) {\n      my ( $low, $high ) = ( $1, $2 );\n      foreach my $val ( $low .. $high ) {\n         ( my $inner = $line ) =~ s/\\\\#/$val/g;\n         do_line( $inner );\n      }\n   }\n   elsif( $line =~ s/\\$REP\\s+(\\d+):\\s*// ) {\n      my $count = $1;\n      do_line( $line ) for 1 .. $count;\n   }\n   else {\n      die \"Unrecognised TEST line $line\\n\";\n   }\n}\n\nopen my $test, \"<\", $ARGV[0] or die \"Cannot open test script $ARGV[0] - $!\";\n\nwhile( my $line = <$test> ) {\n   $linenum++;\n   $line =~ s/^\\s+//;\n   chomp $line;\n\n   next if $line =~ m/^(?:#|$)/;\n   last if $line eq \"__END__\";\n\n   do_line( $line );\n}\n\ndo_onetest if defined $command;\n\nclose $hin;\nclose $hout;\n\nwaitpid $hpid, 0;\nif( $? ) {\n   printf STDERR \"Harness exited %d\\n\", WEXITSTATUS($?)   if WIFEXITED($?);\n   printf STDERR \"Harness exit signal %d\\n\", WTERMSIG($?) if WIFSIGNALED($?);\n   $exitcode = WIFEXITED($?) ? WEXITSTATUS($?) : 125;\n}\n\nexit $exitcode;\n"
  },
  {
    "path": "tbl2inc_c.pl",
    "content": "#!/usr/bin/perl\n\nuse strict;\nuse warnings;\n\nmy ( $encname ) = $ARGV[0] =~ m{/([^/.]+).tbl}\n   or die \"Cannot parse encoding name out of $ARGV[0]\\n\";\n\nprint <<\"EOF\";\nstatic const struct StaticTableEncoding encoding_$encname = {\n  { .decode = &decode_table },\n  {\nEOF\n\nwhile( <> ) {\n   s/\\s*#.*//; # strip comment\n\n   s{^(\\d+)/(\\d+)}{sprintf \"[0x%02x]\", $1*16 + $2}e; # Convert 3/1 to [0x31]\n   s{\"(.)\"}{sprintf \"0x%04x\", ord $1}e;              # Convert \"A\" to 0x41\n   s{U\\+}{0x};                                       # Convert U+0041 to 0x0041\n\n   s{$}{,}; # append comma\n\n   print \"    $_\";\n}\n\nprint <<\"EOF\";\n  }\n};\nEOF\n"
  },
  {
    "path": "vterm.pc.in",
    "content": "libdir=@LIBDIR@\nincludedir=@INCDIR@\n\nName: vterm\nDescription: Abstract VT220/Xterm/ECMA-48 emulation library\nVersion: @VERSION@\nLibs: -L${libdir} -lvterm\nCflags: -I${includedir}\n"
  }
]