Full Code of neovim/libvterm for AI

nvim 934bc2fbf218 cached
80 files
344.2 KB
121.3k tokens
284 symbols
1 requests
Download .txt
Showing preview only (365K chars total). Download the full file or copy to clipboard to get everything.
Repository: neovim/libvterm
Branch: nvim
Commit: 934bc2fbf218
Files: 80
Total size: 344.2 KB

Directory structure:
gitextract_h34518pg/

├── .bzrignore
├── .github/
│   ├── dependabot.yml
│   └── workflows/
│       └── sync.yml
├── CODE-MAP
├── CONTRIBUTING
├── LICENSE
├── Makefile
├── README.md
├── bin/
│   ├── unterm.c
│   ├── vterm-ctrl.c
│   └── vterm-dump.c
├── doc/
│   ├── URLs
│   └── seqs.txt
├── find-wide-chars.pl
├── include/
│   ├── vterm.h
│   └── vterm_keycodes.h
├── src/
│   ├── encoding/
│   │   ├── DECdrawing.inc
│   │   ├── DECdrawing.tbl
│   │   ├── uk.inc
│   │   └── uk.tbl
│   ├── encoding.c
│   ├── fullwidth.inc
│   ├── keyboard.c
│   ├── mouse.c
│   ├── parser.c
│   ├── pen.c
│   ├── rect.h
│   ├── screen.c
│   ├── state.c
│   ├── unicode.c
│   ├── utf8.h
│   ├── vterm.c
│   └── vterm_internal.h
├── t/
│   ├── 02parser.test
│   ├── 03encoding_utf8.test
│   ├── 10state_putglyph.test
│   ├── 11state_movecursor.test
│   ├── 12state_scroll.test
│   ├── 13state_edit.test
│   ├── 14state_encoding.test
│   ├── 15state_mode.test
│   ├── 16state_resize.test
│   ├── 17state_mouse.test
│   ├── 18state_termprops.test
│   ├── 20state_wrapping.test
│   ├── 21state_tabstops.test
│   ├── 22state_save.test
│   ├── 25state_input.test
│   ├── 26state_query.test
│   ├── 27state_reset.test
│   ├── 28state_dbl_wh.test
│   ├── 29state_fallback.test
│   ├── 30state_pen.test
│   ├── 31state_rep.test
│   ├── 32state_flow.test
│   ├── 40state_selection.test
│   ├── 60screen_ascii.test
│   ├── 61screen_unicode.test
│   ├── 62screen_damage.test
│   ├── 63screen_resize.test
│   ├── 64screen_pen.test
│   ├── 65screen_protect.test
│   ├── 66screen_extent.test
│   ├── 67screen_dbl_wh.test
│   ├── 68screen_termprops.test
│   ├── 69screen_pushline.test
│   ├── 69screen_reflow.test
│   ├── 90vttest_01-movement-1.test
│   ├── 90vttest_01-movement-2.test
│   ├── 90vttest_01-movement-3.test
│   ├── 90vttest_01-movement-4.test
│   ├── 90vttest_02-screen-1.test
│   ├── 90vttest_02-screen-2.test
│   ├── 90vttest_02-screen-3.test
│   ├── 90vttest_02-screen-4.test
│   ├── 92lp1640917.test
│   ├── harness.c
│   └── run-test.pl
├── tbl2inc_c.pl
└── vterm.pc.in

================================================
FILE CONTENTS
================================================

================================================
FILE: .bzrignore
================================================
.libs
*.lo
*.la

bin/*
!bin/*.c

pangoterm
t/test
t/suites.h
t/externs.h
t/harness
src/encoding/*.inc


================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "daily"
    commit-message:
      prefix: "ci"


================================================
FILE: .github/workflows/sync.yml
================================================
name: sync
on:
  schedule:
    - cron: '30 1 * * *' # Run every day at 01:30
  workflow_dispatch:

permissions:
  contents: write

jobs:
  mirror:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
        with:
          fetch-depth: 0
          ref: mirror
      - run: sudo apt-get install brz

      - name: Set up git config
        run: |
          git config --global user.name 'marvim'
          git config --global user.email 'marvim@users.noreply.github.com'

      - run: git pull bzr::http://bazaar.leonerd.org.uk/c/libvterm --tags --rebase
      - run: |
          git push --force-with-lease
          git push --tags

  nvim:
    runs-on: ubuntu-latest
    needs: mirror
    steps:
      - uses: actions/checkout@v6
        with:
          fetch-depth: 0

      - name: Set up git config
        run: |
          git config --global user.name 'marvim'
          git config --global user.email 'marvim@users.noreply.github.com'

      - run: git merge origin/mirror -m "Merge mirror"
      - run: git push


================================================
FILE: CODE-MAP
================================================
bin/
 - contains some standalone programs

bin/unterm.c
 - an example program using libvterm to reconstruct the final state of a
   terminal after replaying captured input

bin/vterm-ctrl.c
 - a helper program that emits sequences to control a running libvterm
   terminal

bin/vterm-dump.c
 - an example program using the parser layer of libvterm to interpret captured
   input

CODE-MAP
 - high-level list and description of files in the repository

CONTRIBUTING
 - documentation explaining how developers can contribute fixes and features

doc/
 - contains documentation

doc/seqs.txt
 - documents the sequences recognised by the library

include/vterm.h
 - main include file

include/vterm_keycodes.h
 - include file containing the keyboard input keycode enumerations

LICENSE
 - legalese

Makefile
 - main build file

src/
 - contains the source code for the library

src/encoding.c
 - handles mapping ISO/IEC 2022 alternate character sets into Unicode
   codepoints

src/keyboard.c
 - handles sending reported keyboard events to the output stream

src/mouse.c
 - handles sending reported mouse events to the output stream

src/parser.c
 - parses bytes from the input stream into parser-level events

src/pen.c
 - interprets SGR sequences and maintains current rendering attributes

src/screen.c
 - uses state-level events to maintain a buffer of current screen contents

src/state.c
 - follows parser-level events to keep track of the overall terminal state

src/unicode.c
 - utility functions for Unicode and UTF-8 handling

src/vterm.c
 - toplevel object state and miscellaneous functions

src/vterm_internal.h
 - include file for definitions private to the library's internals

t/
 - contains unit tests

t/harness.c
 - standalone program to embed the library into for unit-test purposes

t/run-test.pl
 - invokes the test harness to run a single unit test script


================================================
FILE: CONTRIBUTING
================================================
How to Contribute
-----------------

The main resources for this library are:

  Launchpad
    https://launchpad.net/libvterm

  IRC:
    ##tty or #tickit on irc.libera.chat

  Email:
    Paul "LeoNerd" Evans <leonerd@leonerd.org.uk>


Bug reports and feature requests can be sent to any of the above resources.

New features, bug patches, etc.. should in the first instance be discussed via
any of the resources listed above, before starting work on the actual code.
There may be future plans or development already in-progress that could be
affected so it is better to discuss the ideas first before starting work
actually writing any code.


================================================
FILE: LICENSE
================================================


The MIT License

Copyright (c) 2008 Paul Evans <leonerd@leonerd.org.uk>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.


================================================
FILE: Makefile
================================================
ifeq ($(shell uname),Darwin)
  LIBTOOL ?= glibtool
else
  LIBTOOL ?= libtool
endif

ifneq ($(VERBOSE),1)
  LIBTOOL +=--quiet
endif

override CFLAGS +=-Wall -Iinclude -std=c99 -Wpedantic

ifeq ($(shell uname),SunOS)
  override CFLAGS +=-D__EXTENSIONS__ -D_XPG6 -D__XOPEN_OR_POSIX
endif

ifeq ($(DEBUG),1)
  override CFLAGS +=-ggdb -DDEBUG
endif

ifeq ($(PROFILE),1)
  override CFLAGS +=-pg
  override LDFLAGS+=-pg
endif

CFILES=$(sort $(wildcard src/*.c))
HFILES=$(sort $(wildcard include/*.h))
OBJECTS=$(CFILES:.c=.lo)
LIBRARY=libvterm.la

BINFILES_SRC=$(sort $(wildcard bin/*.c))
BINFILES=$(BINFILES_SRC:.c=)

TBLFILES=$(sort $(wildcard src/encoding/*.tbl))
INCFILES=$(TBLFILES:.tbl=.inc)

HFILES_INT=$(sort $(wildcard src/*.h)) $(HFILES)

VERSION_CURRENT=0
VERSION_REVISION=0
VERSION_AGE=0

VERSION=0.3.3

PREFIX=/usr/local
BINDIR=$(PREFIX)/bin
LIBDIR=$(PREFIX)/lib
INCDIR=$(PREFIX)/include
MANDIR=$(PREFIX)/share/man
MAN3DIR=$(MANDIR)/man3

all: $(LIBRARY) $(BINFILES)

$(LIBRARY): $(OBJECTS)
	@echo LINK $@
	@$(LIBTOOL) --mode=link --tag=CC $(CC) -rpath $(LIBDIR) -version-info $(VERSION_CURRENT):$(VERSION_REVISION):$(VERSION_AGE) -o $@ $^ $(LDFLAGS)

src/%.lo: src/%.c $(HFILES_INT)
	@echo CC $<
	@$(LIBTOOL) --mode=compile --tag=CC $(CC) $(CFLAGS) -o $@ -c $<

src/encoding/%.inc: src/encoding/%.tbl
	@echo TBL $<
	@perl -CSD tbl2inc_c.pl $< >$@

src/fullwidth.inc:
	@perl find-wide-chars.pl >$@

src/encoding.lo: $(INCFILES)

bin/%: bin/%.c $(LIBRARY)
	@echo CC $<
	@$(LIBTOOL) --mode=link --tag=CC $(CC) $(CFLAGS) -o $@ $< -lvterm $(LDFLAGS)

t/harness.lo: t/harness.c $(HFILES)
	@echo CC $<
	@$(LIBTOOL) --mode=compile --tag=CC $(CC) $(CFLAGS) -o $@ -c $<

t/harness: t/harness.lo $(LIBRARY)
	@echo LINK $@
	@$(LIBTOOL) --mode=link --tag=CC $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)

.PHONY: test
test: $(LIBRARY) t/harness
	for T in `ls t/[0-9]*.test`; do echo "** $$T **"; perl t/run-test.pl $$T $(if $(VALGRIND),--valgrind) || exit 1; done

.PHONY: clean
clean:
	$(LIBTOOL) --mode=clean rm -f $(OBJECTS) $(INCFILES)
	$(LIBTOOL) --mode=clean rm -f t/harness.lo t/harness
	$(LIBTOOL) --mode=clean rm -f $(LIBRARY) $(BINFILES)

.PHONY: install
install: install-inc install-lib install-bin

install-inc:
	install -d $(DESTDIR)$(INCDIR)
	install -m644 $(HFILES) $(DESTDIR)$(INCDIR)
	install -d $(DESTDIR)$(LIBDIR)/pkgconfig
	sed -e "s,@INCDIR@,$(INCDIR)," -e "s,@LIBDIR@,$(LIBDIR)," -e "s,@VERSION@,$(VERSION)," <vterm.pc.in >$(DESTDIR)$(LIBDIR)/pkgconfig/vterm.pc

install-lib: $(LIBRARY)
	install -d $(DESTDIR)$(LIBDIR)
	$(LIBTOOL) --mode=install install $(LIBRARY) $(DESTDIR)$(LIBDIR)/$(LIBRARY)
	$(LIBTOOL) --mode=finish $(DESTDIR)$(LIBDIR)

install-bin: $(BINFILES)
	install -d $(DESTDIR)$(BINDIR)
	$(LIBTOOL) --mode=install install $(BINFILES) $(DESTDIR)$(BINDIR)/

# DIST CUT

DISTDIR=libvterm-$(VERSION)

distdir: $(INCFILES)
	mkdir __distdir
	cp LICENSE CONTRIBUTING __distdir
	mkdir __distdir/src
	cp src/*.c src/*.h src/*.inc __distdir/src
	mkdir __distdir/src/encoding
	cp src/encoding/*.inc __distdir/src/encoding
	mkdir __distdir/include
	cp include/*.h __distdir/include
	mkdir __distdir/bin
	cp bin/*.c __distdir/bin
	mkdir __distdir/t
	cp t/*.test t/harness.c t/run-test.pl __distdir/t
	sed "s,@VERSION@,$(VERSION)," <vterm.pc.in >__distdir/vterm.pc.in
	sed "/^# DIST CUT/Q" <Makefile >__distdir/Makefile
	mv __distdir $(DISTDIR)

TARBALL=$(DISTDIR).tar.gz

dist: distdir
	tar -czf $(TARBALL) $(DISTDIR)
	rm -rf $(DISTDIR)

dist+bzr:
	$(MAKE) dist VERSION=$(VERSION)+bzr`bzr revno`

distdir+bzr:
	$(MAKE) distdir VERSION=$(VERSION)+bzr`bzr revno`


================================================
FILE: README.md
================================================
# libvterm fork

**Important**: This is currently not used by neovim. Vterm has instead been
bundled into the neovim repo itself:
https://github.com/neovim/neovim/tree/master/src/nvim/vterm. All pull requests
should be sent to neovim instead.

This is a fork of https://www.leonerd.org.uk/code/libvterm. An exact mirror
of upstream is in the branch `mirror`.


================================================
FILE: bin/unterm.c
================================================
#include <stdio.h>
#include <string.h>

#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <unistd.h>

#include "vterm.h"

#include "../src/utf8.h" // fill_utf8

/*
 * unterm [OPTIONS] SCRIPTFILE
 *
 * Interprets terminal sequences in SCRIPTFILE and outputs the final state of
 * the terminal buffer at the end.
 *
 * OPTIONS:
 *   -f FORMAT  -- set the output format: ["plain" | "sgr"]
 *   -l LINES,
 *   -c COLS    -- set the size of the emulated terminal
 */

#define streq(a,b) (!strcmp(a,b))

static VTerm *vt;
static VTermScreen *vts;

static int cols;
static int rows;

static enum {
  FORMAT_PLAIN,
  FORMAT_SGR,
} format = FORMAT_PLAIN;

static int dump_cell_color(const VTermColor *col, int sgri, int sgr[], int fg)
{
    /* Reset the color if the given color is the default color */
    if (fg && VTERM_COLOR_IS_DEFAULT_FG(col)) {
        sgr[sgri++] = 39;
        return sgri;
    }
    if (!fg && VTERM_COLOR_IS_DEFAULT_BG(col)) {
        sgr[sgri++] = 49;
        return sgri;
    }

    /* Decide whether to send an indexed color or an RGB color */
    if (VTERM_COLOR_IS_INDEXED(col)) {
        const uint8_t idx = col->indexed.idx;
        if (idx < 8) {
            sgr[sgri++] = (idx + (fg ? 30 : 40));
        }
        else if (idx < 16) {
            sgr[sgri++] = (idx - 8 + (fg ? 90 : 100));
        }
        else {
            sgr[sgri++] = (fg ? 38 : 48);
            sgr[sgri++] = 5;
            sgr[sgri++] = idx;
        }
    }
    else if (VTERM_COLOR_IS_RGB(col)) {
        sgr[sgri++] = (fg ? 38 : 48);
        sgr[sgri++] = 2;
        sgr[sgri++] = col->rgb.red;
        sgr[sgri++] = col->rgb.green;
        sgr[sgri++] = col->rgb.blue;
    }
    return sgri;
}

static void dump_cell(const VTermScreenCell *cell, const VTermScreenCell *prevcell)
{
  switch(format) {
    case FORMAT_PLAIN:
      break;
    case FORMAT_SGR:
      {
        // If all 7 attributes change, that means 7 SGRs max
        // Each colour could consume up to 5 entries
        int sgr[7 + 2*5]; int sgri = 0;

        if(!prevcell->attrs.bold && cell->attrs.bold)
          sgr[sgri++] = 1;
        if(prevcell->attrs.bold && !cell->attrs.bold)
          sgr[sgri++] = 22;

        if(!prevcell->attrs.underline && cell->attrs.underline)
          sgr[sgri++] = 4;
        if(prevcell->attrs.underline && !cell->attrs.underline)
          sgr[sgri++] = 24;

        if(!prevcell->attrs.italic && cell->attrs.italic)
          sgr[sgri++] = 3;
        if(prevcell->attrs.italic && !cell->attrs.italic)
          sgr[sgri++] = 23;

        if(!prevcell->attrs.blink && cell->attrs.blink)
          sgr[sgri++] = 5;
        if(prevcell->attrs.blink && !cell->attrs.blink)
          sgr[sgri++] = 25;

        if(!prevcell->attrs.reverse && cell->attrs.reverse)
          sgr[sgri++] = 7;
        if(prevcell->attrs.reverse && !cell->attrs.reverse)
          sgr[sgri++] = 27;

        if(!prevcell->attrs.conceal && cell->attrs.conceal)
          sgr[sgri++] = 8;
        if(prevcell->attrs.conceal && !cell->attrs.conceal)
          sgr[sgri++] = 28;

        if(!prevcell->attrs.strike && cell->attrs.strike)
          sgr[sgri++] = 9;
        if(prevcell->attrs.strike && !cell->attrs.strike)
          sgr[sgri++] = 29;

        if(!prevcell->attrs.font && cell->attrs.font)
          sgr[sgri++] = 10 + cell->attrs.font;
        if(prevcell->attrs.font && !cell->attrs.font)
          sgr[sgri++] = 10;

        if(!vterm_color_is_equal(&prevcell->fg, &cell->fg)) {
          sgri = dump_cell_color(&cell->fg, sgri, sgr, 1);
        }

        if(!vterm_color_is_equal(&prevcell->bg, &cell->bg)) {
          sgri = dump_cell_color(&cell->bg, sgri, sgr, 0);
        }

        if(!sgri)
          break;

        printf("\x1b[");
        for(int i = 0; i < sgri; i++)
          printf(!i               ? "%d" :
              CSI_ARG_HAS_MORE(sgr[i]) ? ":%d" :
              ";%d",
              CSI_ARG(sgr[i]));
        printf("m");
      }
      break;
  }

  for(int i = 0; i < VTERM_MAX_CHARS_PER_CELL && cell->chars[i]; i++) {
    char bytes[6];
    bytes[fill_utf8(cell->chars[i], bytes)] = 0;
    printf("%s", bytes);
  }
}

static void dump_eol(const VTermScreenCell *prevcell)
{
  switch(format) {
    case FORMAT_PLAIN:
      break;
    case FORMAT_SGR:
      if(prevcell->attrs.bold || prevcell->attrs.underline || prevcell->attrs.italic ||
         prevcell->attrs.blink || prevcell->attrs.reverse || prevcell->attrs.strike ||
         prevcell->attrs.conceal || prevcell->attrs.font)
        printf("\x1b[m");
      break;
  }

  printf("\n");
}

void dump_row(int row)
{
  VTermPos pos = { .row = row, .col = 0 };
  VTermScreenCell prevcell = { 0 };
  vterm_state_get_default_colors(vterm_obtain_state(vt), &prevcell.fg, &prevcell.bg);

  while(pos.col < cols) {
    VTermScreenCell cell;
    vterm_screen_get_cell(vts, pos, &cell);

    dump_cell(&cell, &prevcell);

    pos.col += cell.width;
    prevcell = cell;
  }

  dump_eol(&prevcell);
}

static int screen_sb_pushline(int cols, const VTermScreenCell *cells, void *user)
{
  VTermScreenCell prevcell = { 0 };
  vterm_state_get_default_colors(vterm_obtain_state(vt), &prevcell.fg, &prevcell.bg);

  for(int col = 0; col < cols; col++) {
    dump_cell(cells + col, &prevcell);
    prevcell = cells[col];
  }

  dump_eol(&prevcell);

  return 1;
}

static int screen_resize(int new_rows, int new_cols, void *user)
{
  rows = new_rows;
  cols = new_cols;
  return 1;
}

static VTermScreenCallbacks cb_screen = {
  .sb_pushline = &screen_sb_pushline,
  .resize      = &screen_resize,
};

int main(int argc, char *argv[])
{
  rows = 25;
  cols = 80;

  int opt;
  while((opt = getopt(argc, argv, "f:l:c:")) != -1) {
    switch(opt) {
      case 'f':
        if(streq(optarg, "plain"))
          format = FORMAT_PLAIN;
        else if(streq(optarg, "sgr"))
          format = FORMAT_SGR;
        else {
          fprintf(stderr, "Unrecognised format '%s'\n", optarg);
          exit(1);
        }
        break;

      case 'l':
        rows = atoi(optarg);
        if(!rows)
          rows = 25;
        break;

      case 'c':
        cols = atoi(optarg);
        if(!cols)
          cols = 80;
        break;
    }
  }

  const char *file = argv[optind++];
  int fd = open(file, O_RDONLY);
  if(fd == -1) {
    fprintf(stderr, "Cannot open %s - %s\n", file, strerror(errno));
    exit(1);
  }

  vt = vterm_new(rows, cols);
  vterm_set_utf8(vt, true);

  vts = vterm_obtain_screen(vt);
  vterm_screen_set_callbacks(vts, &cb_screen, NULL);

  vterm_screen_reset(vts, 1);

  int len;
  char buffer[1024];
  while((len = read(fd, buffer, sizeof(buffer))) > 0) {
    vterm_input_write(vt, buffer, len);
  }

  for(int row = 0; row < rows; row++) {
    dump_row(row);
  }

  close(fd);

  vterm_free(vt);

  return 0;
}


================================================
FILE: bin/vterm-ctrl.c
================================================
#define _XOPEN_SOURCE 600  /* strdup */

#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define streq(a,b) (strcmp(a,b)==0)

#include <termios.h>

static char *getvalue(int *argip, int argc, char *argv[])
{
  if(*argip >= argc) {
    fprintf(stderr, "Expected an option value\n");
    exit(1);
  }

  return argv[(*argip)++];
}

static int getchoice(int *argip, int argc, char *argv[], const char *options[])
{
  const char *arg = getvalue(argip, argc, argv);

  int value = -1;
  while(options[++value])
    if(streq(arg, options[value]))
      return value;

  fprintf(stderr, "Unrecognised option value %s\n", arg);
  exit(1);
}

typedef enum {
  OFF,
  ON,
  QUERY,
} BoolQuery;

static BoolQuery getboolq(int *argip, int argc, char *argv[])
{
  return getchoice(argip, argc, argv, (const char *[]){"off", "on", "query", NULL});
}

static char *helptext[] = {
  "reset",
  "s8c1t [off|on]",
  "keypad [app|num]",
  "screen [off|on|query]",
  "cursor [off|on|query]",
  "curblink [off|on|query]",
  "curshape [block|under|bar|query]",
  "mouse [off|click|clickdrag|motion]",
  "reportfocus [off|on|query]",
  "altscreen [off|on|query]",
  "bracketpaste [off|on|query]",
  "icontitle [STR]",
  "icon [STR]",
  "title [STR]",
  NULL
};

static bool seticanon(bool icanon, bool echo)
{
  struct termios termios;

  tcgetattr(0, &termios);

  bool ret = (termios.c_lflag & ICANON);

  if(icanon) termios.c_lflag |=  ICANON;
  else       termios.c_lflag &= ~ICANON;

  if(echo) termios.c_lflag |=  ECHO;
  else     termios.c_lflag &= ~ECHO;

  tcsetattr(0, TCSANOW, &termios);

  return ret;
}

static void await_c1(unsigned char c1)
{
  unsigned char c;

  /* await CSI - 8bit or 2byte 7bit form */
  bool in_esc = false;
  while((c = getchar())) {
    if(c == c1)
      break;
    if(in_esc && c == (char)(c1 - 0x40))
      break;
    if(!in_esc && c == 0x1b)
      in_esc = true;
    else
      in_esc = false;
  }
}

static char *read_csi(void)
{
  await_c1(0x9B); // CSI

  /* TODO: This really should be a more robust CSI parser
   */
  char csi[32];
  int i = 0;
  for(; i < sizeof(csi)-1; i++) {
    char c = csi[i] = getchar();
    if(c >= 0x40 && c <= 0x7e)
      break;
  }
  csi[++i] = 0;

  // TODO: returns longer than 32?

  return strdup(csi);
}

static char *read_dcs(void)
{
  await_c1(0x90);

  char dcs[32];
  bool in_esc = false;
  int i = 0;
  for(; i < sizeof(dcs)-1; ) {
    unsigned char c = getchar();
    if(c == 0x9c) // ST
      break;
    if(in_esc && c == 0x5c)
      break;
    if(!in_esc && c == 0x1b)
      in_esc = true;
    else {
      dcs[i++] = c;
      in_esc = false;
    }
  }
  dcs[++i] = 0;

  return strdup(dcs);
}

static void usage(int exitcode)
{
  fprintf(stderr, "Control a libvterm-based terminal\n"
      "\n"
      "Options:\n");

  for(char **p = helptext; *p; p++)
    fprintf(stderr, "  %s\n", *p);

  exit(exitcode);
}

static bool query_dec_mode(int mode)
{
  printf("\x1b[?%d$p", mode);

  char *s = NULL;
  do {
    if(s)
      free(s);
    s = read_csi();

    /* expect "?" mode ";" value "$y" */

    int reply_mode, reply_value;
    char reply_cmd;
    /* If the sscanf format string ends in a literal, we can't tell from
     * its return value if it matches. Hence we'll %c the cmd and check it
     * explicitly
     */
    if(sscanf(s, "?%d;%d$%c", &reply_mode, &reply_value, &reply_cmd) < 3)
      continue;
    if(reply_cmd != 'y')
      continue;

    if(reply_mode != mode)
      continue;

    free(s);

    if(reply_value == 1 || reply_value == 3)
      return true;
    if(reply_value == 2 || reply_value == 4)
      return false;

    printf("Unrecognised reply to DECRQM: %d\n", reply_value);
    return false;
  } while(1);
}

static void do_dec_mode(int mode, BoolQuery val, const char *name)
{
  switch(val) {
    case OFF:
    case ON:
      printf("\x1b[?%d%c", mode, val == ON ? 'h' : 'l');
      break;

    case QUERY:
      if(query_dec_mode(mode))
        printf("%s on\n", name);
      else
        printf("%s off\n", name);
      break;
  }
}

static int query_rqss_numeric(char *cmd)
{
  printf("\x1bP$q%s\x1b\\", cmd);

  char *s = NULL;
  do {
    if(s)
      free(s);
    s = read_dcs();

    if(!s)
      return -1;
    if(strlen(s) < strlen(cmd))
      return -1;
    if(strcmp(s + strlen(s) - strlen(cmd), cmd) != 0) {
      printf("No match\n");
      continue;
    }

    if(s[0] != '1' || s[1] != '$' || s[2] != 'r')
      return -1;

    int num;
    if(sscanf(s + 3, "%d", &num) != 1)
      return -1;

    return num;
  } while(1);
}

bool wasicanon;

void restoreicanon(void)
{
  seticanon(wasicanon, true);
}

int main(int argc, char *argv[])
{
  int argi = 1;

  if(argc == 1)
    usage(0);

  wasicanon = seticanon(false, false);
  atexit(restoreicanon);

  while(argi < argc) {
    const char *arg = argv[argi++];

    if(streq(arg, "reset")) {
      printf("\x1b" "c");
    }
    else if(streq(arg, "s8c1t")) {
      switch(getchoice(&argi, argc, argv, (const char *[]){"off", "on", NULL})) {
      case 0:
        printf("\x1b F"); break;
      case 1:
        printf("\x1b G"); break;
      }
    }
    else if(streq(arg, "keypad")) {
      switch(getchoice(&argi, argc, argv, (const char *[]){"app", "num", NULL})) {
      case 0:
        printf("\x1b="); break;
      case 1:
        printf("\x1b>"); break;
      }
    }
    else if(streq(arg, "screen")) {
      do_dec_mode(5, getboolq(&argi, argc, argv), "screen");
    }
    else if(streq(arg, "cursor")) {
      do_dec_mode(25, getboolq(&argi, argc, argv), "cursor");
    }
    else if(streq(arg, "curblink")) {
      do_dec_mode(12, getboolq(&argi, argc, argv), "curblink");
    }
    else if(streq(arg, "curshape")) {
      // TODO: This ought to query the current value of DECSCUSR because it
      //   may need blinking on or off
      int shape = getchoice(&argi, argc, argv, (const char *[]){"block", "under", "bar", "query", NULL});
      switch(shape) {
        case 3: // query
          shape = query_rqss_numeric(" q");
          switch(shape) {
            case 1: case 2:
              printf("curshape block\n");
              break;
            case 3: case 4:
              printf("curshape under\n");
              break;
            case 5: case 6:
              printf("curshape bar\n");
              break;
          }
          break;

        case 0:
        case 1:
        case 2:
          printf("\x1b[%d q", 1 + (shape * 2));
          break;
      }
    }
    else if(streq(arg, "mouse")) {
      switch(getchoice(&argi, argc, argv, (const char *[]){"off", "click", "clickdrag", "motion", NULL})) {
      case 0:
        printf("\x1b[?1000l"); break;
      case 1:
        printf("\x1b[?1000h"); break;
      case 2:
        printf("\x1b[?1002h"); break;
      case 3:
        printf("\x1b[?1003h"); break;
      }
    }
    else if(streq(arg, "reportfocus")) {
      do_dec_mode(1004, getboolq(&argi, argc, argv), "reportfocus");
    }
    else if(streq(arg, "altscreen")) {
      do_dec_mode(1049, getboolq(&argi, argc, argv), "altscreen");
    }
    else if(streq(arg, "bracketpaste")) {
      do_dec_mode(2004, getboolq(&argi, argc, argv), "bracketpaste");
    }
    else if(streq(arg, "icontitle")) {
      printf("\x1b]0;%s\a", getvalue(&argi, argc, argv));
    }
    else if(streq(arg, "icon")) {
      printf("\x1b]1;%s\a", getvalue(&argi, argc, argv));
    }
    else if(streq(arg, "title")) {
      printf("\x1b]2;%s\a", getvalue(&argi, argc, argv));
    }
    else {
      fprintf(stderr, "Unrecognised command %s\n", arg);
      exit(1);
    }
  }

  return 0;
}


================================================
FILE: bin/vterm-dump.c
================================================
// Require getopt(3)
#define _XOPEN_SOURCE

#include <stdio.h>
#include <string.h>
#define streq(a,b) (strcmp(a,b)==0)

#include <errno.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

#include "vterm.h"

static const char *special_begin = "{";
static const char *special_end   = "}";

static int parser_text(const char bytes[], size_t len, void *user)
{
  unsigned char *b = (unsigned char *)bytes;

  int i;
  for(i = 0; i < len; /* none */) {
    if(b[i] < 0x20)        // C0
      break;
    else if(b[i] < 0x80)   // ASCII
      i++;
    else if(b[i] < 0xa0)   // C1
      break;
    else if(b[i] < 0xc0)   // UTF-8 continuation
      break;
    else if(b[i] < 0xe0) { // UTF-8 2-byte
      // 2-byte UTF-8
      if(len < i+2) break;
      i += 2;
    }
    else if(b[i] < 0xf0) { // UTF-8 3-byte
      if(len < i+3) break;
      i += 3;
    }
    else if(b[i] < 0xf8) { // UTF-8 4-byte
      if(len < i+4) break;
      i += 4;
    }
    else                   // otherwise invalid
      break;
  }

  printf("%.*s", i, b);
  return i;
}

/* 0     1      2      3       4     5      6      7      8      9      A      B      C      D      E      F    */
static const char *name_c0[] = {
  "NUL", "SOH", "STX", "ETX", "EOT", "ENQ", "ACK", "BEL", "BS",  "HT",  "LF",  "VT",  "FF",  "CR",  "LS0", "LS1",
  "DLE", "DC1", "DC2", "DC3", "DC4", "NAK", "SYN", "ETB", "CAN", "EM",  "SUB", "ESC", "FS",  "GS",  "RS",  "US",
};
static const char *name_c1[] = {
  NULL,  NULL,  "BPH", "NBH", NULL,  "NEL", "SSA", "ESA", "HTS", "HTJ", "VTS", "PLD", "PLU", "RI",  "SS2", "SS3",
  "DCS", "PU1", "PU2", "STS", "CCH", "MW",  "SPA", "EPA", "SOS", NULL,  "SCI", "CSI", "ST",  "OSC", "PM",  "APC",
};

static int parser_control(unsigned char control, void *user)
{
  if(control < 0x20)
    printf("%s%s%s", special_begin, name_c0[control], special_end);
  else if(control == 0x7f)
    printf("%s%s%s", special_begin, "DEL", special_end);
  else if(control >= 0x80 && control < 0xa0 && name_c1[control - 0x80])
    printf("%s%s%s", special_begin, name_c1[control - 0x80], special_end);
  else
    printf("%sCONTROL 0x%02x%s", special_begin, control, special_end);

  if(control == 0x0a)
    printf("\n");
  return 1;
}

static int parser_escape(const char bytes[], size_t len, void *user)
{
  if(bytes[0] >= 0x20 && bytes[0] < 0x30) {
    if(len < 2)
      return -1;
    len = 2;
  }
  else {
    len = 1;
  }

  printf("%sESC %.*s%s", special_begin, (int)len, bytes, special_end);

  return len;
}

/* 0     1      2      3       4     5      6      7      8      9      A      B      C      D      E      F    */
static const char *name_csi_plain[] = {
  "ICH", "CUU", "CUD", "CUF", "CUB", "CNL", "CPL", "CHA", "CUP", "CHT", "ED",  "EL",  "IL",  "DL",  "EF",  "EA",
  "DCH", "SSE", "CPR", "SU",  "SD",  "NP",  "PP",  "CTC", "ECH", "CVT", "CBT", "SRS", "PTX", "SDS", "SIMD",NULL,
  "HPA", "HPR", "REP", "DA",  "VPA", "VPR", "HVP", "TBC", "SM",  "MC",  "HPB", "VPB", "RM",  "SGR", "DSR", "DAQ",
};

/*0           4           8           B         */
static const int newline_csi_plain[] = {
  0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0,
};

static int parser_csi(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user)
{
  const char *name = NULL;
  if(!leader && !intermed && command < 0x70)
    name = name_csi_plain[command - 0x40];
  else if(leader && streq(leader, "?") && !intermed) {
    /* DEC */
    switch(command) {
      case 'h': name = "DECSM"; break;
      case 'l': name = "DECRM"; break;
    }
    if(name)
      leader = NULL;
  }

  if(!leader && !intermed && command < 0x70 && newline_csi_plain[command - 0x40])
    printf("\n");

  if(name)
    printf("%s%s", special_begin, name);
  else
    printf("%sCSI", special_begin);

  if(leader && leader[0])
    printf(" %s", leader);

  for(int i = 0; i < argcount; i++) {
    printf(i ? "," : " ");

    if(args[i] == CSI_ARG_MISSING)
      printf("*");
    else {
      while(CSI_ARG_HAS_MORE(args[i]))
        printf("%ld+", CSI_ARG(args[i++]));
      printf("%ld", CSI_ARG(args[i]));
    }
  }

  if(intermed && intermed[0])
    printf(" %s", intermed);

  if(name)
    printf("%s", special_end);
  else
    printf(" %c%s", command, special_end);

  return 1;
}

static int parser_osc(int command, VTermStringFragment frag, void *user)
{
  if(frag.initial) {
    if(command == -1)
      printf("%sOSC ", special_begin);
    else
      printf("%sOSC %d;", special_begin, command);
  }

  printf("%.*s", (int)frag.len, frag.str);

  if(frag.final)
    printf("%s", special_end);

  return 1;
}

static int parser_dcs(const char *command, size_t commandlen, VTermStringFragment frag, void *user)
{
  if(frag.initial)
    printf("%sDCS %.*s", special_begin, (int)commandlen, command);

  printf("%.*s", (int)frag.len, frag.str);

  if(frag.final)
    printf("%s", special_end);

  return 1;
}

static VTermParserCallbacks parser_cbs = {
  .text    = &parser_text,
  .control = &parser_control,
  .escape  = &parser_escape,
  .csi     = &parser_csi,
  .osc     = &parser_osc,
  .dcs     = &parser_dcs,
};

int main(int argc, char *argv[])
{
  int use_colour = isatty(1);

  int opt;
  while((opt = getopt(argc, argv, "c")) != -1) {
    switch(opt) {
      case 'c': use_colour = 1; break;
    }
  }

  const char *file = argv[optind++];

  int fd;
  if(!file || streq(file, "-"))
    fd = 0; // stdin
  else {
    fd = open(file, O_RDONLY);
    if(fd == -1) {
      fprintf(stderr, "Cannot open %s - %s\n", file, strerror(errno));
      exit(1);
    }
  }

  if(use_colour) {
    special_begin = "\x1b[7m{";
    special_end   = "}\x1b[m";
  }

  /* Size matters not for the parser */
  VTerm *vt = vterm_new(25, 80);
  vterm_set_utf8(vt, 1);
  vterm_parser_set_callbacks(vt, &parser_cbs, NULL);
  vterm_parser_set_emit_nul(vt, true);

  int len;
  char buffer[1024];
  while((len = read(fd, buffer, sizeof(buffer))) > 0) {
    vterm_input_write(vt, buffer, len);
  }

  printf("\n");

  close(fd);
  vterm_free(vt);

  return 0;
}


================================================
FILE: doc/URLs
================================================
ECMA-48:
  http://www.ecma-international.org/publications/standards/Ecma-048.htm

Xterm Control Sequences:
  http://invisible-island.net/xterm/ctlseqs/ctlseqs.html

Digital VT100 User Guide:
  http://vt100.net/docs/vt100-ug/

Digital VT220 Programmer Reference Manual
  http://vt100.net/docs/vt220-rm/

Summary of ANSI standards for ASCII terminals
  http://www.inwap.com/pdp10/ansicode.txt


================================================
FILE: doc/seqs.txt
================================================
Sequences documented in parens are implicit ones from parser.c, which move
between states.

1 = VT100
2 = VT220
3 = VT320
x = xterm

    C0 controls

123    0x00             = NUL
123x   0x07             = BEL
123x   0x08             = BS
123x   0x09             = HT
123x   0x0A             = LF
123x   0x0B             = VT
123x   0x0C             = FF
123x   0x0D             = CR
123x   0x0E             = LS1
123x   0x0F             = LS0
      (0x18             = CAN)
      (0x1A             = SUB)
      (0x1B             = ESC)

123    0x7f             = DEL (ignored)

    C1 controls

123x   0x84             = IND
123x   0x85             = NEL
123x   0x88             = HTS
123x   0x8D             = RI
 23x   0x8E             = SS2
 23x   0x8F             = SS3
      (0x90             = DCS)
      (0x98             = SOS)
      (0x9B             = CSI)
      (0x9C             = ST)
      (0x9D             = OSC)
      (0x9E             = PM)
      (0x9F             = APC)

    Escape sequences
     - excluding sequences that are C1 aliases

123x   ESC (            = SCS, select character set G0
123x   ESC )            = SCS, select character set G1
 23x   ESC *            = SCS, select character set G2
 23x   ESC +            = SCS, select character set G3
123x   ESC 7            = DECSC - save cursor
123x   ESC 8            = DECRC - restore cursor
123x   ESC # 3          = DECDHL, double-height line (top half)
123x   ESC # 4          = DECDHL, double-height line (bottom half)
123x   ESC # 5          = DECSWL, single-width single-height line
123x   ESC # 6          = DECDWL, double-width single-height line
123x   ESC # 8          = DECALN
123    ESC <            = Ignored (used by VT100 to exit VT52 mode)
123x   ESC =            = DECKPAM, keypad application mode
123x   ESC >            = DECKPNM, keypad numeric mode
 23x   ESC Sp F         = S7C1T
 23x   ESC Sp G         = S8C1T
      (ESC P            = DCS)
      (ESC X            = SOS)
      (ESC [            = CSI)
      (ESC \            = ST)
      (ESC ]            = OSC)
      (ESC ^            = PM)
      (ESC _            = APC)
123x   ESC c            = RIS, reset initial state
  3x   ESC n            = LS2
  3x   ESC o            = LS3
  3x   ESC |            = LS3R
  3x   ESC }            = LS2R
  3x   ESC ~            = LS1R

    DCSes

  3x   DCS $ q      ST  = DECRQSS
  3x           m        =   Request SGR
   x           Sp q     =   Request DECSCUSR
  3x           " q      =   Request DECSCA
  3x           r        =   Request DECSTBM
   x           s        =   Request DECSLRM

    CSIs
 23x   CSI @            = ICH
123x   CSI A            = CUU
123x   CSI B            = CUD
123x   CSI C            = CUF
123x   CSI D            = CUB
   x   CSI E            = CNL
   x   CSI F            = CPL
   x   CSI G            = CHA
123x   CSI H            = CUP
   x   CSI I            = CHT
123x   CSI J            = ED
 23x   CSI ? J          = DECSED, selective erase in display
123x   CSI K            = EL
 23x   CSI ? K          = DECSEL, selective erase in line
 23x   CSI L            = IL
 23x   CSI M            = DL
 23x   CSI P            = DCH
   x   CSI S            = SU
   x   CSI T            = SD
 23x   CSI X            = ECH
   x   CSI Z            = CBT
   x   CSI `            = HPA
   x   CSI a            = HPR
   x   CSI b            = REP
123x   CSI   c          = DA, device attributes
123        0            =   DA
 23x   CSI >   c        = DECSDA
 23          0          =   SDA
   x   CSI d            = VPA
   x   CSI e            = VPR
123x   CSI f            = HVP
123x   CSI g            = TBC
123x   CSI h            = SM, Set mode
123x   CSI ? h          = DECSM, DEC set mode
       CSI j            = HPB
       CSI k            = VPB
123x   CSI l            = RM, Reset mode
123x   CSI ? l          = DECRM, DEC reset mode
123x   CSI m            = SGR, Set Graphic Rendition
       CSI ? m          = DECSGR, private Set Graphic Rendition
123x   CSI   n          = DSR, Device Status Report
 23x       5            =   operating status
 23x       6            =   CPR = cursor position
 23x   CSI ? n          = DECDSR; behaves as DSR but uses CSI ? instead of CSI to respond
 23x   CSI ! p          = DECSTR, soft terminal reset
  3x   CSI ? $ p        = DECRQM, request private mode
   x   CSI   Sp q       = DECSCUSR (odd numbers blink, even numbers solid)
           1 or 2       =   block
           3 or 4       =   underline
           5 or 6       =   I-beam to left
   x   CSI > q          = XTVERSION, request version string
 23x   CSI " q          = DECSCA, select character attributes
123x   CSI r            = DECSTBM
   x   CSI s            = DECSLRM
   x   CSI ' }          = DECIC
   x   CSI ' ~          = DECDC

    OSCs

   x   OSC 0;           = Set icon name and title
   x   OSC 1;           = Set icon name
   x   OSC 2;           = Set title
   x   OSC 52;          = Selection management

    Standard modes

 23x   SM 4             = IRM
123x   SM 20            = NLM, linefeed/newline

    DEC modes

123x   DECSM 1          = DECCKM, cursor keys
123x   DECSM 5          = DECSCNM, screen
123x   DECSM 6          = DECOM, origin
123x   DECSM 7          = DECAWM, autowrap
   x   DECSM 12         = Cursor blink
 23x   DECSM 25         = DECTCEM, text cursor enable
   x   DECSM 69         = DECVSSM, vertical screen split
   x   DECSM 1000       = Mouse click/release tracking
   x   DECSM 1002       = Mouse click/release/drag tracking
   x   DECSM 1003       = Mouse all movements tracking
   x   DECSM 1004       = Focus in/out reporting
   x   DECSM 1005       = Mouse protocol extended (UTF-8) - not recommended
   x   DECSM 1006       = Mouse protocol SGR
   x   DECSM 1015       = Mouse protocol rxvt
   x   DECSM 1047       = Altscreen
   x   DECSM 1048       = Save cursor
   x   DECSM 1049       = 1047 + 1048
   x   DECSM 2004       = Bracketed paste

    Graphic Renditions

123x   SGR 0            = Reset
123x   SGR 1            = Bold on
   x   SGR 3            = Italic on
123x   SGR 4            = Underline single
       SGR 4:x          = Underline style
123x   SGR 5            = Blink on
123x   SGR 7            = Reverse on
   x   SGR 8            = Conceal on
   x   SGR 9            = Strikethrough on
       SGR 10-19        = Select font
   x   SGR 21           = Underline double
 23x   SGR 22           = Bold off
   x   SGR 23           = Italic off
 23x   SGR 24           = Underline off
 23x   SGR 25           = Blink off
 23x   SGR 27           = Reverse off
   x   SGR 28           = Conceal off
   x   SGR 29           = Strikethrough off
   x   SGR 30-37        = Foreground ANSI
   x   SGR 38           = Foreground alternative palette
   x   SGR 39           = Foreground default
   x   SGR 40-47        = Background ANSI
   x   SGR 48           = Background alternative palette
   x   SGR 49           = Background default
       SGR 73           = Superscript on
       SGR 74           = Subscript on
       SGR 75           = Superscript/subscript off
   x   SGR 90-97        = Foreground ANSI high-intensity
   x   SGR 100-107      = Background ANSI high-intensity

The state storage used by ESC 7 and DECSM 1048/1049 is shared.

    Unimplemented sequences:

The following sequences are not recognised by libvterm.

123x   0x05             = ENQ
  3    0x11             = DC1 (XON)
  3    0x13             = DC3 (XOFF)
   x   ESC % @          = Select default character set
   x   ESC % G          = Select UTF-8 character set
   x   ESC 6            = DECBI, Back Index
12     ESC Z            = DECID, identify terminal
   x   DCS + Q          = XTGETXRES, Request resource values
       DCS $ q          = [DECRQSS]
  3x           " p      =   Request DECSCL
   x           t        =   Request DECSLPP
   x           $ |      =   Request DECSCPP
   x           * |      =   Request DECSLNS
  3            $ }      =   Request DECSASD
  3            $ ~      =   Request DECSSDT
   x   DCS + p          = XTSETTCAP, set termcap/terminfo data
   x   DCS + q          = XTGETTCAP, request termcap/terminfo
 23    DCS {            = DECDLD, down-line-loadable character set
 23x   DCS |            = DECUDK, user-defined key
   x   CSI Sp @         = Shift left columns
   x   CSI Sp A         = Shift right columns
   x   CSI # P          = XTPUSHCOLORS, push current dynamic colours to stack
   x   CSI # Q          = XTPOPCOLORS, pop dynamic colours from stack
   x   CSI # R          = XTREPORTCOLORS, report current entry on palette stack
   x   CSI ? S          = XTSMGRAPHICS, set/request graphics attribute
   x   CSI > T          = XTRMTITLE, reset title mode features
 23x   CSI i            = DEC printer control
   x   CSI > m          = XTMODKEYS, set key modifier options
   x   CSI > n          = (XTMODKEYS), reset key modifier options
   x   CSI $ p          = DECRQM, request ANSI mode
 23x   CSI " p          = DECSCL, set compatibility level
   x   CSI > p          = XTSMPOINTER, set resource value pointer mode
1  x   CSI q            = DECLL, load LEDs
   x   CSI ? r          = XTRESTORE, restore DEC private mode values
   x   CSI $ r          = DECCARA, change attributes in rectangular area
   x   CSI > s          = XTSHIFTESCAPE, set/reset shift-escape options
   x   CSI ? s          = XTSAVE, save DEC private mode values
   x   CSI t            = XTWINOPS, window operations
   x   CSI > t          = XTSMTITLE, set title mode features
   x   CSI $ t          = DECRARA, reset attributes in rectangular area
  3    CSI   $ u        = DECRQTSR, request terminal state report
  3        1            =   terminal state report
  3    CSI & u          = DECRQUPSS, request user-preferred supplemental set
   x   CSI $ v          = DECCRA, copy rectangular area
  3x   CSI   $ w        = DECRQPSR, request presentation state report
  3x       1            =   cursor information report
  3x       2            =   tab stop report
   x   CSI ' w          = DECEFR, enable filter rectangle
1  x   CSI x            = DECREQTPARM, request terminal parameters
   x   CSI * x          = DECSACE, select attribute change extent
   x   CSI $ x          = DECFRA, fill rectangular area
123    CSI y            = DECTST, invoke confidence test
   x   CSI $ z          = DECERA, erase rectangular area
   x   CSI # {          = XTPUSHSGR, push video attributes onto stack
   x   CSI $ {          = DECSERA, selective erase in rectangular area
   x   CSI # |          = XTREPORTSGR, report selected graphic rendition
   x   CSI $ |          = DECSCPP, select columns per page
   x   CSI # }          = XTPOPSGR, pop video attributes from stack
  3    CSI $ }          = DECSASD, select active status display
  3    CSI $ ~          = DECSSDT, select status line type
 23    SM 2             = KAM, keyboard action
123    SM 12            = SRM, send/receive
123    DECSM 2          = DECANM, ANSI/VT52
123    DECSM 3          = DECCOLM, 132 column
123    DECSM 4          = DECSCLM, scrolling
123    DECSM 8          = DECARM, auto-repeat
12     DECSM 9          = DECINLM, interlace
 23    DECSM 18         = DECPFF, print form feed
 23    DECSM 19         = DECPEX, print extent
 23    DECSM 42         = DECNRCM, national/multinational character


================================================
FILE: find-wide-chars.pl
================================================
#!/usr/bin/perl

use strict;
use warnings;

STDOUT->autoflush(1);

sub iswide
{
   my ( $cp ) = @_;
   return chr($cp) =~ m/\p{East_Asian_Width=Wide}|\p{East_Asian_Width=Fullwidth}/;
}

my ( $start, $end );
foreach my $cp ( 0 .. 0x1FFFF ) {
   iswide($cp) or next;

   if( defined $end and $end == $cp-1 ) {
      # extend the range
      $end = $cp;
      next;
   }

   # start a new range
   printf "  { %#04x, %#04x },\n", $start, $end if defined $start;

   $start = $end = $cp;
}

printf "  { %#04x, %#04x },\n", $start, $end if defined $start;


================================================
FILE: include/vterm.h
================================================
#ifndef __VTERM_H__
#define __VTERM_H__

#ifdef __cplusplus
extern "C" {
#endif

#include <stdint.h>
#include <stdlib.h>
#include <stdbool.h>

#include "vterm_keycodes.h"

#define VTERM_VERSION_MAJOR 0
#define VTERM_VERSION_MINOR 3
#define VTERM_VERSION_PATCH 3

#define VTERM_CHECK_VERSION \
        vterm_check_version(VTERM_VERSION_MAJOR, VTERM_VERSION_MINOR)

/* Any cell can contain at most one basic printing character and 5 combining
 * characters. This number could be changed but will be ABI-incompatible if
 * you do */
#define VTERM_MAX_CHARS_PER_CELL 6

typedef struct VTerm VTerm;
typedef struct VTermState VTermState;
typedef struct VTermScreen VTermScreen;

typedef struct {
  int row;
  int col;
} VTermPos;

/* some small utility functions; we can just keep these static here */

/* order points by on-screen flow order */
static inline int vterm_pos_cmp(VTermPos a, VTermPos b)
{
  return (a.row == b.row) ? a.col - b.col : a.row - b.row;
}

typedef struct {
  int start_row;
  int end_row;
  int start_col;
  int end_col;
} VTermRect;

/* true if the rect contains the point */
static inline int vterm_rect_contains(VTermRect r, VTermPos p)
{
  return p.row >= r.start_row && p.row < r.end_row &&
         p.col >= r.start_col && p.col < r.end_col;
}

/* move a rect */
static inline void vterm_rect_move(VTermRect *rect, int row_delta, int col_delta)
{
  rect->start_row += row_delta; rect->end_row += row_delta;
  rect->start_col += col_delta; rect->end_col += col_delta;
}

/**
 * Bit-field describing the content of the tagged union `VTermColor`.
 */
typedef enum {
  /**
   * If the lower bit of `type` is not set, the colour is 24-bit RGB.
   */
  VTERM_COLOR_RGB = 0x00,

  /**
   * The colour is an index into a palette of 256 colours.
   */
  VTERM_COLOR_INDEXED = 0x01,

  /**
   * Mask that can be used to extract the RGB/Indexed bit.
   */
  VTERM_COLOR_TYPE_MASK = 0x01,

  /**
   * If set, indicates that this colour should be the default foreground
   * color, i.e. there was no SGR request for another colour. When
   * rendering this colour it is possible to ignore "idx" and just use a
   * colour that is not in the palette.
   */
  VTERM_COLOR_DEFAULT_FG = 0x02,

  /**
   * If set, indicates that this colour should be the default background
   * color, i.e. there was no SGR request for another colour. A common
   * option when rendering this colour is to not render a background at
   * all, for example by rendering the window transparently at this spot.
   */
  VTERM_COLOR_DEFAULT_BG = 0x04,

  /**
   * Mask that can be used to extract the default foreground/background bit.
   */
  VTERM_COLOR_DEFAULT_MASK = 0x06
} VTermColorType;

/**
 * Returns true if the VTERM_COLOR_RGB `type` flag is set, indicating that the
 * given VTermColor instance is an indexed colour.
 */
#define VTERM_COLOR_IS_INDEXED(col) \
  (((col)->type & VTERM_COLOR_TYPE_MASK) == VTERM_COLOR_INDEXED)

/**
 * Returns true if the VTERM_COLOR_INDEXED `type` flag is set, indicating that
 * the given VTermColor instance is an rgb colour.
 */
#define VTERM_COLOR_IS_RGB(col) \
  (((col)->type & VTERM_COLOR_TYPE_MASK) == VTERM_COLOR_RGB)

/**
 * Returns true if the VTERM_COLOR_DEFAULT_FG `type` flag is set, indicating
 * that the given VTermColor instance corresponds to the default foreground
 * color.
 */
#define VTERM_COLOR_IS_DEFAULT_FG(col) \
  (!!((col)->type & VTERM_COLOR_DEFAULT_FG))

/**
 * Returns true if the VTERM_COLOR_DEFAULT_BG `type` flag is set, indicating
 * that the given VTermColor instance corresponds to the default background
 * color.
 */
#define VTERM_COLOR_IS_DEFAULT_BG(col) \
  (!!((col)->type & VTERM_COLOR_DEFAULT_BG))

/**
 * Tagged union storing either an RGB color or an index into a colour palette.
 * In order to convert indexed colours to RGB, you may use the
 * vterm_state_convert_color_to_rgb() or vterm_screen_convert_color_to_rgb()
 * functions which lookup the RGB colour from the palette maintained by a
 * VTermState or VTermScreen instance.
 */
typedef union {
  /**
   * Tag indicating which union member is actually valid. This variable
   * coincides with the `type` member of the `rgb` and the `indexed` struct
   * in memory. Please use the `VTERM_COLOR_IS_*` test macros to check whether
   * a particular type flag is set.
   */
  uint8_t type;

  /**
   * Valid if `VTERM_COLOR_IS_RGB(type)` is true. Holds the RGB colour values.
   */
  struct {
    /**
     * Same as the top-level `type` member stored in VTermColor.
     */
    uint8_t type;

    /**
     * The actual 8-bit red, green, blue colour values.
     */
    uint8_t red, green, blue;
  } rgb;

  /**
   * If `VTERM_COLOR_IS_INDEXED(type)` is true, this member holds the index into
   * the colour palette.
   */
  struct {
    /**
     * Same as the top-level `type` member stored in VTermColor.
     */
    uint8_t type;

    /**
     * Index into the colour map.
     */
    uint8_t idx;
  } indexed;
} VTermColor;

/**
 * Constructs a new VTermColor instance representing the given RGB values.
 */
static inline void vterm_color_rgb(VTermColor *col, uint8_t red, uint8_t green,
                                   uint8_t blue)
{
  col->type = VTERM_COLOR_RGB;
  col->rgb.red   = red;
  col->rgb.green = green;
  col->rgb.blue  = blue;
}

/**
 * Construct a new VTermColor instance representing an indexed color with the
 * given index.
 */
static inline void vterm_color_indexed(VTermColor *col, uint8_t idx)
{
  col->type = VTERM_COLOR_INDEXED;
  col->indexed.idx = idx;
}

/**
 * Compares two colours. Returns true if the colors are equal, false otherwise.
 */
int vterm_color_is_equal(const VTermColor *a, const VTermColor *b);

typedef enum {
  /* VTERM_VALUETYPE_NONE = 0 */
  VTERM_VALUETYPE_BOOL = 1,
  VTERM_VALUETYPE_INT,
  VTERM_VALUETYPE_STRING,
  VTERM_VALUETYPE_COLOR,

  VTERM_N_VALUETYPES
} VTermValueType;

typedef struct {
  const char *str;
  size_t      len : 30;
  bool        initial : 1;
  bool        final : 1;
} VTermStringFragment;

typedef union {
  int boolean;
  int number;
  VTermStringFragment string;
  VTermColor color;
} VTermValue;

typedef enum {
  /* VTERM_ATTR_NONE = 0 */
  VTERM_ATTR_BOLD = 1,   // bool:   1, 22
  VTERM_ATTR_UNDERLINE,  // number: 4, 21, 24
  VTERM_ATTR_ITALIC,     // bool:   3, 23
  VTERM_ATTR_BLINK,      // bool:   5, 25
  VTERM_ATTR_REVERSE,    // bool:   7, 27
  VTERM_ATTR_CONCEAL,    // bool:   8, 28
  VTERM_ATTR_STRIKE,     // bool:   9, 29
  VTERM_ATTR_FONT,       // number: 10-19
  VTERM_ATTR_FOREGROUND, // color:  30-39 90-97
  VTERM_ATTR_BACKGROUND, // color:  40-49 100-107
  VTERM_ATTR_SMALL,      // bool:   73, 74, 75
  VTERM_ATTR_BASELINE,   // number: 73, 74, 75

  VTERM_N_ATTRS
} VTermAttr;

typedef enum {
  /* VTERM_PROP_NONE = 0 */
  VTERM_PROP_CURSORVISIBLE = 1, // bool
  VTERM_PROP_CURSORBLINK,       // bool
  VTERM_PROP_ALTSCREEN,         // bool
  VTERM_PROP_TITLE,             // string
  VTERM_PROP_ICONNAME,          // string
  VTERM_PROP_REVERSE,           // bool
  VTERM_PROP_CURSORSHAPE,       // number
  VTERM_PROP_MOUSE,             // number
  VTERM_PROP_FOCUSREPORT,       // bool

  VTERM_N_PROPS
} VTermProp;

enum {
  VTERM_PROP_CURSORSHAPE_BLOCK = 1,
  VTERM_PROP_CURSORSHAPE_UNDERLINE,
  VTERM_PROP_CURSORSHAPE_BAR_LEFT,

  VTERM_N_PROP_CURSORSHAPES
};

enum {
  VTERM_PROP_MOUSE_NONE = 0,
  VTERM_PROP_MOUSE_CLICK,
  VTERM_PROP_MOUSE_DRAG,
  VTERM_PROP_MOUSE_MOVE,

  VTERM_N_PROP_MOUSES
};

typedef enum {
  VTERM_SELECTION_CLIPBOARD = (1<<0),
  VTERM_SELECTION_PRIMARY   = (1<<1),
  VTERM_SELECTION_SECONDARY = (1<<2),
  VTERM_SELECTION_SELECT    = (1<<3),
  VTERM_SELECTION_CUT0      = (1<<4), /* also CUT1 .. CUT7 by bitshifting */
} VTermSelectionMask;

typedef struct {
  const uint32_t *chars;
  int             width;
  unsigned int    protected_cell:1;  /* DECSCA-protected against DECSEL/DECSED */
  unsigned int    dwl:1;             /* DECDWL or DECDHL double-width line */
  unsigned int    dhl:2;             /* DECDHL double-height line (1=top 2=bottom) */
} VTermGlyphInfo;

typedef struct {
  unsigned int    doublewidth:1;     /* DECDWL or DECDHL line */
  unsigned int    doubleheight:2;    /* DECDHL line (1=top 2=bottom) */
  unsigned int    continuation:1;    /* Line is a flow continuation of the previous */
} VTermLineInfo;

/* Copies of VTermState fields that the 'resize' callback might have reason to
 * edit. 'resize' callback gets total control of these fields and may
 * free-and-reallocate them if required. They will be copied back from the
 * struct after the callback has returned.
 */
typedef struct {
  VTermPos pos;                /* current cursor position */
  VTermLineInfo *lineinfos[2]; /* [1] may be NULL */
} VTermStateFields;

typedef struct {
  /* libvterm relies on this memory to be zeroed out before it is returned
   * by the allocator. */
  void *(*malloc)(size_t size, void *allocdata);
  void  (*free)(void *ptr, void *allocdata);
} VTermAllocatorFunctions;

void vterm_check_version(int major, int minor);

struct VTermBuilder {
  int ver; /* currently unused but reserved for some sort of ABI version flag */

  int rows, cols;

  const VTermAllocatorFunctions *allocator;
  void *allocdata;

  /* Override default sizes for various structures */
  size_t outbuffer_len;  /* default: 4096 */
  size_t tmpbuffer_len;  /* default: 4096 */
};

VTerm *vterm_build(const struct VTermBuilder *builder);

/* A convenient shortcut for default cases */
VTerm *vterm_new(int rows, int cols);
/* This shortcuts are generally discouraged in favour of just using vterm_build() */
VTerm *vterm_new_with_allocator(int rows, int cols, VTermAllocatorFunctions *funcs, void *allocdata);

void   vterm_free(VTerm* vt);

void vterm_get_size(const VTerm *vt, int *rowsp, int *colsp);
void vterm_set_size(VTerm *vt, int rows, int cols);

int  vterm_get_utf8(const VTerm *vt);
void vterm_set_utf8(VTerm *vt, int is_utf8);

size_t vterm_input_write(VTerm *vt, const char *bytes, size_t len);

/* Setting output callback will override the buffer logic */
typedef void VTermOutputCallback(const char *s, size_t len, void *user);
void vterm_output_set_callback(VTerm *vt, VTermOutputCallback *func, void *user);

/* These buffer functions only work if output callback is NOT set
 * These are deprecated and will be removed in a later version */
size_t vterm_output_get_buffer_size(const VTerm *vt);
size_t vterm_output_get_buffer_current(const VTerm *vt);
size_t vterm_output_get_buffer_remaining(const VTerm *vt);

/* This too */
size_t vterm_output_read(VTerm *vt, char *buffer, size_t len);

void vterm_keyboard_unichar(VTerm *vt, uint32_t c, VTermModifier mod);
void vterm_keyboard_key(VTerm *vt, VTermKey key, VTermModifier mod);

void vterm_keyboard_start_paste(VTerm *vt);
void vterm_keyboard_end_paste(VTerm *vt);

void vterm_mouse_move(VTerm *vt, int row, int col, VTermModifier mod);
void vterm_mouse_button(VTerm *vt, int button, bool pressed, VTermModifier mod);

// ------------
// Parser layer
// ------------

/* Flag to indicate non-final subparameters in a single CSI parameter.
 * Consider
 *   CSI 1;2:3:4;5a
 * 1 4 and 5 are final.
 * 2 and 3 are non-final and will have this bit set
 *
 * Don't confuse this with the final byte of the CSI escape; 'a' in this case.
 */
#define CSI_ARG_FLAG_MORE (1U<<31)
#define CSI_ARG_MASK      (~(1U<<31))

#define CSI_ARG_HAS_MORE(a) ((a) & CSI_ARG_FLAG_MORE)
#define CSI_ARG(a)          ((a) & CSI_ARG_MASK)

/* Can't use -1 to indicate a missing argument; use this instead */
#define CSI_ARG_MISSING ((1UL<<31)-1)

#define CSI_ARG_IS_MISSING(a) (CSI_ARG(a) == CSI_ARG_MISSING)
#define CSI_ARG_OR(a,def)     (CSI_ARG(a) == CSI_ARG_MISSING ? (def) : CSI_ARG(a))
#define CSI_ARG_COUNT(a)      (CSI_ARG(a) == CSI_ARG_MISSING || CSI_ARG(a) == 0 ? 1 : CSI_ARG(a))

typedef struct {
  int (*text)(const char *bytes, size_t len, void *user);
  int (*control)(unsigned char control, void *user);
  int (*escape)(const char *bytes, size_t len, void *user);
  int (*csi)(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user);
  int (*osc)(int command, VTermStringFragment frag, void *user);
  int (*dcs)(const char *command, size_t commandlen, VTermStringFragment frag, void *user);
  int (*apc)(VTermStringFragment frag, void *user);
  int (*pm)(VTermStringFragment frag, void *user);
  int (*sos)(VTermStringFragment frag, void *user);
  int (*resize)(int rows, int cols, void *user);
} VTermParserCallbacks;

void  vterm_parser_set_callbacks(VTerm *vt, const VTermParserCallbacks *callbacks, void *user);
void *vterm_parser_get_cbdata(VTerm *vt);

/* Normally NUL, CAN, SUB and DEL are ignored. Setting this true causes them
 * to be emitted by the 'control' callback
 */
void vterm_parser_set_emit_nul(VTerm *vt, bool emit);

// -----------
// State layer
// -----------

typedef struct {
  int (*putglyph)(VTermGlyphInfo *info, VTermPos pos, void *user);
  int (*movecursor)(VTermPos pos, VTermPos oldpos, int visible, void *user);
  int (*scrollrect)(VTermRect rect, int downward, int rightward, void *user);
  int (*moverect)(VTermRect dest, VTermRect src, void *user);
  int (*erase)(VTermRect rect, int selective, void *user);
  int (*initpen)(void *user);
  int (*setpenattr)(VTermAttr attr, VTermValue *val, void *user);
  int (*settermprop)(VTermProp prop, VTermValue *val, void *user);
  int (*bell)(void *user);
  int (*resize)(int rows, int cols, VTermStateFields *fields, void *user);
  int (*setlineinfo)(int row, const VTermLineInfo *newinfo, const VTermLineInfo *oldinfo, void *user);
  int (*sb_clear)(void *user);
  // ABI-compat only enabled if vterm_state_callbacks_has_premove() is invoked
  int (*premove)(VTermRect dest, void *user);
} VTermStateCallbacks;

typedef struct {
  int (*control)(unsigned char control, void *user);
  int (*csi)(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user);
  int (*osc)(int command, VTermStringFragment frag, void *user);
  int (*dcs)(const char *command, size_t commandlen, VTermStringFragment frag, void *user);
  int (*apc)(VTermStringFragment frag, void *user);
  int (*pm)(VTermStringFragment frag, void *user);
  int (*sos)(VTermStringFragment frag, void *user);
} VTermStateFallbacks;

typedef struct {
  int (*set)(VTermSelectionMask mask, VTermStringFragment frag, void *user);
  int (*query)(VTermSelectionMask mask, void *user);
} VTermSelectionCallbacks;

VTermState *vterm_obtain_state(VTerm *vt);

void  vterm_state_set_callbacks(VTermState *state, const VTermStateCallbacks *callbacks, void *user);
void *vterm_state_get_cbdata(VTermState *state);

void vterm_state_callbacks_has_premove(VTermState *state);

void  vterm_state_set_unrecognised_fallbacks(VTermState *state, const VTermStateFallbacks *fallbacks, void *user);
void *vterm_state_get_unrecognised_fbdata(VTermState *state);

void vterm_state_reset(VTermState *state, int hard);
void vterm_state_get_cursorpos(const VTermState *state, VTermPos *cursorpos);
void vterm_state_get_default_colors(const VTermState *state, VTermColor *default_fg, VTermColor *default_bg);
void vterm_state_get_palette_color(const VTermState *state, int index, VTermColor *col);
void vterm_state_set_default_colors(VTermState *state, const VTermColor *default_fg, const VTermColor *default_bg);
void vterm_state_set_palette_color(VTermState *state, int index, const VTermColor *col);
void vterm_state_set_bold_highbright(VTermState *state, int bold_is_highbright);
int  vterm_state_get_penattr(const VTermState *state, VTermAttr attr, VTermValue *val);
int  vterm_state_set_termprop(VTermState *state, VTermProp prop, VTermValue *val);
void vterm_state_focus_in(VTermState *state);
void vterm_state_focus_out(VTermState *state);
const VTermLineInfo *vterm_state_get_lineinfo(const VTermState *state, int row);

/**
 * Makes sure that the given color `col` is indeed an RGB colour. After this
 * function returns, VTERM_COLOR_IS_RGB(col) will return true, while all other
 * flags stored in `col->type` will have been reset.
 *
 * @param state is the VTermState instance from which the colour palette should
 * be extracted.
 * @param col is a pointer at the VTermColor instance that should be converted
 * to an RGB colour.
 */
void vterm_state_convert_color_to_rgb(const VTermState *state, VTermColor *col);

void vterm_state_set_selection_callbacks(VTermState *state, const VTermSelectionCallbacks *callbacks, void *user,
    char *buffer, size_t buflen);

void vterm_state_send_selection(VTermState *state, VTermSelectionMask mask, VTermStringFragment frag);

// ------------
// Screen layer
// ------------

typedef struct {
    unsigned int bold      : 1;
    unsigned int underline : 2;
    unsigned int italic    : 1;
    unsigned int blink     : 1;
    unsigned int reverse   : 1;
    unsigned int conceal   : 1;
    unsigned int strike    : 1;
    unsigned int font      : 4; /* 0 to 9 */
    unsigned int dwl       : 1; /* On a DECDWL or DECDHL line */
    unsigned int dhl       : 2; /* On a DECDHL line (1=top 2=bottom) */
    unsigned int small     : 1;
    unsigned int baseline  : 2;
} VTermScreenCellAttrs;

enum {
  VTERM_UNDERLINE_OFF,
  VTERM_UNDERLINE_SINGLE,
  VTERM_UNDERLINE_DOUBLE,
  VTERM_UNDERLINE_CURLY,
};

enum {
  VTERM_BASELINE_NORMAL,
  VTERM_BASELINE_RAISE,
  VTERM_BASELINE_LOWER,
};

typedef struct {
  uint32_t chars[VTERM_MAX_CHARS_PER_CELL];
  char     width;
  VTermScreenCellAttrs attrs;
  VTermColor fg, bg;
} VTermScreenCell;

typedef struct {
  int (*damage)(VTermRect rect, void *user);
  int (*moverect)(VTermRect dest, VTermRect src, void *user);
  int (*movecursor)(VTermPos pos, VTermPos oldpos, int visible, void *user);
  int (*settermprop)(VTermProp prop, VTermValue *val, void *user);
  int (*bell)(void *user);
  int (*resize)(int rows, int cols, void *user);
  int (*sb_pushline)(int cols, const VTermScreenCell *cells, void *user);
  int (*sb_popline)(int cols, VTermScreenCell *cells, void *user);
  int (*sb_clear)(void* user);
  /* ABI-compat this is only used if vterm_screen_callbacks_has_pushline4() is called */
  int (*sb_pushline4)(int cols, const VTermScreenCell *cells, bool continuation, void *user);
} VTermScreenCallbacks;

VTermScreen *vterm_obtain_screen(VTerm *vt);

void  vterm_screen_set_callbacks(VTermScreen *screen, const VTermScreenCallbacks *callbacks, void *user);
void *vterm_screen_get_cbdata(VTermScreen *screen);

void vterm_screen_callbacks_has_pushline4(VTermScreen *screen);

void  vterm_screen_set_unrecognised_fallbacks(VTermScreen *screen, const VTermStateFallbacks *fallbacks, void *user);
void *vterm_screen_get_unrecognised_fbdata(VTermScreen *screen);

void vterm_screen_enable_reflow(VTermScreen *screen, bool reflow);

// Back-compat alias for the brief time it was in 0.3-RC1
#define vterm_screen_set_reflow  vterm_screen_enable_reflow

void vterm_screen_enable_altscreen(VTermScreen *screen, int altscreen);

typedef enum {
  VTERM_DAMAGE_CELL,    /* every cell */
  VTERM_DAMAGE_ROW,     /* entire rows */
  VTERM_DAMAGE_SCREEN,  /* entire screen */
  VTERM_DAMAGE_SCROLL,  /* entire screen + scrollrect */

  VTERM_N_DAMAGES
} VTermDamageSize;

void vterm_screen_flush_damage(VTermScreen *screen);
void vterm_screen_set_damage_merge(VTermScreen *screen, VTermDamageSize size);

void   vterm_screen_reset(VTermScreen *screen, int hard);

/* Neither of these functions NUL-terminate the buffer */
size_t vterm_screen_get_chars(const VTermScreen *screen, uint32_t *chars, size_t len, const VTermRect rect);
size_t vterm_screen_get_text(const VTermScreen *screen, char *str, size_t len, const VTermRect rect);

typedef enum {
  VTERM_ATTR_BOLD_MASK       = 1 << 0,
  VTERM_ATTR_UNDERLINE_MASK  = 1 << 1,
  VTERM_ATTR_ITALIC_MASK     = 1 << 2,
  VTERM_ATTR_BLINK_MASK      = 1 << 3,
  VTERM_ATTR_REVERSE_MASK    = 1 << 4,
  VTERM_ATTR_STRIKE_MASK     = 1 << 5,
  VTERM_ATTR_FONT_MASK       = 1 << 6,
  VTERM_ATTR_FOREGROUND_MASK = 1 << 7,
  VTERM_ATTR_BACKGROUND_MASK = 1 << 8,
  VTERM_ATTR_CONCEAL_MASK    = 1 << 9,
  VTERM_ATTR_SMALL_MASK      = 1 << 10,
  VTERM_ATTR_BASELINE_MASK   = 1 << 11,

  VTERM_ALL_ATTRS_MASK = (1 << 12) - 1
} VTermAttrMask;

int vterm_screen_get_attrs_extent(const VTermScreen *screen, VTermRect *extent, VTermPos pos, VTermAttrMask attrs);

int vterm_screen_get_cell(const VTermScreen *screen, VTermPos pos, VTermScreenCell *cell);

int vterm_screen_is_eol(const VTermScreen *screen, VTermPos pos);

/**
 * Same as vterm_state_convert_color_to_rgb(), but takes a `screen` instead of a `state`
 * instance.
 */
void vterm_screen_convert_color_to_rgb(const VTermScreen *screen, VTermColor *col);

/**
 * Similar to vterm_state_set_default_colors(), but also resets colours in the
 * screen buffer(s)
 */
void vterm_screen_set_default_colors(VTermScreen *screen, const VTermColor *default_fg, const VTermColor *default_bg);

// ---------
// Utilities
// ---------

VTermValueType vterm_get_attr_type(VTermAttr attr);
VTermValueType vterm_get_prop_type(VTermProp prop);

void vterm_scroll_rect(VTermRect rect,
                       int downward,
                       int rightward,
                       int (*moverect)(VTermRect src, VTermRect dest, void *user),
                       int (*eraserect)(VTermRect rect, int selective, void *user),
                       void *user);

void vterm_copy_cells(VTermRect dest,
                      VTermRect src,
                      void (*copycell)(VTermPos dest, VTermPos src, void *user),
                      void *user);

#ifdef __cplusplus
}
#endif

#endif


================================================
FILE: include/vterm_keycodes.h
================================================
#ifndef __VTERM_INPUT_H__
#define __VTERM_INPUT_H__

typedef enum {
  VTERM_MOD_NONE  = 0x00,
  VTERM_MOD_SHIFT = 0x01,
  VTERM_MOD_ALT   = 0x02,
  VTERM_MOD_CTRL  = 0x04,

  VTERM_ALL_MODS_MASK = 0x07 
} VTermModifier;

typedef enum {
  VTERM_KEY_NONE,

  VTERM_KEY_ENTER,
  VTERM_KEY_TAB,
  VTERM_KEY_BACKSPACE,
  VTERM_KEY_ESCAPE,

  VTERM_KEY_UP,
  VTERM_KEY_DOWN,
  VTERM_KEY_LEFT,
  VTERM_KEY_RIGHT,

  VTERM_KEY_INS,
  VTERM_KEY_DEL,
  VTERM_KEY_HOME,
  VTERM_KEY_END,
  VTERM_KEY_PAGEUP,
  VTERM_KEY_PAGEDOWN,

  VTERM_KEY_FUNCTION_0   = 256,
  VTERM_KEY_FUNCTION_MAX = VTERM_KEY_FUNCTION_0 + 255,

  VTERM_KEY_KP_0,
  VTERM_KEY_KP_1,
  VTERM_KEY_KP_2,
  VTERM_KEY_KP_3,
  VTERM_KEY_KP_4,
  VTERM_KEY_KP_5,
  VTERM_KEY_KP_6,
  VTERM_KEY_KP_7,
  VTERM_KEY_KP_8,
  VTERM_KEY_KP_9,
  VTERM_KEY_KP_MULT,
  VTERM_KEY_KP_PLUS,
  VTERM_KEY_KP_COMMA,
  VTERM_KEY_KP_MINUS,
  VTERM_KEY_KP_PERIOD,
  VTERM_KEY_KP_DIVIDE,
  VTERM_KEY_KP_ENTER,
  VTERM_KEY_KP_EQUAL,

  VTERM_KEY_MAX, // Must be last
  VTERM_N_KEYS = VTERM_KEY_MAX
} VTermKey;

#define VTERM_KEY_FUNCTION(n) (VTERM_KEY_FUNCTION_0+(n))

#endif


================================================
FILE: src/encoding/DECdrawing.inc
================================================
static const struct StaticTableEncoding encoding_DECdrawing = {
  { .decode = &decode_table },
  {
    [0x60] = 0x25C6,
    [0x61] = 0x2592,
    [0x62] = 0x2409,
    [0x63] = 0x240C,
    [0x64] = 0x240D,
    [0x65] = 0x240A,
    [0x66] = 0x00B0,
    [0x67] = 0x00B1,
    [0x68] = 0x2424,
    [0x69] = 0x240B,
    [0x6a] = 0x2518,
    [0x6b] = 0x2510,
    [0x6c] = 0x250C,
    [0x6d] = 0x2514,
    [0x6e] = 0x253C,
    [0x6f] = 0x23BA,
    [0x70] = 0x23BB,
    [0x71] = 0x2500,
    [0x72] = 0x23BC,
    [0x73] = 0x23BD,
    [0x74] = 0x251C,
    [0x75] = 0x2524,
    [0x76] = 0x2534,
    [0x77] = 0x252C,
    [0x78] = 0x2502,
    [0x79] = 0x2A7D,
    [0x7a] = 0x2A7E,
    [0x7b] = 0x03C0,
    [0x7c] = 0x2260,
    [0x7d] = 0x00A3,
    [0x7e] = 0x00B7,
  }
};


================================================
FILE: src/encoding/DECdrawing.tbl
================================================
6/0 = U+25C6 # BLACK DIAMOND
6/1 = U+2592 # MEDIUM SHADE (checkerboard)
6/2 = U+2409 # SYMBOL FOR HORIZONTAL TAB
6/3 = U+240C # SYMBOL FOR FORM FEED
6/4 = U+240D # SYMBOL FOR CARRIAGE RETURN
6/5 = U+240A # SYMBOL FOR LINE FEED
6/6 = U+00B0 # DEGREE SIGN
6/7 = U+00B1 # PLUS-MINUS SIGN (plus or minus)
6/8 = U+2424 # SYMBOL FOR NEW LINE
6/9 = U+240B # SYMBOL FOR VERTICAL TAB
6/10 = U+2518 # BOX DRAWINGS LIGHT UP AND LEFT (bottom-right corner)
6/11 = U+2510 # BOX DRAWINGS LIGHT DOWN AND LEFT (top-right corner)
6/12 = U+250C # BOX DRAWINGS LIGHT DOWN AND RIGHT (top-left corner)
6/13 = U+2514 # BOX DRAWINGS LIGHT UP AND RIGHT (bottom-left corner)
6/14 = U+253C # BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL (crossing lines)
6/15 = U+23BA # HORIZONTAL SCAN LINE-1
7/0 = U+23BB # HORIZONTAL SCAN LINE-3
7/1 = U+2500 # BOX DRAWINGS LIGHT HORIZONTAL
7/2 = U+23BC # HORIZONTAL SCAN LINE-7
7/3 = U+23BD # HORIZONTAL SCAN LINE-9
7/4 = U+251C # BOX DRAWINGS LIGHT VERTICAL AND RIGHT
7/5 = U+2524 # BOX DRAWINGS LIGHT VERTICAL AND LEFT
7/6 = U+2534 # BOX DRAWINGS LIGHT UP AND HORIZONTAL
7/7 = U+252C # BOX DRAWINGS LIGHT DOWN AND HORIZONTAL
7/8 = U+2502 # BOX DRAWINGS LIGHT VERTICAL
7/9 = U+2A7D # LESS-THAN OR SLANTED EQUAL-TO
7/10 = U+2A7E # GREATER-THAN OR SLANTED EQUAL-TO
7/11 = U+03C0 # GREEK SMALL LETTER PI
7/12 = U+2260 # NOT EQUAL TO
7/13 = U+00A3 # POUND SIGN
7/14 = U+00B7 # MIDDLE DOT


================================================
FILE: src/encoding/uk.inc
================================================
static const struct StaticTableEncoding encoding_uk = {
  { .decode = &decode_table },
  {
    [0x23] = 0x00a3,
  }
};


================================================
FILE: src/encoding/uk.tbl
================================================
2/3 = "£"


================================================
FILE: src/encoding.c
================================================
#include "vterm_internal.h"

#define UNICODE_INVALID 0xFFFD

#if defined(DEBUG) && DEBUG > 1
# define DEBUG_PRINT_UTF8
#endif

struct UTF8DecoderData {
  // number of bytes remaining in this codepoint
  int bytes_remaining;

  // number of bytes total in this codepoint once it's finished
  // (for detecting overlongs)
  int bytes_total;

  int this_cp;
};

static void init_utf8(VTermEncoding *enc, void *data_)
{
  struct UTF8DecoderData *data = data_;

  data->bytes_remaining = 0;
  data->bytes_total     = 0;
}

static void decode_utf8(VTermEncoding *enc, void *data_,
                        uint32_t cp[], int *cpi, int cplen,
                        const char bytes[], size_t *pos, size_t bytelen)
{
  struct UTF8DecoderData *data = data_;

#ifdef DEBUG_PRINT_UTF8
  printf("BEGIN UTF-8\n");
#endif

  for(; *pos < bytelen && *cpi < cplen; (*pos)++) {
    unsigned char c = bytes[*pos];

#ifdef DEBUG_PRINT_UTF8
    printf(" pos=%zd c=%02x rem=%d\n", *pos, c, data->bytes_remaining);
#endif

    if(c < 0x20) // C0
      return;

    else if(c >= 0x20 && c < 0x7f) {
      if(data->bytes_remaining)
        cp[(*cpi)++] = UNICODE_INVALID;

      cp[(*cpi)++] = c;
#ifdef DEBUG_PRINT_UTF8
      printf(" UTF-8 char: U+%04x\n", c);
#endif
      data->bytes_remaining = 0;
    }

    else if(c == 0x7f) // DEL
      return;

    else if(c >= 0x80 && c < 0xc0) {
      if(!data->bytes_remaining) {
        cp[(*cpi)++] = UNICODE_INVALID;
        continue;
      }

      data->this_cp <<= 6;
      data->this_cp |= c & 0x3f;
      data->bytes_remaining--;

      if(!data->bytes_remaining) {
#ifdef DEBUG_PRINT_UTF8
        printf(" UTF-8 raw char U+%04x bytelen=%d ", data->this_cp, data->bytes_total);
#endif
        // Check for overlong sequences
        switch(data->bytes_total) {
        case 2:
          if(data->this_cp <  0x0080) data->this_cp = UNICODE_INVALID;
          break;
        case 3:
          if(data->this_cp <  0x0800) data->this_cp = UNICODE_INVALID;
          break;
        case 4:
          if(data->this_cp < 0x10000) data->this_cp = UNICODE_INVALID;
          break;
        case 5:
          if(data->this_cp < 0x200000) data->this_cp = UNICODE_INVALID;
          break;
        case 6:
          if(data->this_cp < 0x4000000) data->this_cp = UNICODE_INVALID;
          break;
        }
        // Now look for plain invalid ones
        if((data->this_cp >= 0xD800 && data->this_cp <= 0xDFFF) ||
           data->this_cp == 0xFFFE ||
           data->this_cp == 0xFFFF)
          data->this_cp = UNICODE_INVALID;
#ifdef DEBUG_PRINT_UTF8
        printf(" char: U+%04x\n", data->this_cp);
#endif
        cp[(*cpi)++] = data->this_cp;
      }
    }

    else if(c >= 0xc0 && c < 0xe0) {
      if(data->bytes_remaining)
        cp[(*cpi)++] = UNICODE_INVALID;

      data->this_cp = c & 0x1f;
      data->bytes_total = 2;
      data->bytes_remaining = 1;
    }

    else if(c >= 0xe0 && c < 0xf0) {
      if(data->bytes_remaining)
        cp[(*cpi)++] = UNICODE_INVALID;

      data->this_cp = c & 0x0f;
      data->bytes_total = 3;
      data->bytes_remaining = 2;
    }

    else if(c >= 0xf0 && c < 0xf8) {
      if(data->bytes_remaining)
        cp[(*cpi)++] = UNICODE_INVALID;

      data->this_cp = c & 0x07;
      data->bytes_total = 4;
      data->bytes_remaining = 3;
    }

    else if(c >= 0xf8 && c < 0xfc) {
      if(data->bytes_remaining)
        cp[(*cpi)++] = UNICODE_INVALID;

      data->this_cp = c & 0x03;
      data->bytes_total = 5;
      data->bytes_remaining = 4;
    }

    else if(c >= 0xfc && c < 0xfe) {
      if(data->bytes_remaining)
        cp[(*cpi)++] = UNICODE_INVALID;

      data->this_cp = c & 0x01;
      data->bytes_total = 6;
      data->bytes_remaining = 5;
    }

    else {
      cp[(*cpi)++] = UNICODE_INVALID;
    }
  }
}

static VTermEncoding encoding_utf8 = {
  .init   = &init_utf8,
  .decode = &decode_utf8,
};

static void decode_usascii(VTermEncoding *enc, void *data,
                           uint32_t cp[], int *cpi, int cplen,
                           const char bytes[], size_t *pos, size_t bytelen)
{
  int is_gr = bytes[*pos] & 0x80;

  for(; *pos < bytelen && *cpi < cplen; (*pos)++) {
    unsigned char c = bytes[*pos] ^ is_gr;

    if(c < 0x20 || c == 0x7f || c >= 0x80)
      return;

    cp[(*cpi)++] = c;
  }
}

static VTermEncoding encoding_usascii = {
  .decode = &decode_usascii,
};

struct StaticTableEncoding {
  const VTermEncoding enc;
  const uint32_t chars[128];
};

static void decode_table(VTermEncoding *enc, void *data,
                         uint32_t cp[], int *cpi, int cplen,
                         const char bytes[], size_t *pos, size_t bytelen)
{
  struct StaticTableEncoding *table = (struct StaticTableEncoding *)enc;
  int is_gr = bytes[*pos] & 0x80;

  for(; *pos < bytelen && *cpi < cplen; (*pos)++) {
    unsigned char c = bytes[*pos] ^ is_gr;

    if(c < 0x20 || c == 0x7f || c >= 0x80)
      return;

    if(table->chars[c])
      cp[(*cpi)++] = table->chars[c];
    else
      cp[(*cpi)++] = c;
  }
}

#include "encoding/DECdrawing.inc"
#include "encoding/uk.inc"

static struct {
  VTermEncodingType type;
  char designation;
  VTermEncoding *enc;
}
encodings[] = {
  { ENC_UTF8,      'u', &encoding_utf8 },
  { ENC_SINGLE_94, '0', (VTermEncoding*)&encoding_DECdrawing },
  { ENC_SINGLE_94, 'A', (VTermEncoding*)&encoding_uk },
  { ENC_SINGLE_94, 'B', &encoding_usascii },
  { 0 },
};

/* This ought to be INTERNAL but isn't because it's used by unit testing */
VTermEncoding *vterm_lookup_encoding(VTermEncodingType type, char designation)
{
  for(int i = 0; encodings[i].designation; i++)
    if(encodings[i].type == type && encodings[i].designation == designation)
      return encodings[i].enc;
  return NULL;
}


================================================
FILE: src/fullwidth.inc
================================================
  { 0x1100, 0x115f },
  { 0x231a, 0x231b },
  { 0x2329, 0x232a },
  { 0x23e9, 0x23ec },
  { 0x23f0, 0x23f0 },
  { 0x23f3, 0x23f3 },
  { 0x25fd, 0x25fe },
  { 0x2614, 0x2615 },
  { 0x2648, 0x2653 },
  { 0x267f, 0x267f },
  { 0x2693, 0x2693 },
  { 0x26a1, 0x26a1 },
  { 0x26aa, 0x26ab },
  { 0x26bd, 0x26be },
  { 0x26c4, 0x26c5 },
  { 0x26ce, 0x26ce },
  { 0x26d4, 0x26d4 },
  { 0x26ea, 0x26ea },
  { 0x26f2, 0x26f3 },
  { 0x26f5, 0x26f5 },
  { 0x26fa, 0x26fa },
  { 0x26fd, 0x26fd },
  { 0x2705, 0x2705 },
  { 0x270a, 0x270b },
  { 0x2728, 0x2728 },
  { 0x274c, 0x274c },
  { 0x274e, 0x274e },
  { 0x2753, 0x2755 },
  { 0x2757, 0x2757 },
  { 0x2795, 0x2797 },
  { 0x27b0, 0x27b0 },
  { 0x27bf, 0x27bf },
  { 0x2b1b, 0x2b1c },
  { 0x2b50, 0x2b50 },
  { 0x2b55, 0x2b55 },
  { 0x2e80, 0x2e99 },
  { 0x2e9b, 0x2ef3 },
  { 0x2f00, 0x2fd5 },
  { 0x2ff0, 0x2ffb },
  { 0x3000, 0x303e },
  { 0x3041, 0x3096 },
  { 0x3099, 0x30ff },
  { 0x3105, 0x312f },
  { 0x3131, 0x318e },
  { 0x3190, 0x31ba },
  { 0x31c0, 0x31e3 },
  { 0x31f0, 0x321e },
  { 0x3220, 0x3247 },
  { 0x3250, 0x4dbf },
  { 0x4e00, 0xa48c },
  { 0xa490, 0xa4c6 },
  { 0xa960, 0xa97c },
  { 0xac00, 0xd7a3 },
  { 0xf900, 0xfaff },
  { 0xfe10, 0xfe19 },
  { 0xfe30, 0xfe52 },
  { 0xfe54, 0xfe66 },
  { 0xfe68, 0xfe6b },
  { 0xff01, 0xff60 },
  { 0xffe0, 0xffe6 },
  { 0x16fe0, 0x16fe3 },
  { 0x17000, 0x187f7 },
  { 0x18800, 0x18af2 },
  { 0x1b000, 0x1b11e },
  { 0x1b150, 0x1b152 },
  { 0x1b164, 0x1b167 },
  { 0x1b170, 0x1b2fb },
  { 0x1f004, 0x1f004 },
  { 0x1f0cf, 0x1f0cf },
  { 0x1f18e, 0x1f18e },
  { 0x1f191, 0x1f19a },
  { 0x1f200, 0x1f202 },
  { 0x1f210, 0x1f23b },
  { 0x1f240, 0x1f248 },
  { 0x1f250, 0x1f251 },
  { 0x1f260, 0x1f265 },
  { 0x1f300, 0x1f320 },
  { 0x1f32d, 0x1f335 },
  { 0x1f337, 0x1f37c },
  { 0x1f37e, 0x1f393 },
  { 0x1f3a0, 0x1f3ca },
  { 0x1f3cf, 0x1f3d3 },
  { 0x1f3e0, 0x1f3f0 },
  { 0x1f3f4, 0x1f3f4 },
  { 0x1f3f8, 0x1f43e },
  { 0x1f440, 0x1f440 },
  { 0x1f442, 0x1f4fc },
  { 0x1f4ff, 0x1f53d },
  { 0x1f54b, 0x1f54e },
  { 0x1f550, 0x1f567 },
  { 0x1f57a, 0x1f57a },
  { 0x1f595, 0x1f596 },
  { 0x1f5a4, 0x1f5a4 },
  { 0x1f5fb, 0x1f64f },
  { 0x1f680, 0x1f6c5 },
  { 0x1f6cc, 0x1f6cc },
  { 0x1f6d0, 0x1f6d2 },
  { 0x1f6d5, 0x1f6d5 },
  { 0x1f6eb, 0x1f6ec },
  { 0x1f6f4, 0x1f6fa },
  { 0x1f7e0, 0x1f7eb },
  { 0x1f90d, 0x1f971 },
  { 0x1f973, 0x1f976 },
  { 0x1f97a, 0x1f9a2 },
  { 0x1f9a5, 0x1f9aa },
  { 0x1f9ae, 0x1f9ca },
  { 0x1f9cd, 0x1f9ff },
  { 0x1fa70, 0x1fa73 },
  { 0x1fa78, 0x1fa7a },
  { 0x1fa80, 0x1fa82 },
  { 0x1fa90, 0x1fa95 },


================================================
FILE: src/keyboard.c
================================================
#include "vterm_internal.h"

#include <stdio.h>

#include "utf8.h"

void vterm_keyboard_unichar(VTerm *vt, uint32_t c, VTermModifier mod)
{
  /* The shift modifier is never important for Unicode characters
   * apart from Space
   */
  if(c != ' ')
    mod &= ~VTERM_MOD_SHIFT;

  if(mod == 0) {
    // Normal text - ignore just shift
    char str[6];
    int seqlen = fill_utf8(c, str);
    vterm_push_output_bytes(vt, str, seqlen);
    return;
  }

  int needs_CSIu;
  switch(c) {
    /* Special Ctrl- letters that can't be represented elsewise */
    case 'i': case 'j': case 'm': case '[':
      needs_CSIu = 1;
      break;
    /* Ctrl-\ ] ^ _ don't need CSUu */
    case '\\': case ']': case '^': case '_':
      needs_CSIu = 0;
      break;
    /* Shift-space needs CSIu */
    case ' ':
      needs_CSIu = !!(mod & VTERM_MOD_SHIFT);
      break;
    /* All other characters needs CSIu except for letters a-z */
    default:
      needs_CSIu = (c < 'a' || c > 'z');
  }

  /* ALT we can just prefix with ESC; anything else requires CSI u */
  if(needs_CSIu && (mod & ~VTERM_MOD_ALT)) {
    vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d;%du", c, mod+1);
    return;
  }

  if(mod & VTERM_MOD_CTRL)
    c &= 0x1f;

  vterm_push_output_sprintf(vt, "%s%c", mod & VTERM_MOD_ALT ? ESC_S : "", c);
}

typedef struct {
  enum {
    KEYCODE_NONE,
    KEYCODE_LITERAL,
    KEYCODE_TAB,
    KEYCODE_ENTER,
    KEYCODE_SS3,
    KEYCODE_CSI,
    KEYCODE_CSI_CURSOR,
    KEYCODE_CSINUM,
    KEYCODE_KEYPAD,
  } type;
  char literal;
  int csinum;
} keycodes_s;

static keycodes_s keycodes[] = {
  { KEYCODE_NONE }, // NONE

  { KEYCODE_ENTER,   '\r'   }, // ENTER
  { KEYCODE_TAB,     '\t'   }, // TAB
  { KEYCODE_LITERAL, '\x7f' }, // BACKSPACE == ASCII DEL
  { KEYCODE_LITERAL, '\x1b' }, // ESCAPE

  { KEYCODE_CSI_CURSOR, 'A' }, // UP
  { KEYCODE_CSI_CURSOR, 'B' }, // DOWN
  { KEYCODE_CSI_CURSOR, 'D' }, // LEFT
  { KEYCODE_CSI_CURSOR, 'C' }, // RIGHT

  { KEYCODE_CSINUM, '~', 2 },  // INS
  { KEYCODE_CSINUM, '~', 3 },  // DEL
  { KEYCODE_CSI_CURSOR, 'H' }, // HOME
  { KEYCODE_CSI_CURSOR, 'F' }, // END
  { KEYCODE_CSINUM, '~', 5 },  // PAGEUP
  { KEYCODE_CSINUM, '~', 6 },  // PAGEDOWN
};

static keycodes_s keycodes_fn[] = {
  { KEYCODE_NONE },            // F0 - shouldn't happen
  { KEYCODE_SS3,    'P' },     // F1
  { KEYCODE_SS3,    'Q' },     // F2
  { KEYCODE_SS3,    'R' },     // F3
  { KEYCODE_SS3,    'S' },     // F4
  { KEYCODE_CSINUM, '~', 15 }, // F5
  { KEYCODE_CSINUM, '~', 17 }, // F6
  { KEYCODE_CSINUM, '~', 18 }, // F7
  { KEYCODE_CSINUM, '~', 19 }, // F8
  { KEYCODE_CSINUM, '~', 20 }, // F9
  { KEYCODE_CSINUM, '~', 21 }, // F10
  { KEYCODE_CSINUM, '~', 23 }, // F11
  { KEYCODE_CSINUM, '~', 24 }, // F12
};

static keycodes_s keycodes_kp[] = {
  { KEYCODE_KEYPAD, '0', 'p' }, // KP_0
  { KEYCODE_KEYPAD, '1', 'q' }, // KP_1
  { KEYCODE_KEYPAD, '2', 'r' }, // KP_2
  { KEYCODE_KEYPAD, '3', 's' }, // KP_3
  { KEYCODE_KEYPAD, '4', 't' }, // KP_4
  { KEYCODE_KEYPAD, '5', 'u' }, // KP_5
  { KEYCODE_KEYPAD, '6', 'v' }, // KP_6
  { KEYCODE_KEYPAD, '7', 'w' }, // KP_7
  { KEYCODE_KEYPAD, '8', 'x' }, // KP_8
  { KEYCODE_KEYPAD, '9', 'y' }, // KP_9
  { KEYCODE_KEYPAD, '*', 'j' }, // KP_MULT
  { KEYCODE_KEYPAD, '+', 'k' }, // KP_PLUS
  { KEYCODE_KEYPAD, ',', 'l' }, // KP_COMMA
  { KEYCODE_KEYPAD, '-', 'm' }, // KP_MINUS
  { KEYCODE_KEYPAD, '.', 'n' }, // KP_PERIOD
  { KEYCODE_KEYPAD, '/', 'o' }, // KP_DIVIDE
  { KEYCODE_KEYPAD, '\n', 'M' }, // KP_ENTER
  { KEYCODE_KEYPAD, '=', 'X' }, // KP_EQUAL
};

void vterm_keyboard_key(VTerm *vt, VTermKey key, VTermModifier mod)
{
  if(key == VTERM_KEY_NONE)
    return;

  keycodes_s k;
  if(key < VTERM_KEY_FUNCTION_0) {
    if(key >= sizeof(keycodes)/sizeof(keycodes[0]))
      return;
    k = keycodes[key];
  }
  else if(key >= VTERM_KEY_FUNCTION_0 && key <= VTERM_KEY_FUNCTION_MAX) {
    if((key - VTERM_KEY_FUNCTION_0) >= sizeof(keycodes_fn)/sizeof(keycodes_fn[0]))
      return;
    k = keycodes_fn[key - VTERM_KEY_FUNCTION_0];
  }
  else if(key >= VTERM_KEY_KP_0) {
    if((key - VTERM_KEY_KP_0) >= sizeof(keycodes_kp)/sizeof(keycodes_kp[0]))
      return;
    k = keycodes_kp[key - VTERM_KEY_KP_0];
  }

  switch(k.type) {
  case KEYCODE_NONE:
    break;

  case KEYCODE_TAB:
    /* Shift-Tab is CSI Z but plain Tab is 0x09 */
    if(mod == VTERM_MOD_SHIFT)
      vterm_push_output_sprintf_ctrl(vt, C1_CSI, "Z");
    else if(mod & VTERM_MOD_SHIFT)
      vterm_push_output_sprintf_ctrl(vt, C1_CSI, "1;%dZ", mod+1);
    else
      goto case_LITERAL;
    break;

  case KEYCODE_ENTER:
    /* Enter is CRLF in newline mode, but just LF in linefeed */
    if(vt->state->mode.newline)
      vterm_push_output_sprintf(vt, "\r\n");
    else
      goto case_LITERAL;
    break;

  case KEYCODE_LITERAL: case_LITERAL:
    if(mod & (VTERM_MOD_SHIFT|VTERM_MOD_CTRL))
      vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d;%du", k.literal, mod+1);
    else
      vterm_push_output_sprintf(vt, mod & VTERM_MOD_ALT ? ESC_S "%c" : "%c", k.literal);
    break;

  case KEYCODE_SS3: case_SS3:
    if(mod == 0)
      vterm_push_output_sprintf_ctrl(vt, C1_SS3, "%c", k.literal);
    else
      goto case_CSI;
    break;

  case KEYCODE_CSI: case_CSI:
    if(mod == 0)
      vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%c", k.literal);
    else
      vterm_push_output_sprintf_ctrl(vt, C1_CSI, "1;%d%c", mod + 1, k.literal);
    break;

  case KEYCODE_CSINUM:
    if(mod == 0)
      vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d%c", k.csinum, k.literal);
    else
      vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d;%d%c", k.csinum, mod + 1, k.literal);
    break;

  case KEYCODE_CSI_CURSOR:
    if(vt->state->mode.cursor)
      goto case_SS3;
    else
      goto case_CSI;

  case KEYCODE_KEYPAD:
    if(vt->state->mode.keypad) {
      k.literal = k.csinum;
      goto case_SS3;
    }
    else
      goto case_LITERAL;
  }
}

void vterm_keyboard_start_paste(VTerm *vt)
{
  if(vt->state->mode.bracketpaste)
    vterm_push_output_sprintf_ctrl(vt, C1_CSI, "200~");
}

void vterm_keyboard_end_paste(VTerm *vt)
{
  if(vt->state->mode.bracketpaste)
    vterm_push_output_sprintf_ctrl(vt, C1_CSI, "201~");
}


================================================
FILE: src/mouse.c
================================================
#include "vterm_internal.h"

#include "utf8.h"

static void output_mouse(VTermState *state, int code, int pressed, int modifiers, int col, int row)
{
  modifiers <<= 2;

  switch(state->mouse_protocol) {
  case MOUSE_X10:
    if(col + 0x21 > 0xff)
      col = 0xff - 0x21;
    if(row + 0x21 > 0xff)
      row = 0xff - 0x21;

    if(!pressed)
      code = 3;

    vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "M%c%c%c",
        (code | modifiers) + 0x20, col + 0x21, row + 0x21);
    break;

  case MOUSE_UTF8:
    {
      char utf8[18]; size_t len = 0;

      if(!pressed)
        code = 3;

      len += fill_utf8((code | modifiers) + 0x20, utf8 + len);
      len += fill_utf8(col + 0x21, utf8 + len);
      len += fill_utf8(row + 0x21, utf8 + len);
      utf8[len] = 0;

      vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "M%s", utf8);
    }
    break;

  case MOUSE_SGR:
    vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "<%d;%d;%d%c",
        code | modifiers, col + 1, row + 1, pressed ? 'M' : 'm');
    break;

  case MOUSE_RXVT:
    if(!pressed)
      code = 3;

    vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "%d;%d;%dM",
        code | modifiers, col + 1, row + 1);
    break;
  }
}

void vterm_mouse_move(VTerm *vt, int row, int col, VTermModifier mod)
{
  VTermState *state = vt->state;

  if(col == state->mouse_col && row == state->mouse_row)
    return;

  state->mouse_col = col;
  state->mouse_row = row;

  if((state->mouse_flags & MOUSE_WANT_DRAG && state->mouse_buttons) ||
     (state->mouse_flags & MOUSE_WANT_MOVE)) {
    int button = state->mouse_buttons & 0x01 ? 1 :
                 state->mouse_buttons & 0x02 ? 2 :
                 state->mouse_buttons & 0x04 ? 3 : 4;
    output_mouse(state, button-1 + 0x20, 1, mod, col, row);
  }
}

void vterm_mouse_button(VTerm *vt, int button, bool pressed, VTermModifier mod)
{
  VTermState *state = vt->state;

  int old_buttons = state->mouse_buttons;

  if(button > 0 && button <= 3) {
    if(pressed)
      state->mouse_buttons |= (1 << (button-1));
    else
      state->mouse_buttons &= ~(1 << (button-1));
  }

  /* Most of the time we don't get button releases from 4/5 */
  if(state->mouse_buttons == old_buttons && button < 4)
    return;

  if(!state->mouse_flags)
    return;

  if(button < 4) {
    output_mouse(state, button-1, pressed, mod, state->mouse_col, state->mouse_row);
  }
  else if(button < 8) {
    output_mouse(state, button-4 + 0x40, pressed, mod, state->mouse_col, state->mouse_row);
  }
}


================================================
FILE: src/parser.c
================================================
#include "vterm_internal.h"

#include <stdio.h>
#include <string.h>

#undef DEBUG_PARSER

static bool is_intermed(unsigned char c)
{
  return c >= 0x20 && c <= 0x2f;
}

static void do_control(VTerm *vt, unsigned char control)
{
  if(vt->parser.callbacks && vt->parser.callbacks->control)
    if((*vt->parser.callbacks->control)(control, vt->parser.cbdata))
      return;

  DEBUG_LOG("libvterm: Unhandled control 0x%02x\n", control);
}

static void do_csi(VTerm *vt, char command)
{
#ifdef DEBUG_PARSER
  printf("Parsed CSI args as:\n", arglen, args);
  printf(" leader: %s\n", vt->parser.v.csi.leader);
  for(int argi = 0; argi < vt->parser.v.csi.argi; argi++) {
    printf(" %lu", CSI_ARG(vt->parser.v.csi.args[argi]));
    if(!CSI_ARG_HAS_MORE(vt->parser.v.csi.args[argi]))
      printf("\n");
  printf(" intermed: %s\n", vt->parser.intermed);
  }
#endif

  if(vt->parser.callbacks && vt->parser.callbacks->csi)
    if((*vt->parser.callbacks->csi)(
          vt->parser.v.csi.leaderlen ? vt->parser.v.csi.leader : NULL, 
          vt->parser.v.csi.args,
          vt->parser.v.csi.argi,
          vt->parser.intermedlen ? vt->parser.intermed : NULL,
          command,
          vt->parser.cbdata))
      return;

  DEBUG_LOG("libvterm: Unhandled CSI %c\n", command);
}

static void do_escape(VTerm *vt, char command)
{
  char seq[INTERMED_MAX+1];

  size_t len = vt->parser.intermedlen;
  strncpy(seq, vt->parser.intermed, len);
  seq[len++] = command;
  seq[len]   = 0;

  if(vt->parser.callbacks && vt->parser.callbacks->escape)
    if((*vt->parser.callbacks->escape)(seq, len, vt->parser.cbdata))
      return;

  DEBUG_LOG("libvterm: Unhandled escape ESC 0x%02x\n", command);
}

static void string_fragment(VTerm *vt, const char *str, size_t len, bool final)
{
  VTermStringFragment frag = {
    .str     = str,
    .len     = len,
    .initial = vt->parser.string_initial,
    .final   = final,
  };

  switch(vt->parser.state) {
    case OSC:
      if(vt->parser.callbacks && vt->parser.callbacks->osc)
        (*vt->parser.callbacks->osc)(vt->parser.v.osc.command, frag, vt->parser.cbdata);
      break;

    case DCS:
      if(vt->parser.callbacks && vt->parser.callbacks->dcs)
        (*vt->parser.callbacks->dcs)(vt->parser.v.dcs.command, vt->parser.v.dcs.commandlen, frag, vt->parser.cbdata);
      break;

    case APC:
      if(vt->parser.callbacks && vt->parser.callbacks->apc)
        (*vt->parser.callbacks->apc)(frag, vt->parser.cbdata);
      break;

    case PM:
      if(vt->parser.callbacks && vt->parser.callbacks->pm)
        (*vt->parser.callbacks->pm)(frag, vt->parser.cbdata);
      break;

    case SOS:
      if(vt->parser.callbacks && vt->parser.callbacks->sos)
        (*vt->parser.callbacks->sos)(frag, vt->parser.cbdata);
      break;

    case NORMAL:
    case CSI_LEADER:
    case CSI_ARGS:
    case CSI_INTERMED:
    case OSC_COMMAND:
    case DCS_COMMAND:
      break;
  }

  vt->parser.string_initial = false;
}

size_t vterm_input_write(VTerm *vt, const char *bytes, size_t len)
{
  size_t pos = 0;
  const char *string_start;

  switch(vt->parser.state) {
  case NORMAL:
  case CSI_LEADER:
  case CSI_ARGS:
  case CSI_INTERMED:
  case OSC_COMMAND:
  case DCS_COMMAND:
    string_start = NULL;
    break;
  case OSC:
  case DCS:
  case APC:
  case PM:
  case SOS:
    string_start = bytes;
    break;
  }

#define ENTER_STATE(st)        do { vt->parser.state = st; string_start = NULL; } while(0)
#define ENTER_NORMAL_STATE()   ENTER_STATE(NORMAL)

#define IS_STRING_STATE()      (vt->parser.state >= OSC_COMMAND)

  for( ; pos < len; pos++) {
    unsigned char c = bytes[pos];
    bool c1_allowed = !vt->mode.utf8;

    if(c == 0x00 || c == 0x7f) { // NUL, DEL
      if(IS_STRING_STATE()) {
        string_fragment(vt, string_start, bytes + pos - string_start, false);
        string_start = bytes + pos + 1;
      }
      if(vt->parser.emit_nul)
        do_control(vt, c);
      continue;
    }
    if(c == 0x18 || c == 0x1a) { // CAN, SUB
      vt->parser.in_esc = false;
      ENTER_NORMAL_STATE();
      if(vt->parser.emit_nul)
        do_control(vt, c);
      continue;
    }
    else if(c == 0x1b) { // ESC
      vt->parser.intermedlen = 0;
      if(!IS_STRING_STATE())
        vt->parser.state = NORMAL;
      vt->parser.in_esc = true;
      continue;
    }
    else if(c == 0x07 &&  // BEL, can stand for ST in OSC or DCS state
            IS_STRING_STATE()) {
      // fallthrough
    }
    else if(c < 0x20) { // other C0
      if(vt->parser.state == SOS)
        continue; // All other C0s permitted in SOS

      if(IS_STRING_STATE())
        string_fragment(vt, string_start, bytes + pos - string_start, false);
      do_control(vt, c);
      if(IS_STRING_STATE())
        string_start = bytes + pos + 1;
      continue;
    }
    // else fallthrough

    size_t string_len = bytes + pos - string_start;

    if(vt->parser.in_esc) {
      // Hoist an ESC letter into a C1 if we're not in a string mode
      // Always accept ESC \ == ST even in string mode
      if(!vt->parser.intermedlen &&
          c >= 0x40 && c < 0x60 &&
          ((!IS_STRING_STATE() || c == 0x5c))) {
        c += 0x40;
        c1_allowed = true;
        if(string_len)
          string_len -= 1;
        vt->parser.in_esc = false;
      }
      else {
        string_start = NULL;
        vt->parser.state = NORMAL;
      }
    }

    switch(vt->parser.state) {
    case CSI_LEADER:
      /* Extract leader bytes 0x3c to 0x3f */
      if(c >= 0x3c && c <= 0x3f) {
        if(vt->parser.v.csi.leaderlen < CSI_LEADER_MAX-1)
          vt->parser.v.csi.leader[vt->parser.v.csi.leaderlen++] = c;
        break;
      }

      /* else fallthrough */
      vt->parser.v.csi.leader[vt->parser.v.csi.leaderlen] = 0;

      vt->parser.v.csi.argi = 0;
      vt->parser.v.csi.args[0] = CSI_ARG_MISSING;
      vt->parser.state = CSI_ARGS;

      /* fallthrough */
    case CSI_ARGS:
      /* Numerical value of argument */
      if(c >= '0' && c <= '9') {
        if(vt->parser.v.csi.args[vt->parser.v.csi.argi] == CSI_ARG_MISSING)
          vt->parser.v.csi.args[vt->parser.v.csi.argi] = 0;
        vt->parser.v.csi.args[vt->parser.v.csi.argi] *= 10;
        vt->parser.v.csi.args[vt->parser.v.csi.argi] += c - '0';
        break;
      }
      if(c == ':') {
        vt->parser.v.csi.args[vt->parser.v.csi.argi] |= CSI_ARG_FLAG_MORE;
        c = ';';
      }
      if(c == ';') {
        vt->parser.v.csi.argi++;
        vt->parser.v.csi.args[vt->parser.v.csi.argi] = CSI_ARG_MISSING;
        break;
      }

      /* else fallthrough */
      vt->parser.v.csi.argi++;
      vt->parser.intermedlen = 0;
      vt->parser.state = CSI_INTERMED;
    case CSI_INTERMED:
      if(is_intermed(c)) {
        if(vt->parser.intermedlen < INTERMED_MAX-1)
          vt->parser.intermed[vt->parser.intermedlen++] = c;
        break;
      }
      else if(c == 0x1b) {
        /* ESC in CSI cancels */
      }
      else if(c >= 0x40 && c <= 0x7e) {
        vt->parser.intermed[vt->parser.intermedlen] = 0;
        do_csi(vt, c);
      }
      /* else was invalid CSI */

      ENTER_NORMAL_STATE();
      break;

    case OSC_COMMAND:
      /* Numerical value of command */
      if(c >= '0' && c <= '9') {
        if(vt->parser.v.osc.command == -1)
          vt->parser.v.osc.command = 0;
        else
          vt->parser.v.osc.command *= 10;
        vt->parser.v.osc.command += c - '0';
        break;
      }
      if(c == ';') {
        vt->parser.state = OSC;
        string_start = bytes + pos + 1;
        break;
      }

      /* else fallthrough */
      string_start = bytes + pos;
      string_len   = 0;
      vt->parser.state = OSC;
      goto string_state;

    case DCS_COMMAND:
      if(vt->parser.v.dcs.commandlen < CSI_LEADER_MAX)
        vt->parser.v.dcs.command[vt->parser.v.dcs.commandlen++] = c;

      if(c >= 0x40 && c<= 0x7e) {
        string_start = bytes + pos + 1;
        vt->parser.state = DCS;
      }
      break;

string_state:
    case OSC:
    case DCS:
    case APC:
    case PM:
    case SOS:
      if(c == 0x07 || (c1_allowed && c == 0x9c)) {
        string_fragment(vt, string_start, string_len, true);
        ENTER_NORMAL_STATE();
      }
      break;

    case NORMAL:
      if(vt->parser.in_esc) {
        if(is_intermed(c)) {
          if(vt->parser.intermedlen < INTERMED_MAX-1)
            vt->parser.intermed[vt->parser.intermedlen++] = c;
        }
        else if(c >= 0x30 && c < 0x7f) {
          do_escape(vt, c);
          vt->parser.in_esc = 0;
          ENTER_NORMAL_STATE();
        }
        else {
          DEBUG_LOG("TODO: Unhandled byte %02x in Escape\n", c);
        }
        break;
      }
      if(c1_allowed && c >= 0x80 && c < 0xa0) {
        switch(c) {
        case 0x90: // DCS
          vt->parser.string_initial = true;
          vt->parser.v.dcs.commandlen = 0;
          ENTER_STATE(DCS_COMMAND);
          break;
        case 0x98: // SOS
          vt->parser.string_initial = true;
          ENTER_STATE(SOS);
          string_start = bytes + pos + 1;
          string_len = 0;
          break;
        case 0x9b: // CSI
          vt->parser.v.csi.leaderlen = 0;
          ENTER_STATE(CSI_LEADER);
          break;
        case 0x9d: // OSC
          vt->parser.v.osc.command = -1;
          vt->parser.string_initial = true;
          string_start = bytes + pos + 1;
          ENTER_STATE(OSC_COMMAND);
          break;
        case 0x9e: // PM
          vt->parser.string_initial = true;
          ENTER_STATE(PM);
          string_start = bytes + pos + 1;
          string_len = 0;
          break;
        case 0x9f: // APC
          vt->parser.string_initial = true;
          ENTER_STATE(APC);
          string_start = bytes + pos + 1;
          string_len = 0;
          break;
        default:
          do_control(vt, c);
          break;
        }
      }
      else {
        size_t eaten = 0;
        if(vt->parser.callbacks && vt->parser.callbacks->text)
          eaten = (*vt->parser.callbacks->text)(bytes + pos, len - pos, vt->parser.cbdata);

        if(!eaten) {
          DEBUG_LOG("libvterm: Text callback did not consume any input\n");
          /* force it to make progress */
          eaten = 1;
        }

        pos += (eaten - 1); // we'll ++ it again in a moment
      }
      break;
    }
  }

  if(string_start) {
    size_t string_len = bytes + pos - string_start;
    if(vt->parser.in_esc)
      string_len -= 1;
    string_fragment(vt, string_start, string_len, false);
  }

  return len;
}

void vterm_parser_set_callbacks(VTerm *vt, const VTermParserCallbacks *callbacks, void *user)
{
  vt->parser.callbacks = callbacks;
  vt->parser.cbdata = user;
}

void *vterm_parser_get_cbdata(VTerm *vt)
{
  return vt->parser.cbdata;
}

void vterm_parser_set_emit_nul(VTerm *vt, bool emit)
{
  vt->parser.emit_nul = emit;
}


================================================
FILE: src/pen.c
================================================
#include "vterm_internal.h"

#include <stdio.h>

/**
 * Structure used to store RGB triples without the additional metadata stored in
 * VTermColor.
 */
typedef struct {
  uint8_t red, green, blue;
} VTermRGB;

static const VTermRGB ansi_colors[] = {
  /* R    G    B */
  {   0,   0,   0 }, // black
  { 224,   0,   0 }, // red
  {   0, 224,   0 }, // green
  { 224, 224,   0 }, // yellow
  {   0,   0, 224 }, // blue
  { 224,   0, 224 }, // magenta
  {   0, 224, 224 }, // cyan
  { 224, 224, 224 }, // white == light grey

  // high intensity
  { 128, 128, 128 }, // black
  { 255,  64,  64 }, // red
  {  64, 255,  64 }, // green
  { 255, 255,  64 }, // yellow
  {  64,  64, 255 }, // blue
  { 255,  64, 255 }, // magenta
  {  64, 255, 255 }, // cyan
  { 255, 255, 255 }, // white for real
};

static int ramp6[] = {
  0x00, 0x33, 0x66, 0x99, 0xCC, 0xFF,
};

static int ramp24[] = {
  0x00, 0x0B, 0x16, 0x21, 0x2C, 0x37, 0x42, 0x4D, 0x58, 0x63, 0x6E, 0x79,
  0x85, 0x90, 0x9B, 0xA6, 0xB1, 0xBC, 0xC7, 0xD2, 0xDD, 0xE8, 0xF3, 0xFF,
};

static void lookup_default_colour_ansi(long idx, VTermColor *col)
{
  if (idx >= 0 && idx < 16) {
    vterm_color_rgb(
        col,
        ansi_colors[idx].red, ansi_colors[idx].green, ansi_colors[idx].blue);
  }
}

static bool lookup_colour_ansi(const VTermState *state, long index, VTermColor *col)
{
  if(index >= 0 && index < 16) {
    *col = state->colors[index];
    return true;
  }

  return false;
}

static bool lookup_colour_palette(const VTermState *state, long index, VTermColor *col)
{
  if(index >= 0 && index < 16) {
    // Normal 8 colours or high intensity - parse as palette 0
    return lookup_colour_ansi(state, index, col);
  }
  else if(index >= 16 && index < 232) {
    // 216-colour cube
    index -= 16;

    vterm_color_rgb(col, ramp6[index/6/6 % 6],
                         ramp6[index/6   % 6],
                         ramp6[index     % 6]);

    return true;
  }
  else if(index >= 232 && index < 256) {
    // 24 greyscales
    index -= 232;

    vterm_color_rgb(col, ramp24[index], ramp24[index], ramp24[index]);

    return true;
  }

  return false;
}

static int lookup_colour(const VTermState *state, int palette, const long args[], int argcount, VTermColor *col)
{
  switch(palette) {
  case 2: // RGB mode - 3 args contain colour values directly
    if(argcount < 3)
      return argcount;

    vterm_color_rgb(col, CSI_ARG(args[0]), CSI_ARG(args[1]), CSI_ARG(args[2]));

    return 3;

  case 5: // XTerm 256-colour mode
    if (!argcount || CSI_ARG_IS_MISSING(args[0])) {
      return argcount ? 1 : 0;
    }

    vterm_color_indexed(col, args[0]);

    return argcount ? 1 : 0;

  default:
    DEBUG_LOG("Unrecognised colour palette %d\n", palette);
    return 0;
  }
}

// Some conveniences

static void setpenattr(VTermState *state, VTermAttr attr, VTermValueType type, VTermValue *val)
{
#ifdef DEBUG
  if(type != vterm_get_attr_type(attr)) {
    DEBUG_LOG("Cannot set attr %d as it has type %d, not type %d\n",
        attr, vterm_get_attr_type(attr), type);
    return;
  }
#endif
  if(state->callbacks && state->callbacks->setpenattr)
    (*state->callbacks->setpenattr)(attr, val, state->cbdata);
}

static void setpenattr_bool(VTermState *state, VTermAttr attr, int boolean)
{
  VTermValue val = { .boolean = boolean };
  setpenattr(state, attr, VTERM_VALUETYPE_BOOL, &val);
}

static void setpenattr_int(VTermState *state, VTermAttr attr, int number)
{
  VTermValue val = { .number = number };
  setpenattr(state, attr, VTERM_VALUETYPE_INT, &val);
}

static void setpenattr_col(VTermState *state, VTermAttr attr, VTermColor color)
{
  VTermValue val = { .color = color };
  setpenattr(state, attr, VTERM_VALUETYPE_COLOR, &val);
}

static void set_pen_col_ansi(VTermState *state, VTermAttr attr, long col)
{
  VTermColor *colp = (attr == VTERM_ATTR_BACKGROUND) ? &state->pen.bg : &state->pen.fg;

  vterm_color_indexed(colp, col);

  setpenattr_col(state, attr, *colp);
}

INTERNAL void vterm_state_newpen(VTermState *state)
{
  // 90% grey so that pure white is brighter
  vterm_color_rgb(&state->default_fg, 240, 240, 240);
  vterm_color_rgb(&state->default_bg, 0, 0, 0);
  vterm_state_set_default_colors(state, &state->default_fg, &state->default_bg);

  for(int col = 0; col < 16; col++)
    lookup_default_colour_ansi(col, &state->colors[col]);
}

INTERNAL void vterm_state_resetpen(VTermState *state)
{
  state->pen.bold = 0;      setpenattr_bool(state, VTERM_ATTR_BOLD, 0);
  state->pen.underline = 0; setpenattr_int (state, VTERM_ATTR_UNDERLINE, 0);
  state->pen.italic = 0;    setpenattr_bool(state, VTERM_ATTR_ITALIC, 0);
  state->pen.blink = 0;     setpenattr_bool(state, VTERM_ATTR_BLINK, 0);
  state->pen.reverse = 0;   setpenattr_bool(state, VTERM_ATTR_REVERSE, 0);
  state->pen.conceal = 0;   setpenattr_bool(state, VTERM_ATTR_CONCEAL, 0);
  state->pen.strike = 0;    setpenattr_bool(state, VTERM_ATTR_STRIKE, 0);
  state->pen.font = 0;      setpenattr_int (state, VTERM_ATTR_FONT, 0);
  state->pen.small = 0;     setpenattr_bool(state, VTERM_ATTR_SMALL, 0);
  state->pen.baseline = 0;  setpenattr_int (state, VTERM_ATTR_BASELINE, 0);

  state->pen.fg = state->default_fg;  setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->default_fg);
  state->pen.bg = state->default_bg;  setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->default_bg);
}

INTERNAL void vterm_state_savepen(VTermState *state, int save)
{
  if(save) {
    state->saved.pen = state->pen;
  }
  else {
    state->pen = state->saved.pen;

    setpenattr_bool(state, VTERM_ATTR_BOLD,      state->pen.bold);
    setpenattr_int (state, VTERM_ATTR_UNDERLINE, state->pen.underline);
    setpenattr_bool(state, VTERM_ATTR_ITALIC,    state->pen.italic);
    setpenattr_bool(state, VTERM_ATTR_BLINK,     state->pen.blink);
    setpenattr_bool(state, VTERM_ATTR_REVERSE,   state->pen.reverse);
    setpenattr_bool(state, VTERM_ATTR_CONCEAL,   state->pen.conceal);
    setpenattr_bool(state, VTERM_ATTR_STRIKE,    state->pen.strike);
    setpenattr_int (state, VTERM_ATTR_FONT,      state->pen.font);
    setpenattr_bool(state, VTERM_ATTR_SMALL,     state->pen.small);
    setpenattr_int (state, VTERM_ATTR_BASELINE,  state->pen.baseline);

    setpenattr_col( state, VTERM_ATTR_FOREGROUND, state->pen.fg);
    setpenattr_col( state, VTERM_ATTR_BACKGROUND, state->pen.bg);
  }
}

int vterm_color_is_equal(const VTermColor *a, const VTermColor *b)
{
  /* First make sure that the two colours are of the same type (RGB/Indexed) */
  if (a->type != b->type) {
    return false;
  }

  /* Depending on the type inspect the corresponding members */
  if (VTERM_COLOR_IS_INDEXED(a)) {
    return a->indexed.idx == b->indexed.idx;
  }
  else if (VTERM_COLOR_IS_RGB(a)) {
    return    (a->rgb.red   == b->rgb.red)
           && (a->rgb.green == b->rgb.green)
           && (a->rgb.blue  == b->rgb.blue);
  }

  return 0;
}

void vterm_state_get_default_colors(const VTermState *state, VTermColor *default_fg, VTermColor *default_bg)
{
  *default_fg = state->default_fg;
  *default_bg = state->default_bg;
}

void vterm_state_get_palette_color(const VTermState *state, int index, VTermColor *col)
{
  lookup_colour_palette(state, index, col);
}

void vterm_state_set_default_colors(VTermState *state, const VTermColor *default_fg, const VTermColor *default_bg)
{
  if(default_fg) {
    state->default_fg = *default_fg;
    state->default_fg.type = (state->default_fg.type & ~VTERM_COLOR_DEFAULT_MASK)
                           | VTERM_COLOR_DEFAULT_FG;
  }

  if(default_bg) {
    state->default_bg = *default_bg;
    state->default_bg.type = (state->default_bg.type & ~VTERM_COLOR_DEFAULT_MASK)
                           | VTERM_COLOR_DEFAULT_BG;
  }
}

void vterm_state_set_palette_color(VTermState *state, int index, const VTermColor *col)
{
  if(index >= 0 && index < 16)
    state->colors[index] = *col;
}

void vterm_state_convert_color_to_rgb(const VTermState *state, VTermColor *col)
{
  if (VTERM_COLOR_IS_INDEXED(col)) { /* Convert indexed colors to RGB */
    lookup_colour_palette(state, col->indexed.idx, col);
  }
  col->type &= VTERM_COLOR_TYPE_MASK; /* Reset any metadata but the type */
}

void vterm_state_set_bold_highbright(VTermState *state, int bold_is_highbright)
{
  state->bold_is_highbright = bold_is_highbright;
}

INTERNAL void vterm_state_setpen(VTermState *state, const long args[], int argcount)
{
  // SGR - ECMA-48 8.3.117

  int argi = 0;
  int value;

  while(argi < argcount) {
    // This logic is easier to do 'done' backwards; set it true, and make it
    // false again in the 'default' case
    int done = 1;

    long arg;
    switch(arg = CSI_ARG(args[argi])) {
    case CSI_ARG_MISSING:
    case 0: // Reset
      vterm_state_resetpen(state);
      break;

    case 1: { // Bold on
      const VTermColor *fg = &state->pen.fg;
      state->pen.bold = 1;
      setpenattr_bool(state, VTERM_ATTR_BOLD, 1);
      if(!VTERM_COLOR_IS_DEFAULT_FG(fg) && VTERM_COLOR_IS_INDEXED(fg) && fg->indexed.idx < 8 && state->bold_is_highbright)
        set_pen_col_ansi(state, VTERM_ATTR_FOREGROUND, fg->indexed.idx + (state->pen.bold ? 8 : 0));
      break;
    }

    case 3: // Italic on
      state->pen.italic = 1;
      setpenattr_bool(state, VTERM_ATTR_ITALIC, 1);
      break;

    case 4: // Underline
      state->pen.underline = VTERM_UNDERLINE_SINGLE;
      if(CSI_ARG_HAS_MORE(args[argi])) {
        argi++;
        switch(CSI_ARG(args[argi])) {
          case 0:
            state->pen.underline = 0;
            break;
          case 1:
            state->pen.underline = VTERM_UNDERLINE_SINGLE;
            break;
          case 2:
            state->pen.underline = VTERM_UNDERLINE_DOUBLE;
            break;
          case 3:
            state->pen.underline = VTERM_UNDERLINE_CURLY;
            break;
        }
      }
      setpenattr_int(state, VTERM_ATTR_UNDERLINE, state->pen.underline);
      break;

    case 5: // Blink
      state->pen.blink = 1;
      setpenattr_bool(state, VTERM_ATTR_BLINK, 1);
      break;

    case 7: // Reverse on
      state->pen.reverse = 1;
      setpenattr_bool(state, VTERM_ATTR_REVERSE, 1);
      break;

    case 8: // Conceal on
      state->pen.conceal = 1;
      setpenattr_bool(state, VTERM_ATTR_CONCEAL, 1);
      break;

    case 9: // Strikethrough on
      state->pen.strike = 1;
      setpenattr_bool(state, VTERM_ATTR_STRIKE, 1);
      break;

    case 10: case 11: case 12: case 13: case 14:
    case 15: case 16: case 17: case 18: case 19: // Select font
      state->pen.font = CSI_ARG(args[argi]) - 10;
      setpenattr_int(state, VTERM_ATTR_FONT, state->pen.font);
      break;

    case 21: // Underline double
      state->pen.underline = VTERM_UNDERLINE_DOUBLE;
      setpenattr_int(state, VTERM_ATTR_UNDERLINE, state->pen.underline);
      break;

    case 22: // Bold off
      state->pen.bold = 0;
      setpenattr_bool(state, VTERM_ATTR_BOLD, 0);
      break;

    case 23: // Italic and Gothic (currently unsupported) off
      state->pen.italic = 0;
      setpenattr_bool(state, VTERM_ATTR_ITALIC, 0);
      break;

    case 24: // Underline off
      state->pen.underline = 0;
      setpenattr_int(state, VTERM_ATTR_UNDERLINE, 0);
      break;

    case 25: // Blink off
      state->pen.blink = 0;
      setpenattr_bool(state, VTERM_ATTR_BLINK, 0);
      break;

    case 27: // Reverse off
      state->pen.reverse = 0;
      setpenattr_bool(state, VTERM_ATTR_REVERSE, 0);
      break;

    case 28: // Conceal off (Reveal)
      state->pen.conceal = 0;
      setpenattr_bool(state, VTERM_ATTR_CONCEAL, 0);
      break;

    case 29: // Strikethrough off
      state->pen.strike = 0;
      setpenattr_bool(state, VTERM_ATTR_STRIKE, 0);
      break;

    case 30: case 31: case 32: case 33:
    case 34: case 35: case 36: case 37: // Foreground colour palette
      value = CSI_ARG(args[argi]) - 30;
      if(state->pen.bold && state->bold_is_highbright)
        value += 8;
      set_pen_col_ansi(state, VTERM_ATTR_FOREGROUND, value);
      break;

    case 38: // Foreground colour alternative palette
      if(argcount - argi < 1)
        return;
      argi += 1 + lookup_colour(state, CSI_ARG(args[argi+1]), args+argi+2, argcount-argi-2, &state->pen.fg);
      setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->pen.fg);
      break;

    case 39: // Foreground colour default
      state->pen.fg = state->default_fg;
      setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->pen.fg);
      break;

    case 40: case 41: case 42: case 43:
    case 44: case 45: case 46: case 47: // Background colour palette
      value = CSI_ARG(args[argi]) - 40;
      set_pen_col_ansi(state, VTERM_ATTR_BACKGROUND, value);
      break;

    case 48: // Background colour alternative palette
      if(argcount - argi < 1)
        return;
      argi += 1 + lookup_colour(state, CSI_ARG(args[argi+1]), args+argi+2, argcount-argi-2, &state->pen.bg);
      setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->pen.bg);
      break;

    case 49: // Default background
      state->pen.bg = state->default_bg;
      setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->pen.bg);
      break;

    case 73: // Superscript
    case 74: // Subscript
    case 75: // Superscript/subscript off
      state->pen.small = (arg != 75);
      state->pen.baseline =
        (arg == 73) ? VTERM_BASELINE_RAISE :
        (arg == 74) ? VTERM_BASELINE_LOWER :
                      VTERM_BASELINE_NORMAL;
      setpenattr_bool(state, VTERM_ATTR_SMALL,    state->pen.small);
      setpenattr_int (state, VTERM_ATTR_BASELINE, state->pen.baseline);
      break;

    case 90: case 91: case 92: case 93:
    case 94: case 95: case 96: case 97: // Foreground colour high-intensity palette
      value = CSI_ARG(args[argi]) - 90 + 8;
      set_pen_col_ansi(state, VTERM_ATTR_FOREGROUND, value);
      break;

    case 100: case 101: case 102: case 103:
    case 104: case 105: case 106: case 107: // Background colour high-intensity palette
      value = CSI_ARG(args[argi]) - 100 + 8;
      set_pen_col_ansi(state, VTERM_ATTR_BACKGROUND, value);
      break;

    default:
      done = 0;
      break;
    }

    if(!done)
      DEBUG_LOG("libvterm: Unhandled CSI SGR %ld\n", arg);

    while(CSI_ARG_HAS_MORE(args[argi++]));
  }
}

static int vterm_state_getpen_color(const VTermColor *col, int argi, long args[], int fg)
{
    /* Do nothing if the given color is the default color */
    if (( fg && VTERM_COLOR_IS_DEFAULT_FG(col)) ||
        (!fg && VTERM_COLOR_IS_DEFAULT_BG(col))) {
        return argi;
    }

    /* Decide whether to send an indexed color or an RGB color */
    if (VTERM_COLOR_IS_INDEXED(col)) {
        const uint8_t idx = col->indexed.idx;
        if (idx < 8) {
            args[argi++] = (idx + (fg ? 30 : 40));
        }
        else if (idx < 16) {
            args[argi++] = (idx - 8 + (fg ? 90 : 100));
        }
        else {
            args[argi++] = CSI_ARG_FLAG_MORE | (fg ? 38 : 48);
            args[argi++] = CSI_ARG_FLAG_MORE | 5;
            args[argi++] = idx;
        }
    }
    else if (VTERM_COLOR_IS_RGB(col)) {
        args[argi++] = CSI_ARG_FLAG_MORE | (fg ? 38 : 48);
        args[argi++] = CSI_ARG_FLAG_MORE | 2;
        args[argi++] = CSI_ARG_FLAG_MORE | col->rgb.red;
        args[argi++] = CSI_ARG_FLAG_MORE | col->rgb.green;
        args[argi++] = col->rgb.blue;
    }
    return argi;
}

INTERNAL int vterm_state_getpen(VTermState *state, long args[], int argcount)
{
  int argi = 0;

  if(state->pen.bold)
    args[argi++] = 1;

  if(state->pen.italic)
    args[argi++] = 3;

  if(state->pen.underline == VTERM_UNDERLINE_SINGLE)
    args[argi++] = 4;
  if(state->pen.underline == VTERM_UNDERLINE_CURLY)
    args[argi++] = 4 | CSI_ARG_FLAG_MORE, args[argi++] = 3;

  if(state->pen.blink)
    args[argi++] = 5;

  if(state->pen.reverse)
    args[argi++] = 7;

  if(state->pen.conceal)
    args[argi++] = 8;

  if(state->pen.strike)
    args[argi++] = 9;

  if(state->pen.font)
    args[argi++] = 10 + state->pen.font;

  if(state->pen.underline == VTERM_UNDERLINE_DOUBLE)
    args[argi++] = 21;

  argi = vterm_state_getpen_color(&state->pen.fg, argi, args, true);

  argi = vterm_state_getpen_color(&state->pen.bg, argi, args, false);

  if(state->pen.small) {
    if(state->pen.baseline == VTERM_BASELINE_RAISE)
      args[argi++] = 73;
    else if(state->pen.baseline == VTERM_BASELINE_LOWER)
      args[argi++] = 74;
  }

  return argi;
}

int vterm_state_get_penattr(const VTermState *state, VTermAttr attr, VTermValue *val)
{
  switch(attr) {
  case VTERM_ATTR_BOLD:
    val->boolean = state->pen.bold;
    return 1;

  case VTERM_ATTR_UNDERLINE:
    val->number = state->pen.underline;
    return 1;

  case VTERM_ATTR_ITALIC:
    val->boolean = state->pen.italic;
    return 1;

  case VTERM_ATTR_BLINK:
    val->boolean = state->pen.blink;
    return 1;

  case VTERM_ATTR_REVERSE:
    val->boolean = state->pen.reverse;
    return 1;

  case VTERM_ATTR_CONCEAL:
    val->boolean = state->pen.conceal;
    return 1;

  case VTERM_ATTR_STRIKE:
    val->boolean = state->pen.strike;
    return 1;

  case VTERM_ATTR_FONT:
    val->number = state->pen.font;
    return 1;

  case VTERM_ATTR_FOREGROUND:
    val->color = state->pen.fg;
    return 1;

  case VTERM_ATTR_BACKGROUND:
    val->color = state->pen.bg;
    return 1;

  case VTERM_ATTR_SMALL:
    val->boolean = state->pen.small;
    return 1;

  case VTERM_ATTR_BASELINE:
    val->number = state->pen.baseline;
    return 1;

  case VTERM_N_ATTRS:
    return 0;
  }

  return 0;
}


================================================
FILE: src/rect.h
================================================
/*
 * Some utility functions on VTermRect structures
 */

#define STRFrect "(%d,%d-%d,%d)"
#define ARGSrect(r) (r).start_row, (r).start_col, (r).end_row, (r).end_col

/* Expand dst to contain src as well */
static void rect_expand(VTermRect *dst, VTermRect *src)
{
  if(dst->start_row > src->start_row) dst->start_row = src->start_row;
  if(dst->start_col > src->start_col) dst->start_col = src->start_col;
  if(dst->end_row   < src->end_row)   dst->end_row   = src->end_row;
  if(dst->end_col   < src->end_col)   dst->end_col   = src->end_col;
}

/* Clip the dst to ensure it does not step outside of bounds */
static void rect_clip(VTermRect *dst, VTermRect *bounds)
{
  if(dst->start_row < bounds->start_row) dst->start_row = bounds->start_row;
  if(dst->start_col < bounds->start_col) dst->start_col = bounds->start_col;
  if(dst->end_row   > bounds->end_row)   dst->end_row   = bounds->end_row;
  if(dst->end_col   > bounds->end_col)   dst->end_col   = bounds->end_col;
  /* Ensure it doesn't end up negatively-sized */
  if(dst->end_row < dst->start_row) dst->end_row = dst->start_row;
  if(dst->end_col < dst->start_col) dst->end_col = dst->start_col;
}

/* True if the two rectangles are equal */
static int rect_equal(VTermRect *a, VTermRect *b)
{
  return (a->start_row == b->start_row) &&
         (a->start_col == b->start_col) &&
         (a->end_row   == b->end_row)   &&
         (a->end_col   == b->end_col);
}

/* True if small is contained entirely within big */
static int rect_contains(VTermRect *big, VTermRect *small)
{
  if(small->start_row < big->start_row) return 0;
  if(small->start_col < big->start_col) return 0;
  if(small->end_row   > big->end_row)   return 0;
  if(small->end_col   > big->end_col)   return 0;
  return 1;
}

/* True if the rectangles overlap at all */
static int rect_intersects(VTermRect *a, VTermRect *b)
{
  if(a->start_row > b->end_row || b->start_row > a->end_row)
    return 0;
  if(a->start_col > b->end_col || b->start_col > a->end_col)
    return 0;
  return 1;
}


================================================
FILE: src/screen.c
================================================
#include "vterm_internal.h"

#include <stdio.h>
#include <string.h>

#include "rect.h"
#include "utf8.h"

#define UNICODE_SPACE 0x20
#define UNICODE_LINEFEED 0x0a

#undef DEBUG_REFLOW

/* State of the pen at some moment in time, also used in a cell */
typedef struct
{
  /* After the bitfield */
  VTermColor   fg, bg;

  unsigned int bold      : 1;
  unsigned int underline : 2;
  unsigned int italic    : 1;
  unsigned int blink     : 1;
  unsigned int reverse   : 1;
  unsigned int conceal   : 1;
  unsigned int strike    : 1;
  unsigned int font      : 4; /* 0 to 9 */
  unsigned int small     : 1;
  unsigned int baseline  : 2;

  /* Extra state storage that isn't strictly pen-related */
  unsigned int protected_cell : 1;
  unsigned int dwl            : 1; /* on a DECDWL or DECDHL line */
  unsigned int dhl            : 2; /* on a DECDHL line (1=top 2=bottom) */
} ScreenPen;

/* Internal representation of a screen cell */
typedef struct
{
  uint32_t chars[VTERM_MAX_CHARS_PER_CELL];
  ScreenPen pen;
} ScreenCell;

struct VTermScreen
{
  VTerm *vt;
  VTermState *state;

  const VTermScreenCallbacks *callbacks;
  void *cbdata;
  bool callbacks_has_pushline4;

  VTermDamageSize damage_merge;
  /* start_row == -1 => no damage */
  VTermRect damaged;
  VTermRect pending_scrollrect;
  int pending_scroll_downward, pending_scroll_rightward;

  int rows;
  int cols;

  unsigned int global_reverse : 1;
  unsigned int reflow : 1;

  /* Primary and Altscreen. buffers[1] is lazily allocated as needed */
  ScreenCell *buffers[2];

  /* buffer will == buffers[0] or buffers[1], depending on altscreen */
  ScreenCell *buffer;

  /* buffer for a single screen row used in scrollback storage callbacks */
  VTermScreenCell *sb_buffer;

  ScreenPen pen;
};

static inline void clearcell(const VTermScreen *screen, ScreenCell *cell)
{
  cell->chars[0] = 0;
  cell->pen = screen->pen;
}

static inline ScreenCell *getcell(const VTermScreen *screen, int row, int col)
{
  if(row < 0 || row >= screen->rows)
    return NULL;
  if(col < 0 || col >= screen->cols)
    return NULL;
  return screen->buffer + (screen->cols * row) + col;
}

static ScreenCell *alloc_buffer(VTermScreen *screen, int rows, int cols)
{
  ScreenCell *new_buffer = vterm_allocator_malloc(screen->vt, sizeof(ScreenCell) * rows * cols);

  for(int row = 0; row < rows; row++) {
    for(int col = 0; col < cols; col++) {
      clearcell(screen, &new_buffer[row * cols + col]);
    }
  }

  return new_buffer;
}

static void damagerect(VTermScreen *screen, VTermRect rect)
{
  VTermRect emit;

  switch(screen->damage_merge) {
  case VTERM_DAMAGE_CELL:
    /* Always emit damage event */
    emit = rect;
    break;

  case VTERM_DAMAGE_ROW:
    /* Emit damage longer than one row. Try to merge with existing damage in
     * the same row */
    if(rect.end_row > rect.start_row + 1) {
      // Bigger than 1 line - flush existing, emit this
      vterm_screen_flush_damage(screen);
      emit = rect;
    }
    else if(screen->damaged.start_row == -1) {
      // None stored yet
      screen->damaged = rect;
      return;
    }
    else if(rect.start_row == screen->damaged.start_row) {
      // Merge with the stored line
      if(screen->damaged.start_col > rect.start_col)
        screen->damaged.start_col = rect.start_col;
      if(screen->damaged.end_col < rect.end_col)
        screen->damaged.end_col = rect.end_col;
      return;
    }
    else {
      // Emit the currently stored line, store a new one
      emit = screen->damaged;
      screen->damaged = rect;
    }
    break;

  case VTERM_DAMAGE_SCREEN:
  case VTERM_DAMAGE_SCROLL:
    /* Never emit damage event */
    if(screen->damaged.start_row == -1)
      screen->damaged = rect;
    else {
      rect_expand(&screen->damaged, &rect);
    }
    return;

  default:
    DEBUG_LOG("TODO: Maybe merge damage for level %d\n", screen->damage_merge);
    return;
  }

  if(screen->callbacks && screen->callbacks->damage)
    (*screen->callbacks->damage)(emit, screen->cbdata);
}

static void damagescreen(VTermScreen *screen)
{
  VTermRect rect = {
    .start_row = 0,
    .end_row   = screen->rows,
    .start_col = 0,
    .end_col   = screen->cols,
  };

  damagerect(screen, rect);
}

static int putglyph(VTermGlyphInfo *info, VTermPos pos, void *user)
{
  VTermScreen *screen = user;
  ScreenCell *cell = getcell(screen, pos.row, pos.col);

  if(!cell)
    return 0;

  int i;
  for(i = 0; i < VTERM_MAX_CHARS_PER_CELL && info->chars[i]; i++) {
    cell->chars[i] = info->chars[i];
    cell->pen = screen->pen;
  }
  if(i < VTERM_MAX_CHARS_PER_CELL)
    cell->chars[i] = 0;

  for(int col = 1; col < info->width; col++)
    getcell(screen, pos.row, pos.col + col)->chars[0] = (uint32_t)-1;

  VTermRect rect = {
    .start_row = pos.row,
    .end_row   = pos.row+1,
    .start_col = pos.col,
    .end_col   = pos.col+info->width,
  };

  cell->pen.protected_cell = info->protected_cell;
  cell->pen.dwl            = info->dwl;
  cell->pen.dhl            = info->dhl;

  damagerect(screen, rect);

  return 1;
}

static void sb_pushline_from_row(VTermScreen *screen, int row, bool continuation)
{
  VTermPos pos = { .row = row };
  for(pos.col = 0; pos.col < screen->cols; pos.col++)
    vterm_screen_get_cell(screen, pos, screen->sb_buffer + pos.col);

  if(screen->callbacks_has_pushline4 && screen->callbacks->sb_pushline4)
    (screen->callbacks->sb_pushline4)(screen->cols, screen->sb_buffer, continuation, screen->cbdata);
  else
    (screen->callbacks->sb_pushline)(screen->cols, screen->sb_buffer, screen->cbdata);
}

static int premove(VTermRect rect, void *user)
{
  VTermScreen *screen = user;

  if(((screen->callbacks && screen->callbacks->sb_pushline) ||
        (screen->callbacks_has_pushline4 && screen->callbacks && screen->callbacks->sb_pushline4)) &&
     rect.start_row == 0 && rect.start_col == 0 &&        // starts top-left corner
     rect.end_col == screen->cols &&                      // full width
     screen->buffer == screen->buffers[BUFIDX_PRIMARY]) { // not altscreen
    for(int row = 0; row < rect.end_row; row++) {
      const VTermLineInfo *lineinfo = vterm_state_get_lineinfo(screen->state, row);
      sb_pushline_from_row(screen, row, lineinfo->continuation);
    }
  }

  return 1;
}

static int moverect_internal(VTermRect dest, VTermRect src, void *user)
{
  VTermScreen *screen = user;

  int cols = src.end_col - src.start_col;
  int downward = src.start_row - dest.start_row;

  int init_row, test_row, inc_row;
  if(downward < 0) {
    init_row = dest.end_row - 1;
    test_row = dest.start_row - 1;
    inc_row  = -1;
  }
  else {
    init_row = dest.start_row;
    test_row = dest.end_row;
    inc_row  = +1;
  }

  for(int row = init_row; row != test_row; row += inc_row)
    memmove(getcell(screen, row, dest.start_col),
            getcell(screen, row + downward, src.start_col),
            cols * sizeof(ScreenCell));

  return 1;
}

static int moverect_user(VTermRect dest, VTermRect src, void *user)
{
  VTermScreen *screen = user;

  if(screen->callbacks && screen->callbacks->moverect) {
    if(screen->damage_merge != VTERM_DAMAGE_SCROLL)
      // Avoid an infinite loop
      vterm_screen_flush_damage(screen);

    if((*screen->callbacks->moverect)(dest, src, screen->cbdata))
      return 1;
  }

  damagerect(screen, dest);

  return 1;
}

static int erase_internal(VTermRect rect, int selective, void *user)
{
  VTermScreen *screen = user;

  for(int row = rect.start_row; row < screen->state->rows && row < rect.end_row; row++) {
    const VTermLineInfo *info = vterm_state_get_lineinfo(screen->state, row);

    for(int col = rect.start_col; col < rect.end_col; col++) {
      ScreenCell *cell = getcell(screen, row, col);

      if(selective && cell->pen.protected_cell)
        continue;

      cell->chars[0] = 0;
      cell->pen = (ScreenPen){
        /* Only copy .fg and .bg; leave things like rv in reset state */
        .fg = screen->pen.fg,
        .bg = screen->pen.bg,
      };
      cell->pen.dwl = info->doublewidth;
      cell->pen.dhl = info->doubleheight;
    }
  }

  return 1;
}

static int erase_user(VTermRect rect, int selective, void *user)
{
  VTermScreen *screen = user;

  damagerect(screen, rect);

  return 1;
}

static int erase(VTermRect rect, int selective, void *user)
{
  erase_internal(rect, selective, user);
  return erase_user(rect, 0, user);
}

static int scrollrect(VTermRect rect, int downward, int rightward, void *user)
{
  VTermScreen *screen = user;

  if(screen->damage_merge != VTERM_DAMAGE_SCROLL) {
    vterm_scroll_rect(rect, downward, rightward,
        moverect_internal, erase_internal, screen);

    vterm_screen_flush_damage(screen);

    vterm_scroll_rect(rect, downward, rightward,
        moverect_user, erase_user, screen);

    return 1;
  }

  if(screen->damaged.start_row != -1 &&
     !rect_intersects(&rect, &screen->damaged)) {
    vterm_screen_flush_damage(screen);
  }

  if(screen->pending_scrollrect.start_row == -1) {
    screen->pending_scrollrect = rect;
    screen->pending_scroll_downward  = downward;
    screen->pending_scroll_rightward = rightward;
  }
  else if(rect_equal(&screen->pending_scrollrect, &rect) &&
     ((screen->pending_scroll_downward  == 0 && downward  == 0) ||
      (screen->pending_scroll_rightward == 0 && rightward == 0))) {
    screen->pending_scroll_downward  += downward;
    screen->pending_scroll_rightward += rightward;
  }
  else {
    vterm_screen_flush_damage(screen);

    screen->pending_scrollrect = rect;
    screen->pending_scroll_downward  = downward;
    screen->pending_scroll_rightward = rightward;
  }

  vterm_scroll_rect(rect, downward, rightward,
      moverect_internal, erase_internal, screen);

  if(screen->damaged.start_row == -1)
    return 1;

  if(rect_contains(&rect, &screen->damaged)) {
    /* Scroll region entirely contains the damage; just move it */
    vterm_rect_move(&screen->damaged, -downward, -rightward);
    rect_clip(&screen->damaged, &rect);
  }
  /* There are a number of possible cases here, but lets restrict this to only
   * the common case where we might actually gain some performance by
   * optimising it. Namely, a vertical scroll that neatly cuts the damage
   * region in half.
   */
  else if(rect.start_col <= screen->damaged.start_col &&
          rect.end_col   >= screen->damaged.end_col &&
          rightward == 0) {
    if(screen->damaged.start_row >= rect.start_row &&
       screen->damaged.start_row  < rect.end_row) {
      screen->damaged.start_row -= downward;
      if(screen->damaged.start_row < rect.start_row)
        screen->damaged.start_row = rect.start_row;
      if(screen->damaged.start_row > rect.end_row)
        screen->damaged.start_row = rect.end_row;
    }
    if(screen->damaged.end_row >= rect.start_row &&
       screen->damaged.end_row  < rect.end_row) {
      screen->damaged.end_row -= downward;
      if(screen->damaged.end_row < rect.start_row)
        screen->damaged.end_row = rect.start_row;
      if(screen->damaged.end_row > rect.end_row)
        screen->damaged.end_row = rect.end_row;
    }
  }
  else {
    DEBUG_LOG("TODO: Just flush and redo damaged=" STRFrect " rect=" STRFrect "\n",
        ARGSrect(screen->damaged), ARGSrect(rect));
  }

  return 1;
}

static int movecursor(VTermPos pos, VTermPos oldpos, int visible, void *user)
{
  VTermScreen *screen = user;

  if(screen->callbacks && screen->callbacks->movecursor)
    return (*screen->callbacks->movecursor)(pos, oldpos, visible, screen->cbdata);

  return 0;
}

static int setpenattr(VTermAttr attr, VTermValue *val, void *user)
{
  VTermScreen *screen = user;

  switch(attr) {
  case VTERM_ATTR_BOLD:
    screen->pen.bold = val->boolean;
    return 1;
  case VTERM_ATTR_UNDERLINE:
    screen->pen.underline = val->number;
    return 1;
  case VTERM_ATTR_ITALIC:
    screen->pen.italic = val->boolean;
    return 1;
  case VTERM_ATTR_BLINK:
    screen->pen.blink = val->boolean;
    return 1;
  case VTERM_ATTR_REVERSE:
    screen->pen.reverse = val->boolean;
    return 1;
  case VTERM_ATTR_CONCEAL:
    screen->pen.conceal = val->boolean;
    return 1;
  case VTERM_ATTR_STRIKE:
    screen->pen.strike = val->boolean;
    return 1;
  case VTERM_ATTR_FONT:
    screen->pen.font = val->number;
    return 1;
  case VTERM_ATTR_FOREGROUND:
    screen->pen.fg = val->color;
    return 1;
  case VTERM_ATTR_BACKGROUND:
    screen->pen.bg = val->color;
    return 1;
  case VTERM_ATTR_SMALL:
    screen->pen.small = val->boolean;
    return 1;
  case VTERM_ATTR_BASELINE:
    screen->pen.baseline = val->number;
    return 1;

  case VTERM_N_ATTRS:
    return 0;
  }

  return 0;
}

static int settermprop(VTermProp prop, VTermValue *val, void *user)
{
  VTermScreen *screen = user;

  switch(prop) {
  case VTERM_PROP_ALTSCREEN:
    if(val->boolean && !screen->buffers[BUFIDX_ALTSCREEN])
      return 0;

    screen->buffer = val->boolean ? screen->buffers[BUFIDX_ALTSCREEN] : screen->buffers[BUFIDX_PRIMARY];
    /* only send a damage event on disable; because during enable there's an
     * erase that sends a damage anyway
     */
    if(!val->boolean)
      damagescreen(screen);
    break;
  case VTERM_PROP_REVERSE:
    screen->global_reverse = val->boolean;
    damagescreen(screen);
    break;
  default:
    ; /* ignore */
  }

  if(screen->callbacks && screen->callbacks->settermprop)
    return (*screen->callbacks->settermprop)(prop, val, screen->cbdata);

  return 1;
}

static int bell(void *user)
{
  VTermScreen *screen = user;

  if(screen->callbacks && screen->callbacks->bell)
    return (*screen->callbacks->bell)(screen->cbdata);

  return 0;
}

/* How many cells are non-blank
 * Returns the position of the first blank cell in the trailing blank end */
static int line_popcount(ScreenCell *buffer, int row, int rows, int cols)
{
  int col = cols - 1;
  while(col >= 0 && buffer[row * cols + col].chars[0] == 0)
    col--;
  return col + 1;
}

static void resize_buffer(VTermScreen *screen, int bufidx, int new_rows, int new_cols, bool active, VTermStateFields *statefields)
{
  int old_rows = screen->rows;
  int old_cols = screen->cols;

  ScreenCell *old_buffer = screen->buffers[bufidx];
  VTermLineInfo *old_lineinfo = statefields->lineinfos[bufidx];

  ScreenCell *new_buffer = vterm_allocator_malloc(screen->vt, sizeof(ScreenCell) * new_rows * new_cols);
  VTermLineInfo *new_lineinfo = vterm_allocator_malloc(screen->vt, sizeof(new_lineinfo[0]) * new_rows);

  int old_row = old_rows - 1;
  int new_row = new_rows - 1;

  VTermPos old_cursor = statefields->pos;
  VTermPos new_cursor = { -1, -1 };

#ifdef DEBUG_REFLOW
  fprintf(stderr, "Resizing from %dx%d to %dx%d; cursor was at (%d,%d)\n",
      old_cols, old_rows, new_cols, new_rows, old_cursor.col, old_cursor.row);
#endif

  /* Keep track of the final row that is knonw to be blank, so we know what
   * spare space we have for scrolling into
   */
  int final_blank_row = new_rows;

  while(old_row >= 0) {
    int old_row_end = old_row;
    /* TODO: Stop if dwl or dhl */
    while(screen->reflow && old_lineinfo && old_row >= 0 && old_lineinfo[old_row].continuation)
      old_row--;
    int old_row_start = old_row;

    int width = 0;
    for(int row = old_row_start; row <= old_row_end; row++) {
      if(screen->reflow && row < (old_rows - 1) && old_lineinfo[row + 1].continuation)
        width += old_cols;
      else
        width += line_popcount(old_buffer, row, old_rows, old_cols);
    }

    if(final_blank_row == (new_row + 1) && width == 0)
      final_blank_row = new_row;

    int new_height = screen->reflow
      ? width ? (width + new_cols - 1) / new_cols : 1
      : 1;

    int new_row_end = new_row;
    int new_row_start = new_row - new_height + 1;

    old_row = old_row_start;
    int old_col = 0;

    int spare_rows = new_rows - final_blank_row;

    if(new_row_start < 0 && /* we'd fall off the top */
        spare_rows >= 0 && /* we actually have spare rows */
        (!active || new_cursor.row == -1 || (new_cursor.row - new_row_start) < new_rows))
    {
      /* Attempt to scroll content down into the blank rows at the bottom to
       * make it fit
       */
      int downwards = -new_row_start;
      if(downwards > spare_rows)
        downwards = spare_rows;
      int rowcount = new_rows - downwards;

#ifdef DEBUG_REFLOW
      fprintf(stderr, "  scroll %d rows +%d downwards\n", rowcount, downwards);
#endif

      memmove(&new_buffer[downwards * new_cols], &new_buffer[0],   rowcount * new_cols * sizeof(ScreenCell));
      memmove(&new_lineinfo[downwards],          &new_lineinfo[0], rowcount            * sizeof(new_lineinfo[0]));

      new_row += downwards;
      new_row_start += downwards;
      new_row_end += downwards;

      if(new_cursor.row >= 0)
        new_cursor.row += downwards;

      final_blank_row += downwards;
    }

#ifdef DEBUG_REFLOW
    fprintf(stderr, "  rows [%d..%d] <- [%d..%d] width=%d\n",
        new_row_start, new_row_end, old_row_start, old_row_end, width);
#endif

    if(new_row_start < 0) {
      if(old_row_start <= old_cursor.row && old_cursor.row < old_row_end) {
        new_cursor.row = 0;
        new_cursor.col = old_cursor.col;
        if(new_cursor.col >= new_cols)
          new_cursor.col = new_cols-1;
      }
      break;
    }

    for(new_row = new_row_start, old_row = old_row_start; new_row <= new_row_end; new_row++) {
      int count = width >= new_cols ? new_cols : width;
      width -= count;

      int new_col = 0;

      while(count) {
        /* TODO: This could surely be done a lot faster by memcpy()'ing the entire range */
        new_buffer[new_row * new_cols + new_col] = old_buffer[old_row * old_cols + old_col];

        if(old_cursor.row == old_row && old_cursor.col == old_col)
          new_cursor.row = new_row, new_cursor.col = new_col;

        old_col++;
        if(old_col == old_cols) {
          old_row++;

          if(!screen->reflow) {
            new_col++;
            break;
          }
          old_col = 0;
        }

        new_col++;
        count--;
      }

      if(old_cursor.row == old_row && old_cursor.col >= old_col) {
        new_cursor.row = new_row, new_cursor.col = (old_cursor.col - old_col + new_col);
        if(new_cursor.col >= new_cols)
          new_cursor.col = new_cols-1;
      }

      while(new_col < new_cols) {
        clearcell(screen, &new_buffer[new_row * new_cols + new_col]);
        new_col++;
      }

      new_lineinfo[new_row].continuation = (new_row > new_row_start);
    }

    old_row = old_row_start - 1;
    new_row = new_row_start - 1;
  }

  if(old_cursor.row <= old_row) {
    /* cursor would have moved entirely off the top of the screen; lets just
     * bring it within range */
    new_cursor.row = 0, new_cursor.col = old_cursor.col;
    if(new_cursor.col >= new_cols)
      new_cursor.col = new_cols-1;
  }

  /* We really expect the cursor position to be set by now */
  if(active && (new_cursor.row == -1 || new_cursor.col == -1)) {
    fprintf(stderr, "screen_resize failed to update cursor position\n");
    abort();
  }

  if(old_row >= 0 && bufidx == BUFIDX_PRIMARY) {
    /* Push spare lines to scrollback buffer */
    if((screen->callbacks && screen->callbacks->sb_pushline) ||
          (screen->callbacks_has_pushline4 && screen->callbacks && screen->callbacks->sb_pushline4))
      for(int row = 0; row <= old_row; row++) {
        const VTermLineInfo *lineinfo = old_lineinfo + row;
        sb_pushline_from_row(screen, row, lineinfo->continuation);
      }
    if(active)
      statefields->pos.row -= (old_row + 1);
  }
  if(new_row >= 0 && bufidx == BUFIDX_PRIMARY &&
      screen->callbacks && screen->callbacks->sb_popline) {
    /* Try to backfill rows by popping scrollback buffer */
    while(new_row >= 0) {
      if(!(screen->callbacks->sb_popline(old_cols, screen->sb_buffer, screen->cbdata)))
        break;

      VTermPos pos = { .row = new_row };
      for(pos.col = 0; pos.col < old_cols && pos.col < new_cols; pos.col += screen->sb_buffer[pos.col].width) {
        VTermScreenCell *src = &screen->sb_buffer[pos.col];
        ScreenCell *dst = &new_buffer[pos.row * new_cols + pos.col];

        for(int i = 0; i < VTERM_MAX_CHARS_PER_CELL; i++) {
          dst->chars[i] = src->chars[i];
          if(!src->chars[i])
            break;
        }

        dst->pen.bold      = src->attrs.bold;
        dst->pen.underline = src->attrs.underline;
        dst->pen.italic    = src->attrs.italic;
        dst->pen.blink     = src->attrs.blink;
        dst->pen.reverse   = src->attrs.reverse ^ screen->global_reverse;
        dst->pen.conceal   = src->attrs.conceal;
        dst->pen.strike    = src->attrs.strike;
        dst->pen.font      = src->attrs.font;
        dst->pen.small     = src->attrs.small;
        dst->pen.baseline  = src->attrs.baseline;

        dst->pen.fg = src->fg;
        dst->pen.bg = src->bg;

        if(src->width == 2 && pos.col < (new_cols-1))
          (dst + 1)->chars[0] = (uint32_t) -1;
      }
      for( ; pos.col < new_cols; pos.col++)
        clearcell(screen, &new_buffer[pos.row * new_cols + pos.col]);
      new_row--;

      if(active)
        statefields->pos.row++;
    }
  }
  if(new_row >= 0) {
    /* Scroll new rows back up to the top and fill in blanks at the bottom */
    int moverows = new_rows - new_row - 1;
    memmove(&new_buffer[0], &new_buffer[(new_row + 1) * new_cols], moverows * new_cols * sizeof(ScreenCell));
    memmove(&new_lineinfo[0], &new_lineinfo[new_row + 1], moverows * sizeof(new_lineinfo[0]));

    new_cursor.row -= (new_row + 1);

    for(new_row = moverows; new_row < new_rows; new_row++) {
      for(int col = 0; col < new_cols; col++)
        clearcell(screen, &new_buffer[new_row * new_cols + col]);
      new_lineinfo[new_row] = (VTermLineInfo){ 0 };
    }
  }

  vterm_allocator_free(screen->vt, old_buffer);
  screen->buffers[bufidx] = new_buffer;

  vterm_allocator_free(screen->vt, old_lineinfo);
  statefields->lineinfos[bufidx] = new_lineinfo;

  if(active)
    statefields->pos = new_cursor;

  return;
}

static int resize(int new_rows, int new_cols, VTermStateFields *fields, void *user)
{
  VTermScreen *screen = user;

  int altscreen_active = (screen->buffers[BUFIDX_ALTSCREEN] && screen->buffer == screen->buffers[BUFIDX_ALTSCREEN]);

  int old_rows = screen->rows;
  int old_cols = screen->cols;

  if(new_cols > old_cols) {
    /* Ensure that ->sb_buffer is large enough for a new or and old row */
    if(screen->sb_buffer)
      vterm_allocator_free(screen->vt, screen->sb_buffer);

    screen->sb_buffer = vterm_allocator_malloc(screen->vt, sizeof(VTermScreenCell) * new_cols);
  }

  resize_buffer(screen, 0, new_rows, new_cols, !altscreen_active, fields);
  if(screen->buffers[BUFIDX_ALTSCREEN])
    resize_buffer(screen, 1, new_rows, new_cols, altscreen_active, fields);
  else if(new_rows != old_rows) {
    /* We don't need a full resize of the altscreen because it isn't enabled
     * but we should at least keep the lineinfo the right size */
    vterm_allocator_free(screen->vt, fields->lineinfos[BUFIDX_ALTSCREEN]);

    VTermLineInfo *new_lineinfo = vterm_allocator_malloc(screen->vt, sizeof(new_lineinfo[0]) * new_rows);
    for(int row = 0; row < new_rows; row++)
      new_lineinfo[row] = (VTermLineInfo){ 0 };

    fields->lineinfos[BUFIDX_ALTSCREEN] = new_lineinfo;
  }

  screen->buffer = altscreen_active ? screen->buffers[BUFIDX_ALTSCREEN] : screen->buffers[BUFIDX_PRIMARY];

  screen->rows = new_rows;
  screen->cols = new_cols;

  if(new_cols <= old_cols) {
    if(screen->sb_buffer)
      vterm_allocator_free(screen->vt, screen->sb_buffer);

    screen->sb_buffer = vterm_allocator_malloc(screen->vt, sizeof(VTermScreenCell) * new_cols);
  }

  /* TODO: Maaaaybe we can optimise this if there's no reflow happening */
  damagescreen(screen);

  if(screen->callbacks && screen->callbacks->resize)
    return (*screen->callbacks->resize)(new_rows, new_cols, screen->cbdata);

  return 1;
}

static int setlineinfo(int row, const VTermLineInfo *newinfo, const VTermLineInfo *oldinfo, void *user)
{
  VTermScreen *screen = user;

  if(newinfo->doublewidth != oldinfo->doublewidth ||
     newinfo->doubleheight != oldinfo->doubleheight) {
    for(int col = 0; col < screen->cols; col++) {
      ScreenCell *cell = getcell(screen, row, col);
      cell->pen.dwl = newinfo->doublewidth;
      cell->pen.dhl = newinfo->doubleheight;
    }

    VTermRect rect = {
      .start_row = row,
      .end_row   = row + 1,
      .start_col = 0,
      .end_col   = newinfo->doublewidth ? screen->cols / 2 : screen->cols,
    };
    damagerect(screen, rect);

    if(newinfo->doublewidth) {
      rect.start_col = screen->cols / 2;
      rect.end_col   = screen->cols;

      erase_internal(rect, 0, user);
    }
  }

  return 1;
}

static int sb_clear(void *user) {
  VTermScreen *screen = user;

  if(screen->callbacks && screen->callbacks->sb_clear)
    if((*screen->callbacks->sb_clear)(screen->cbdata))
      return 1;

  return 0;
}

static VTermStateCallbacks state_cbs = {
  .putglyph    = &putglyph,
  .movecursor  = &movecursor,
  .premove     = &premove,
  .scrollrect  = &scrollrect,
  .erase       = &erase,
  .setpenattr  = &setpenattr,
  .settermprop = &settermprop,
  .bell        = &bell,
  .resize      = &resize,
  .setlineinfo = &setlineinfo,
  .sb_clear    = &sb_clear,
};

static VTermScreen *screen_new(VTerm *vt)
{
  VTermState *state = vterm_obtain_state(vt);
  if(!state)
    return NULL;

  VTermScreen *screen = vterm_allocator_malloc(vt, sizeof(VTermScreen));
  int rows, cols;

  vterm_get_size(vt, &rows, &cols);

  screen->vt = vt;
  screen->state = state;

  screen->damage_merge = VTERM_DAMAGE_CELL;
  screen->damaged.start_row = -1;
  screen->pending_scrollrect.start_row = -1;

  screen->rows = rows;
  screen->cols = cols;

  screen->global_reverse = false;
  screen->reflow = false;

  screen->callbacks = NULL;
  screen->cbdata    = NULL;
  screen->callbacks_has_pushline4 = false;

  screen->buffers[BUFIDX_PRIMARY] = alloc_buffer(screen, rows, cols);

  screen->buffer = screen->buffers[BUFIDX_PRIMARY];

  screen->sb_buffer = vterm_allocator_malloc(screen->vt, sizeof(VTermScreenCell) * cols);

  vterm_state_set_callbacks(screen->state, &state_cbs, screen);
  vterm_state_callbacks_has_premove(screen->state);

  return screen;
}

INTERNAL void vterm_screen_free(VTermScreen *screen)
{
  vterm_allocator_free(screen->vt, screen->buffers[BUFIDX_PRIMARY]);
  if(screen->buffers[BUFIDX_ALTSCREEN])
    vterm_allocator_free(screen->vt, screen->buffers[BUFIDX_ALTSCREEN]);

  vterm_allocator_free(screen->vt, screen->sb_buffer);

  vterm_allocator_free(screen->vt, screen);
}

void vterm_screen_reset(VTermScreen *screen, int hard)
{
  screen->damaged.start_row = -1;
  screen->pending_scrollrect.start_row = -1;
  vterm_state_reset(screen->state, hard);
  vterm_screen_flush_damage(screen);
}

static size_t _get_chars(const VTermScreen *screen, const int utf8, void *buffer, size_t len, const VTermRect rect)
{
  size_t outpos = 0;
  int padding = 0;

#define PUT(c)                                             \
  if(utf8) {                                               \
    size_t thislen = utf8_seqlen(c);                       \
    if(buffer && outpos + thislen <= len)                  \
      outpos += fill_utf8((c), (char *)buffer + outpos);   \
    else                                                   \
      outpos += thislen;                                   \
  }                                                        \
  else {                                                   \
    if(buffer && outpos + 1 <= len)                        \
      ((uint32_t*)buffer)[outpos++] = (c);                 \
    else                                                   \
      outpos++;                                            \
  }

  for(int row = rect.start_row; row < rect.end_row; row++) {
    for(int col = rect.start_col; col < rect.end_col; col++) {
      ScreenCell *cell = getcell(screen, row, col);

      if(cell->chars[0] == 0)
        // Erased cell, might need a space
        padding++;
      else if(cell->chars[0] == (uint32_t)-1)
        // Gap behind a double-width char, do nothing
        ;
      else {
        while(padding) {
          PUT(UNICODE_SPACE);
          padding--;
        }
        for(int i = 0; i < VTERM_MAX_CHARS_PER_CELL && cell->chars[i]; i++) {
          PUT(cell->chars[i]);
        }
      }
    }

    if(row < rect.end_row - 1) {
      PUT(UNICODE_LINEFEED);
      padding = 0;
    }
  }

  return outpos;
}

size_t vterm_screen_get_chars(const VTermScreen *screen, uint32_t *chars, size_t len, const VTermRect rect)
{
  return _get_chars(screen, 0, chars, len, rect);
}

size_t vterm_screen_get_text(const VTermScreen *screen, char *str, size_t len, const VTermRect rect)
{
  return _get_chars(screen, 1, str, len, rect);
}

/* Copy internal to external representation of a screen cell */
int vterm_screen_get_cell(const VTermScreen *screen, VTermPos pos, VTermScreenCell *cell)
{
  ScreenCell *intcell = getcell(screen, pos.row, pos.col);
  if(!intcell)
    return 0;

  for(int i = 0; i < VTERM_MAX_CHARS_PER_CELL; i++) {
    cell->chars[i] = intcell->chars[i];
    if(!intcell->chars[i])
      break;
  }

  cell->attrs.bold      = intcell->pen.bold;
  cell->attrs.underline = intcell->pen.underline;
  cell->attrs.italic    = intcell->pen.italic;
  cell->attrs.blink     = intcell->pen.blink;
  cell->attrs.reverse   = intcell->pen.reverse ^ screen->global_reverse;
  cell->attrs.conceal   = intcell->pen.conceal;
  cell->attrs.strike    = intcell->pen.strike;
  cell->attrs.font      = intcell->pen.font;
  cell->attrs.small     = intcell->pen.small;
  cell->attrs.baseline  = intcell->pen.baseline;

  cell->attrs.dwl = intcell->pen.dwl;
  cell->attrs.dhl = intcell->pen.dhl;

  cell->fg = intcell->pen.fg;
  cell->bg = intcell->pen.bg;

  if(pos.col < (screen->cols - 1) &&
     getcell(screen, pos.row, pos.col + 1)->chars[0] == (uint32_t)-1)
    cell->width = 2;
  else
    cell->width = 1;

  return 1;
}

int vterm_screen_is_eol(const VTermScreen *screen, VTermPos pos)
{
  /* This cell is EOL if this and every cell to the right is black */
  for(; pos.col < screen->cols; pos.col++) {
    ScreenCell *cell = getcell(screen, pos.row, pos.col);
    if(cell->chars[0] != 0)
      return 0;
  }

  return 1;
}

VTermScreen *vterm_obtain_screen(VTerm *vt)
{
  if(vt->screen)
    return vt->screen;

  VTermScreen *screen = screen_new(vt);
  vt->screen = screen;

  return screen;
}

void vterm_screen_enable_reflow(VTermScreen *screen, bool reflow)
{
  screen->reflow = reflow;
}

#undef vterm_screen_set_reflow
void vterm_screen_set_reflow(VTermScreen *screen, bool reflow)
{
  vterm_screen_enable_reflow(screen, reflow);
}

void vterm_screen_enable_altscreen(VTermScreen *screen, int altscreen)
{
  if(!screen->buffers[BUFIDX_ALTSCREEN] && altscreen) {
    int rows, cols;
    vterm_get_size(screen->vt, &rows, &cols);

    screen->buffers[BUFIDX_ALTSCREEN] = alloc_buffer(screen, rows, cols);
  }
}

void vterm_screen_set_callbacks(VTermScreen *screen, const VTermScreenCallbacks *callbacks, void *user)
{
  screen->callbacks = callbacks;
  screen->cbdata = user;
}

void *vterm_screen_get_cbdata(VTermScreen *screen)
{
  return screen->cbdata;
}

void vterm_screen_callbacks_has_pushline4(VTermScreen *screen)
{
  screen->callbacks_has_pushline4 = true;
}

void vterm_screen_set_unrecognised_fallbacks(VTermScreen *screen, const VTermStateFallbacks *fallbacks, void *user)
{
  vterm_state_set_unrecognised_fallbacks(screen->state, fallbacks, user);
}

void *vterm_screen_get_unrecognised_fbdata(VTermScreen *screen)
{
  return vterm_state_get_unrecognised_fbdata(screen->state);
}

void vterm_screen_flush_damage(VTermScreen *screen)
{
  if(screen->pending_scrollrect.start_row != -1) {
    vterm_scroll_rect(screen->pending_scrollrect, screen->pending_scroll_downward, screen->pending_scroll_rightward,
        moverect_user, erase_user, screen);

    screen->pending_scrollrect.start_row = -1;
  }

  if(screen->damaged.start_row != -1) {
    if(screen->callbacks && screen->callbacks->damage)
      (*screen->callbacks->damage)(screen->damaged, screen->cbdata);

    screen->damaged.start_row = -1;
  }
}

void vterm_screen_set_damage_merge(VTermScreen *screen, VTermDamageSize size)
{
  vterm_screen_flush_damage(screen);
  screen->damage_merge = size;
}

static int attrs_differ(VTermAttrMask attrs, ScreenCell *a, ScreenCell *b)
{
  if((attrs & VTERM_ATTR_BOLD_MASK)       && (a->pen.bold != b->pen.bold))
    return 1;
  if((attrs & VTERM_ATTR_UNDERLINE_MASK)  && (a->pen.underline != b->pen.underline))
    return 1;
  if((attrs & VTERM_ATTR_ITALIC_MASK)     && (a->pen.italic != b->pen.italic))
    return 1;
  if((attrs & VTERM_ATTR_BLINK_MASK)      && (a->pen.blink != b->pen.blink))
    return 1;
  if((attrs & VTERM_ATTR_REVERSE_MASK)    && (a->pen.reverse != b->pen.reverse))
    return 1;
  if((attrs & VTERM_ATTR_CONCEAL_MASK)    && (a->pen.conceal != b->pen.conceal))
    return 1;
  if((attrs & VTERM_ATTR_STRIKE_MASK)     && (a->pen.strike != b->pen.strike))
    return 1;
  if((attrs & VTERM_ATTR_FONT_MASK)       && (a->pen.font != b->pen.font))
    return 1;
  if((attrs & VTERM_ATTR_FOREGROUND_MASK) && !vterm_color_is_equal(&a->pen.fg, &b->pen.fg))
    return 1;
  if((attrs & VTERM_ATTR_BACKGROUND_MASK) && !vterm_color_is_equal(&a->pen.bg, &b->pen.bg))
    return 1;
  if((attrs & VTERM_ATTR_SMALL_MASK)    && (a->pen.small != b->pen.small))
    return 1;
  if((attrs & VTERM_ATTR_BASELINE_MASK)    && (a->pen.baseline != b->pen.baseline))
    return 1;

  return 0;
}

int vterm_screen_get_attrs_extent(const VTermScreen *screen, VTermRect *extent, VTermPos pos, VTermAttrMask attrs)
{
  ScreenCell *target = getcell(screen, pos.row, pos.col);

  // TODO: bounds check
  extent->start_row = pos.row;
  extent->end_row   = pos.row + 1;

  if(extent->start_col < 0)
    extent->start_col = 0;
  if(extent->end_col < 0)
    extent->end_col = screen->cols;

  int col;

  for(col = pos.col - 1; col >= extent->start_col; col--)
    if(attrs_differ(attrs, target, getcell(screen, pos.row, col)))
      break;
  extent->start_col = col + 1;

  for(col = pos.col + 1; col < extent->end_col; col++)
    if(attrs_differ(attrs, target, getcell(screen, pos.row, col)))
      break;
  extent->end_col = col - 1;

  return 1;
}

void vterm_screen_convert_color_to_rgb(const VTermScreen *screen, VTermColor *col)
{
  vterm_state_convert_color_to_rgb(screen->state, col);
}

static void reset_default_colours(VTermScreen *screen, ScreenCell *buffer)
{
  for(int row = 0; row <= screen->rows - 1; row++)
    for(int col = 0; col <= screen->cols - 1; col++) {
      ScreenCell *cell = &buffer[row * screen->cols + col];
      if(VTERM_COLOR_IS_DEFAULT_FG(&cell->pen.fg))
        cell->pen.fg = screen->pen.fg;
      if(VTERM_COLOR_IS_DEFAULT_BG(&cell->pen.bg))
        cell->pen.bg = screen->pen.bg;
    }
}

void vterm_screen_set_default_colors(VTermScreen *screen, const VTermColor *default_fg, const VTermColor *default_bg)
{
  vterm_state_set_default_colors(screen->state, default_fg, default_bg);

  if(default_fg && VTERM_COLOR_IS_DEFAULT_FG(&screen->pen.fg)) {
    screen->pen.fg = *default_fg;
    screen->pen.fg.type = (screen->pen.fg.type & ~VTERM_COLOR_DEFAULT_MASK)
                        | VTERM_COLOR_DEFAULT_FG;
  }

  if(default_bg && VTERM_COLOR_IS_DEFAULT_BG(&screen->pen.bg)) {
    screen->pen.bg = *default_bg;
    screen->pen.bg.type = (screen->pen.bg.type & ~VTERM_COLOR_DEFAULT_MASK)
                        | VTERM_COLOR_DEFAULT_BG;
  }

  reset_default_colours(screen, screen->buffers[0]);
  if(screen->buffers[1])
    reset_default_colours(screen, screen->buffers[1]);
}


================================================
FILE: src/state.c
================================================
#include "vterm_internal.h"

#include <stdio.h>
#include <string.h>

#define strneq(a,b,n) (strncmp(a,b,n)==0)

#if defined(DEBUG) && DEBUG > 1
# define DEBUG_GLYPH_COMBINE
#endif

/* Some convenient wrappers to make callback functions easier */

static void putglyph(VTermState *state, const uint32_t chars[], int width, VTermPos pos)
{
  VTermGlyphInfo info = {
    .chars = chars,
    .width = width,
    .protected_cell = state->protected_cell,
    .dwl = state->lineinfo[pos.row].doublewidth,
    .dhl = state->lineinfo[pos.row].doubleheight,
  };

  if(state->callbacks && state->callbacks->putglyph)
    if((*state->callbacks->putglyph)(&info, pos, state->cbdata))
      return;

  DEBUG_LOG("libvterm: Unhandled putglyph U+%04x at (%d,%d)\n", chars[0], pos.col, pos.row);
}

static void updatecursor(VTermState *state, VTermPos *oldpos, int cancel_phantom)
{
  if(state->pos.col == oldpos->col && state->pos.row == oldpos->row)
    return;

  if(cancel_phantom)
    state->at_phantom = 0;

  if(state->callbacks && state->callbacks->movecursor)
    if((*state->callbacks->movecursor)(state->pos, *oldpos, state->mode.cursor_visible, state->cbdata))
      return;
}

static void erase(VTermState *state, VTermRect rect, int selective)
{
  if(rect.end_col == state->cols) {
    /* If we're erasing the final cells of any lines, cancel the continuation
     * marker on the subsequent line
     */
    for(int row = rect.start_row + 1; row < rect.end_row + 1 && row < state->rows; row++)
      state->lineinfo[row].continuation = 0;
  }

  if(state->callbacks && state->callbacks->erase)
    if((*state->callbacks->erase)(rect, selective, state->cbdata))
      return;
}

static VTermState *vterm_state_new(VTerm *vt)
{
  VTermState *state = vterm_allocator_malloc(vt, sizeof(VTermState));

  state->vt = vt;

  state->rows = vt->rows;
  state->cols = vt->cols;

  state->mouse_col     = 0;
  state->mouse_row     = 0;
  state->mouse_buttons = 0;

  state->mouse_protocol = MOUSE_X10;

  state->callbacks = NULL;
  state->cbdata    = NULL;
  state->callbacks_has_premove = false;

  state->selection.callbacks = NULL;
  state->selection.user      = NULL;
  state->selection.buffer    = NULL;

  vterm_state_newpen(state);

  state->bold_is_highbright = 0;

  state->combine_chars_size = 16;
  state->combine_chars = vterm_allocator_malloc(state->vt, state->combine_chars_size * sizeof(state->combine_chars[0]));

  state->tabstops = vterm_allocator_malloc(state->vt, (state->cols + 7) / 8);

  state->lineinfos[BUFIDX_PRIMARY]   = vterm_allocator_malloc(state->vt, state->rows * sizeof(VTermLineInfo));
  /* TODO: Make an 'enable' function */
  state->lineinfos[BUFIDX_ALTSCREEN] = vterm_allocator_malloc(state->vt, state->rows * sizeof(VTermLineInfo));
  state->lineinfo = state->lineinfos[BUFIDX_PRIMARY];

  state->encoding_utf8.enc = vterm_lookup_encoding(ENC_UTF8, 'u');
  if(*state->encoding_utf8.enc->init)
    (*state->encoding_utf8.enc->init)(state->encoding_utf8.enc, state->encoding_utf8.data);

  return state;
}

INTERNAL void vterm_state_free(VTermState *state)
{
  vterm_allocator_free(state->vt, state->tabstops);
  vterm_allocator_free(state->vt, state->lineinfos[BUFIDX_PRIMARY]);
  if(state->lineinfos[BUFIDX_ALTSCREEN])
    vterm_allocator_free(state->vt, state->lineinfos[BUFIDX_ALTSCREEN]);
  vterm_allocator_free(state->vt, state->combine_chars);
  vterm_allocator_free(state->vt, state);
}

static void scroll(VTermState *state, VTermRect rect, int downward, int rightward)
{
  if(!downward && !rightward)
    return;

  int rows = rect.end_row - rect.start_row;
  if(downward > rows)
    downward = rows;
  else if(downward < -rows)
    downward = -rows;

  int cols = rect.end_col - rect.start_col;
  if(rightward > cols)
    rightward = cols;
  else if(rightward < -cols)
    rightward = -cols;

  if(state->callbacks_has_premove && state->callbacks && state->callbacks->premove) {
    // TODO: technically this logic is wrong if both downward != 0 and rightward != 0

    /* Work out what subsection of the destination area is about to be destroyed */
    if(downward > 0)
      /* about to destroy the top */
      (*state->callbacks->premove)((VTermRect){
          .start_row = rect.start_row, .end_row = rect.start_row + downward,
          .start_col = rect.start_col, .end_col = rect.end_col}, state->cbdata);
    else if(downward < 0)
      /* about to destroy the bottom */
      (*state->callbacks->premove)((VTermRect){
          .start_row = rect.end_row + downward, .end_row = rect.end_row,
          .start_col = rect.start_col,          .end_col = rect.end_col}, state->cbdata);

    if(rightward > 0)
      /* about to destroy the left */
      (*state->callbacks->premove)((VTermRect){
          .start_row = rect.start_row, .end_row = rect.end_row,
          .start_col = rect.start_col, .end_col = rect.start_col + rightward}, state->cbdata);
    else if(rightward < 0)
      /* about to destroy the right */
      (*state->callbacks->premove)((VTermRect){
          .start_row = rect.start_row,           .end_row = rect.end_row,
          .start_col = rect.end_col + rightward, .end_col = rect.end_col}, state->cbdata);
  }

  // Update lineinfo if full line
  if(rect.start_col == 0 && rect.end_col == state->cols && rightward == 0) {
    int height = rect.end_row - rect.start_row - abs(downward);

    if(downward > 0) {
      memmove(state->lineinfo + rect.start_row,
              state->lineinfo + rect.start_row + downward,
              height * sizeof(state->lineinfo[0]));
      for(int row = rect.end_row - downward; row < rect.end_row; row++)
        state->lineinfo[row] = (VTermLineInfo){ 0 };
    }
    else {
      memmove(state->lineinfo + rect.start_row - downward,
              state->lineinfo + rect.start_row,
              height * sizeof(state->lineinfo[0]));
      for(int row = rect.start_row; row < rect.start_row - downward; row++)
        state->lineinfo[row] = (VTermLineInfo){ 0 };
    }
  }

  if(state->callbacks && state->callbacks->scrollrect)
    if((*state->callbacks->scrollrect)(rect, downward, rightward, state->cbdata))
      return;

  if(state->callbacks)
    vterm_scroll_rect(rect, downward, rightward,
        state->callbacks->moverect, state->callbacks->erase, state->cbdata);
}

static void linefeed(VTermState *state)
{
  if(state->pos.row == SCROLLREGION_BOTTOM(state) - 1) {
    VTermRect rect = {
      .start_row = state->scrollregion_top,
      .end_row   = SCROLLREGION_BOTTOM(state),
      .start_col = SCROLLREGION_LEFT(state),
      .end_col   = SCROLLREGION_RIGHT(state),
    };

    scroll(state, rect, 1, 0);
  }
  else if(state->pos.row < state->rows-1)
    state->pos.row++;
}

static void grow_combine_buffer(VTermState *state)
{
  size_t    new_size = state->combine_chars_size * 2;
  uint32_t *new_chars = vterm_allocator_malloc(state->vt, new_size * sizeof(new_chars[0]));

  memcpy(new_chars, state->combine_chars, state->combine_chars_size * sizeof(new_chars[0]));

  vterm_allocator_free(state->vt, state->combine_chars);

  state->combine_chars = new_chars;
  state->combine_chars_size = new_size;
}

static void set_col_tabstop(VTermState *state, int col)
{
  unsigned char mask = 1 << (col & 7);
  state->tabstops[col >> 3] |= mask;
}

static void clear_col_tabstop(VTermState *state, int col)
{
  unsigned char mask = 1 << (col & 7);
  state->tabstops[col >> 3] &= ~mask;
}

static int is_col_tabstop(VTermState *state, int col)
{
  unsigned char mask = 1 << (col & 7);
  return state->tabstops[col >> 3] & mask;
}

static int is_cursor_in_scrollregion(const VTermState *state)
{
  if(state->pos.row < state->scrollregion_top ||
     state->pos.row >= SCROLLREGION_BOTTOM(state))
    return 0;
  if(state->pos.col < SCROLLREGION_LEFT(state) ||
     state->pos.col >= SCROLLREGION_RIGHT(state))
    return 0;

  return 1;
}

static void tab(VTermState *state, int count, int direction)
{
  while(count > 0) {
    if(direction > 0) {
      if(state->pos.col >= THISROWWIDTH(state)-1)
        return;

      state->pos.col++;
    }
    else if(direction < 0) {
      if(state->pos.col < 1)
        return;

      state->pos.col--;
    }

    if(is_col_tabstop(state, state->pos.col))
      count--;
  }
}

#define NO_FORCE 0
#define FORCE    1

#define DWL_OFF 0
#define DWL_ON  1

#define DHL_OFF    0
#define DHL_TOP    1
#define DHL_BOTTOM 2

static void set_lineinfo(VTermState *state, int row, int force, int dwl, int dhl)
{
  VTermLineInfo info = state->lineinfo[row];

  if(dwl == DWL_OFF)
    info.doublewidth = DWL_OFF;
  else if(dwl == DWL_ON)
    info.doublewidth = DWL_ON;
  // else -1 to ignore

  if(dhl == DHL_OFF)
    info.doubleheight = DHL_OFF;
  else if(dhl == DHL_TOP)
    info.doubleheight = DHL_TOP;
  else if(dhl == DHL_BOTTOM)
    info.doubleheight = DHL_BOTTOM;

  if((state->callbacks &&
      state->callbacks->setlineinfo &&
      (*state->callbacks->setlineinfo)(row, &info, state->lineinfo + row, state->cbdata))
      || force)
    state->lineinfo[row] = info;
}

static int on_text(const char bytes[], size_t len, void *user)
{
  VTermState *state = user;

  VTermPos oldpos = state->pos;

  uint32_t *codepoints = (uint32_t *)(state->vt->tmpbuffer);
  size_t maxpoints = (state->vt->tmpbuffer_len) / sizeof(uint32_t);

  int npoints = 0;
  size_t eaten = 0;

  VTermEncodingInstance *encoding =
    state->gsingle_set     ? &state->encoding[state->gsingle_set] :
    !(bytes[eaten] & 0x80) ? &state->encoding[state->gl_set] :
    state->vt->mode.utf8   ? &state->encoding_utf8 :
                             &state->encoding[state->gr_set];

  (*encoding->enc->decode)(encoding->enc, encoding->data,
      codepoints, &npoints, state->gsingle_set ? 1 : maxpoints,
      bytes, &eaten, len);

  /* There's a chance an encoding (e.g. UTF-8) hasn't found enough bytes yet
   * for even a single codepoint
   */
  if(!npoints)
    return eaten;

  if(state->gsingle_set && npoints)
    state->gsingle_set = 0;

  int i = 0;

  /* This is a combining char. that needs to be merged with the previous
   * glyph output */
  if(vterm_unicode_is_combining(codepoints[i])) {
    /* See if the cursor has moved since */
    if(state->pos.row == state->combine_pos.row && state->pos.col == state->combine_pos.col + state->combine_width) {
#ifdef DEBUG_GLYPH_COMBINE
      int printpos;
      printf("DEBUG: COMBINING SPLIT GLYPH of chars {");
      for(printpos = 0; state->combine_chars[printpos]; printpos++)
        printf("U+%04x ", state->combine_chars[printpos]);
      printf("} + {");
#endif

      /* Find where we need to append these combining chars */
      int saved_i = 0;
      while(state->combine_chars[saved_i])
        saved_i++;

      /* Add extra ones */
      while(i < npoints && vterm_unicode_is_combining(codepoints[i])) {
        if(saved_i >= state->combine_chars_size)
          grow_combine_buffer(state);
        state->combine_chars[saved_i++] = codepoints[i++];
      }
      if(saved_i >= state->combine_chars_size)
        grow_combine_buffer(state);
      state->combine_chars[saved_i] = 0;

#ifdef DEBUG_GLYPH_COMBINE
      for(; state->combine_chars[printpos]; printpos++)
        printf("U+%04x ", state->combine_chars[printpos]);
      printf("}\n");
#endif

      /* Now render it */
      putglyph(state, state->combine_chars, state->combine_width, state->combine_pos);
    }
    else {
      DEBUG_LOG("libvterm: TODO: Skip over split char+combining\n");
    }
  }

  for(; i < npoints; i++) {
    // Try to find combining characters following this
    int glyph_starts = i;
    int glyph_ends;
    for(glyph_ends = i + 1;
        (glyph_ends < npoints) && (glyph_ends < glyph_starts + VTERM_MAX_CHARS_PER_CELL);
        glyph_ends++)
      if(!vterm_unicode_is_combining(codepoints[glyph_ends]))
        break;

    int width = 0;

    uint32_t chars[VTERM_MAX_CHARS_PER_CELL + 1];

    for( ; i < glyph_ends; i++) {
      chars[i - glyph_starts] = codepoints[i];
      int this_width = vterm_unicode_width(codepoints[i]);
#ifdef DEBUG
      if(this_width < 0) {
        fprintf(stderr, "Text with negative-width codepoint U+%04x\n", codepoints[i]);
        abort();
      }
#endif
      width += this_width;
    }

    while(i < npoints && vterm_unicode_is_combining(codepoints[i]))
      i++;

    chars[glyph_ends - glyph_starts] = 0;
    i--;

#ifdef DEBUG_GLYPH_COMBINE
    int printpos;
    printf("DEBUG: COMBINED GLYPH of %d chars {", glyph_ends - glyph_starts);
    for(printpos = 0; printpos < glyph_ends - glyph_starts; printpos++)
      printf("U+%04x ", chars[printpos]);
    printf("}, onscreen width %d\n", width);
#endif

    if(state->at_phantom || state->pos.col + width > THISROWWIDTH(state)) {
      linefeed(state);
      state->pos.col = 0;
      state->at_phantom = 0;
      state->lineinfo[state->pos.row].continuation = 1;
    }

    if(state->mode.insert) {
      /* TODO: This will be a little inefficient for large bodies of text, as
       * it'll have to 'ICH' effectively before every glyph. We should scan
       * ahead and ICH as many times as required
       */
      VTermRect rect = {
        .start_row = state->pos.row,
        .end_row   = state->pos.row + 1,
        .start_col = state->pos.col,
        .end_col   = THISROWWIDTH(state),
      };
      scroll(state, rect, 0, -1);
    }

    putglyph(state, chars, width, state->pos);

    if(i == npoints - 1) {
      /* End of the buffer. Save the chars in case we have to combine with
       * more on the next call */
      int save_i;
      for(save_i = 0; chars[save_i]; save_i++) {
        if(save_i >= state->combine_chars_size)
          grow_combine_buffer(state);
        state->combine_chars[save_i] = chars[save_i];
      }
      if(save_i >= state->combine_chars_size)
        grow_combine_buffer(state);
      state->combine_chars[save_i] = 0;
      state->combine_width = width;
      state->combine_pos = state->pos;
    }

    if(state->pos.col + width >= THISROWWIDTH(state)) {
      if(state->mode.autowrap)
        state->at_phantom = 1;
    }
    else {
      state->pos.col += width;
    }
  }

  updatecursor(state, &oldpos, 0);

#ifdef DEBUG
  if(state->pos.row < 0 || state->pos.row >= state->rows ||
     state->pos.col < 0 || state->pos.col >= state->cols) {
    fprintf(stderr, "Position out of bounds after text: (%d,%d)\n",
        state->pos.row, state->pos.col);
    abort();
  }
#endif

  return eaten;
}

static int on_control(unsigned char control, void *user)
{
  VTermState *state = user;

  VTermPos oldpos = state->pos;

  switch(control) {
  case 0x07: // BEL - ECMA-48 8.3.3
    if(state->callbacks && state->callbacks->bell)
      (*state->callbacks->bell)(state->cbdata);
    break;

  case 0x08: // BS - ECMA-48 8.3.5
    if(state->pos.col > 0)
      state->pos.col--;
    break;

  case 0x09: // HT - ECMA-48 8.3.60
    tab(state, 1, +1);
    break;

  case 0x0a: // LF - ECMA-48 8.3.74
  case 0x0b: // VT
  case 0x0c: // FF
    linefeed(state);
    if(state->mode.newline)
      state->pos.col = 0;
    break;

  case 0x0d: // CR - ECMA-48 8.3.15
    state->pos.col = 0;
    break;

  case 0x0e: // LS1 - ECMA-48 8.3.76
    state->gl_set = 1;
    break;

  case 0x0f: // LS0 - ECMA-48 8.3.75
    state->gl_set = 0;
    break;

  case 0x84: // IND - DEPRECATED but implemented for completeness
    linefeed(state);
    break;

  case 0x85: // NEL - ECMA-48 8.3.86
    linefeed(state);
    state->pos.col = 0;
    break;

  case 0x88: // HTS - ECMA-48 8.3.62
    set_col_tabstop(state, state->pos.col);
    break;

  case 0x8d: // RI - ECMA-48 8.3.104
    if(state->pos.row == state->scrollregion_top) {
      VTermRect rect = {
        .start_row = state->scrollregion_top,
        .end_row   = SCROLLREGION_BOTTOM(state),
        .start_col = SCROLLREGION_LEFT(state),
        .end_col   = SCROLLREGION_RIGHT(state),
      };

      scroll(state, rect, -1, 0);
    }
    else if(state->pos.row > 0)
        state->pos.row--;
    break;

  case 0x8e: // SS2 - ECMA-48 8.3.141
    state->gsingle_set = 2;
    break;

  case 0x8f: // SS3 - ECMA-48 8.3.142
    state->gsingle_set = 3;
    break;

  default:
    if(state->fallbacks && state->fallbacks->control)
      if((*state->fallbacks->control)(control, state->fbdata))
        return 1;

    return 0;
  }

  updatecursor(state, &oldpos, 1);

#ifdef DEBUG
  if(state->pos.row < 0 || state->pos.row >= state->rows ||
     state->pos.col < 0 || state->pos.col >= state->cols) {
    fprintf(stderr, "Position out of bounds after Ctrl %02x: (%d,%d)\n",
        control, state->pos.row, state->pos.col);
    abort();
  }
#endif

  return 1;
}

static int settermprop_bool(VTermState *state, VTermProp prop, int v)
{
  VTermValue val = { .boolean = v };
  return vterm_state_set_termprop(state, prop, &val);
}

static int settermprop_int(VTermState *state, VTermProp prop, int v)
{
  VTermValue val = { .number = v };
  return vterm_state_set_termprop(state, prop, &val);
}

static int settermprop_string(VTermState *state, VTermProp prop, VTermStringFragment frag)
{
  VTermValue val = { .string = frag };
  return vterm_state_set_termprop(state, prop, &val);
}

static void savecursor(VTermState *state, int save)
{
  if(save) {
    state->saved.pos = state->pos;
    state->saved.mode.cursor_visible = state->mode.cursor_visible;
    state->saved.mode.cursor_blink   = state->mode.cursor_blink;
    state->saved.mode.cursor_shape   = state->mode.cursor_shape;

    vterm_state_savepen(state, 1);
  }
  else {
    VTermPos oldpos = state->pos;

    state->pos = state->saved.pos;

    settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, state->saved.mode.cursor_visible);
    settermprop_bool(state, VTERM_PROP_CURSORBLINK,   state->saved.mode.cursor_blink);
    settermprop_int (state, VTERM_PROP_CURSORSHAPE,   state->saved.mode.cursor_shape);

    vterm_state_savepen(state, 0);

    updatecursor(state, &oldpos, 1);
  }
}

static int on_escape(const char *bytes, size_t len, void *user)
{
  VTermState *state = user;

  /* Easier to decode this from the first byte, even though the final
   * byte terminates it
   */
  switch(bytes[0]) {
  case ' ':
    if(len != 2)
      return 0;

    switch(bytes[1]) {
      case 'F': // S7C1T
        state->vt->mode.ctrl8bit = 0;
        break;

      case 'G': // S8C1T
        state->vt->mode.ctrl8bit = 1;
        break;

      default:
        return 0;
    }
    return 2;

  case '#':
    if(len != 2)
      return 0;

    switch(bytes[1]) {
      case '3': // DECDHL top
        if(state->mode.leftrightmargin)
          break;
        set_lineinfo(state, state->pos.row, NO_FORCE, DWL_ON, DHL_TOP);
        break;

      case '4': // DECDHL bottom
        if(state->mode.leftrightmargin)
          break;
        set_lineinfo(state, state->pos.row, NO_FORCE, DWL_ON, DHL_BOTTOM);
        break;

      case '5': // DECSWL
        if(state->mode.leftrightmargin)
          break;
        set_lineinfo(state, state->pos.row, NO_FORCE, DWL_OFF, DHL_OFF);
        break;

      case '6': // DECDWL
        if(state->mode.leftrightmargin)
          break;
        set_lineinfo(state, state->pos.row, NO_FORCE, DWL_ON, DHL_OFF);
        break;

      case '8': // DECALN
      {
        VTermPos pos;
        uint32_t E[] = { 'E', 0 };
        for(pos.row = 0; pos.row < state->rows; pos.row++)
          for(pos.col = 0; pos.col < ROWWIDTH(state, pos.row); pos.col++)
            putglyph(state, E, 1, pos);
        break;
      }

      default:
        return 0;
    }
    return 2;

  case '(': case ')': case '*': case '+': // SCS
    if(len != 2)
      return 0;

    {
      int setnum = bytes[0] - 0x28;
      VTermEncoding *newenc = vterm_lookup_encoding(ENC_SINGLE_94, bytes[1]);

      if(newenc) {
        state->encoding[setnum].enc = newenc;

        if(newenc->init)
          (*newenc->init)(newenc, state->encoding[setnum].data);
      }
    }

    return 2;

  case '7': // DECSC
    savecursor(state, 1);
    return 1;

  case '8': // DECRC
    savecursor(state, 0);
    return 1;

  case '<': // Ignored by VT100. Used in VT52 mode to switch up to VT100
    return 1;

  case '=': // DECKPAM
    state->mode.keypad = 1;
    return 1;

  case '>': // DECKPNM
    state->mode.keypad = 0;
    return 1;

  case 'c': // RIS - ECMA-48 8.3.105
  {
    VTermPos oldpos = state->pos;
    vterm_state_reset(state, 1);
    if(state->callbacks && state->callbacks->movecursor)
      (*state->callbacks->movecursor)(state->pos, oldpos, state->mode.cursor_visible, state->cbdata);
    return 1;
  }

  case 'n': // LS2 - ECMA-48 8.3.78
    state->gl_set = 2;
    return 1;

  case 'o': // LS3 - ECMA-48 8.3.80
    state->gl_set = 3;
    return 1;

  case '~': // LS1R - ECMA-48 8.3.77
    state->gr_set = 1;
    return 1;

  case '}': // LS2R - ECMA-48 8.3.79
    state->gr_set = 2;
    return 1;

  case '|': // LS3R - ECMA-48 8.3.81
    state->gr_set = 3;
    return 1;

  default:
    return 0;
  }
}

static void set_mode(VTermState *state, int num, int val)
{
  switch(num) {
  case 4: // IRM - ECMA-48 7.2.10
    state->mode.insert = val;
    break;

  case 20: // LNM - ANSI X3.4-1977
    state->mode.newline = val;
    break;

  default:
    DEBUG_LOG("libvterm: Unknown mode %d\n", num);
    return;
  }
}

static void set_dec_mode(VTermState *state, int num, int val)
{
  switch(num) {
  case 1:
    state->mode.cursor = val;
    break;

  case 5: // DECSCNM - screen mode
    settermprop_bool(state, VTERM_PROP_REVERSE, val);
    break;

  case 6: // DECOM - origin mode
    {
      VTermPos oldpos = state->pos;
      state->mode.origin = val;
      state->pos.row = state->mode.origin ? state->scrollregion_top : 0;
      state->pos.col = state->mode.origin ? SCROLLREGION_LEFT(state) : 0;
      updatecursor(state, &oldpos, 1);
    }
    break;

  case 7:
    state->mode.autowrap = val;
    break;

  case 12:
    settermprop_bool(state, VTERM_PROP_CURSORBLINK, val);
    break;

  case 25:
    settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, val);
    break;

  case 69: // DECVSSM - vertical split screen mode
           // DECLRMM - left/right margin mode
    state->mode.leftrightmargin = val;
    if(val) {
      // Setting DECVSSM must clear doublewidth/doubleheight state of every line
      for(int row = 0; row < state->rows; row++)
        set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF);
    }

    break;

  case 1000:
  case 1002:
  case 1003:
    settermprop_int(state, VTERM_PROP_MOUSE,
        !val          ? VTERM_PROP_MOUSE_NONE  :
        (num == 1000) ? VTERM_PROP_MOUSE_CLICK :
        (num == 1002) ? VTERM_PROP_MOUSE_DRAG  :
                        VTERM_PROP_MOUSE_MOVE);
    break;

  case 1004:
    settermprop_bool(state, VTERM_PROP_FOCUSREPORT, val);
    state->mode.report_focus = val;
    break;

  case 1005:
    state->mouse_protocol = val ? MOUSE_UTF8 : MOUSE_X10;
    break;

  case 1006:
    state->mouse_protocol = val ? MOUSE_SGR : MOUSE_X10;
    break;

  case 1015:
    state->mouse_protocol = val ? MOUSE_RXVT : MOUSE_X10;
    break;

  case 1047:
    settermprop_bool(state, VTERM_PROP_ALTSCREEN, val);
    break;

  case 1048:
    savecursor(state, val);
    break;

  case 1049:
    settermprop_bool(state, VTERM_PROP_ALTSCREEN, val);
    savecursor(state, val);
    break;

  case 2004:
    state->mode.bracketpaste = val;
    break;

  default:
    DEBUG_LOG("libvterm: Unknown DEC mode %d\n", num);
    return;
  }
}

static void request_dec_mode(VTermState *state, int num)
{
  int reply;

  switch(num) {
    case 1:
      reply = state->mode.cursor;
      break;

    case 5:
      reply = state->mode.screen;
      break;

    case 6:
      reply = state->mode.origin;
      break;

    case 7:
      reply = state->mode.autowrap;
      break;

    case 12:
      reply = state->mode.cursor_blink;
      break;

    case 25:
      reply = state->mode.cursor_visible;
      break;

    case 69:
      reply = state->mode.leftrightmargin;
      break;

    case 1000:
      reply = state->mouse_flags == MOUSE_WANT_CLICK;
      break;

    case 1002:
      reply = state->mouse_flags == (MOUSE_WANT_CLICK|MOUSE_WANT_DRAG);
      break;

    case 1003:
      reply = state->mouse_flags == (MOUSE_WANT_CLICK|MOUSE_WANT_MOVE);
      break;

    case 1004:
      reply = state->mode.report_focus;
      break;

    case 1005:
      reply = state->mouse_protocol == MOUSE_UTF8;
      break;

    case 1006:
      reply = state->mouse_protocol == MOUSE_SGR;
      break;

    case 1015:
      reply = state->mouse_protocol == MOUSE_RXVT;
      break;

    case 1047:
      reply = state->mode.alt_screen;
      break;

    case 2004:
      reply = state->mode.bracketpaste;
      break;

    default:
      vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?%d;%d$y", num, 0);
      return;
  }

  vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?%d;%d$y", num, reply ? 1 : 2);
}

static void request_version_string(VTermState *state)
{
  vterm_push_output_sprintf_str(state->vt, C1_DCS, true, ">|libvterm(%d.%d)",
      VTERM_VERSION_MAJOR, VTERM_VERSION_MINOR);
}

static int on_csi(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user)
{
  VTermState *state = user;
  int leader_byte = 0;
  int intermed_byte = 0;
  int cancel_phantom = 1;

  if(leader && leader[0]) {
    if(leader[1]) // longer than 1 char
      return 0;

    switch(leader[0]) {
    case '?':
    case '>':
      leader_byte = leader[0];
      break;
    default:
      return 0;
    }
  }

  if(intermed && intermed[0]) {
    if(intermed[1]) // longer than 1 char
      return 0;

    switch(intermed[0]) {
    case ' ':
    case '!':
    case '"':
    case '$':
    case '\'':
      intermed_byte = intermed[0];
      break;
    default:
      return 0;
    }
  }

  VTermPos oldpos = state->pos;

  // Some temporaries for later code
  int count, val;
  int row, col;
  VTermRect rect;
  int selective;

#define LBOUND(v,min) if((v) < (min)) (v) = (min)
#define UBOUND(v,max) if((v) > (max)) (v) = (max)

#define LEADER(l,b) ((l << 8) | b)
#define INTERMED(i,b) ((i << 16) | b)

  switch(intermed_byte << 16 | leader_byte << 8 | command) {
  case 0x40: // ICH - ECMA-48 8.3.64
    count = CSI_ARG_COUNT(args[0]);

    if(!is_cursor_in_scrollregion(state))
      break;

    rect.start_row = state->pos.row;
    rect.end_row   = state->pos.row + 1;
    rect.start_col = state->pos.col;
    if(state->mode.leftrightmargin)
      rect.end_col = SCROLLREGION_RIGHT(state);
    else
      rect.end_col = THISROWWIDTH(state);

    scroll(state, rect, 0, -count);

    break;

  case 0x41: // CUU - ECMA-48 8.3.22
    count = CSI_ARG_COUNT(args[0]);
    state->pos.row -= count;
    state->at_phantom = 0;
    break;

  case 0x42: // CUD - ECMA-48 8.3.19
    count = CSI_ARG_COUNT(args[0]);
    state->pos.row += count;
    state->at_phantom = 0;
    break;

  case 0x43: // CUF - ECMA-48 8.3.20
    count = CSI_ARG_COUNT(args[0]);
    state->pos.col += count;
    state->at_phantom = 0;
    break;

  case 0x44: // CUB - ECMA-48 8.3.18
    count = CSI_ARG_COUNT(args[0]);
    state->pos.col -= count;
    state->at_phantom = 0;
    break;

  case 0x45: // CNL - ECMA-48 8.3.12
    count = CSI_ARG_COUNT(args[0]);
    state->pos.col = 0;
    state->pos.row += count;
    state->at_phantom = 0;
    break;

  case 0x46: // CPL - ECMA-48 8.3.13
    count = CSI_ARG_COUNT(args[0]);
    state->pos.col = 0;
    state->pos.row -= count;
    state->at_phantom = 0;
    break;

  case 0x47: // CHA - ECMA-48 8.3.9
    val = CSI_ARG_OR(args[0], 1);
    state->pos.col = val-1;
    state->at_phantom = 0;
    break;

  case 0x48: // CUP - ECMA-48 8.3.21
    row = CSI_ARG_OR(args[0], 1);
    col = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? 1 : CSI_ARG(args[1]);
    // zero-based
    state->pos.row = row-1;
    state->pos.col = col-1;
    if(state->mode.origin) {
      state->pos.row += state->scrollregion_top;
      state->pos.col += SCROLLREGION_LEFT(state);
    }
    state->at_phantom = 0;
    break;

  case 0x49: // CHT - ECMA-48 8.3.10
    count = CSI_ARG_COUNT(args[0]);
    tab(state, count, +1);
    break;

  case 0x4a: // ED - ECMA-48 8.3.39
  case LEADER('?', 0x4a): // DECSED - Selective Erase in Display
    selective = (leader_byte == '?');
    switch(CSI_ARG(args[0])) {
    case CSI_ARG_MISSING:
    case 0:
      rect.start_row = state->pos.row; rect.end_row = state->pos.row + 1;
      rect.start_col = state->pos.col; rect.end_col = state->cols;
      if(rect.end_col > rect.start_col)
        erase(state, rect, selective);

      rect.start_row = state->pos.row + 1; rect.end_row = state->rows;
      rect.start_col = 0;
      for(int row = rect.start_row; row < rect.end_row; row++)
        set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF);
      if(rect.end_row > rect.start_row)
        erase(state, rect, selective);
      break;

    case 1:
      rect.start_row = 0; rect.end_row = state->pos.row;
      rect.start_col = 0; rect.end_col = state->cols;
      for(int row = rect.start_row; row < rect.end_row; row++)
        set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF);
      if(rect.end_col > rect.start_col)
        erase(state, rect, selective);

      rect.start_row = state->pos.row; rect.end_row = state->pos.row + 1;
                          rect.end_col = state->pos.col + 1;
      if(rect.end_row > rect.start_row)
        erase(state, rect, selective);
      break;

    case 2:
      rect.start_row = 0; rect.end_row = state->rows;
      rect.start_col = 0; rect.end_col = state->cols;
      for(int row = rect.start_row; row < rect.end_row; row++)
        set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF);
      erase(state, rect, selective);
      break;

    case 3:
      if(state->callbacks && state->callbacks->sb_clear)
        if((*state->callbacks->sb_clear)(state->cbdata))
          return 1;
      break;
    }
    break;

  case 0x4b: // EL - ECMA-48 8.3.41
  case LEADER('?', 0x4b): // DECSEL - Selective Erase in Line
    selective = (leader_byte == '?');
    rect.start_row = state->pos.row;
    rect.end_row   = state->pos.row + 1;

    switch(CSI_ARG(args[0])) {
    case CSI_ARG_MISSING:
    case 0:
      rect.start_col = state->pos.col; rect.end_col = THISROWWIDTH(state); break;
    case 1:
      rect.start_col = 0; rect.end_col = state->pos.col + 1; break;
    case 2:
      rect.start_col = 0; rect.end_col = THISROWWIDTH(state); break;
    default:
      return 0;
    }

    if(rect.end_col > rect.start_col)
      erase(state, rect, selective);

    break;

  case 0x4c: // IL - ECMA-48 8.3.67
    count = CSI_ARG_COUNT(args[0]);

    if(!is_cursor_in_scrollregion(state))
      break;

    rect.start_row = state->pos.row;
    rect.end_row   = SCROLLREGION_BOTTOM(state);
    rect.start_col = SCROLLREGION_LEFT(state);
    rect.end_col   = SCROLLREGION_RIGHT(state);

    scroll(state, rect, -count, 0);

    break;

  case 0x4d: // DL - ECMA-48 8.3.32
    count = CSI_ARG_COUNT(args[0]);

    if(!is_cursor_in_scrollregion(state))
      break;

    rect.start_row = state->pos.row;
    rect.end_row   = SCROLLREGION_BOTTOM(state);
    rect.start_col = SCROLLREGION_LEFT(state);
    rect.end_col   = SCROLLREGION_RIGHT(state);

    scroll(state, rect, count, 0);

    break;

  case 0x50: // DCH - ECMA-48 8.3.26
    count = CSI_ARG_COUNT(args[0]);

    if(!is_cursor_in_scrollregion(state))
      break;

    rect.start_row = state->pos.row;
    rect.end_row   = state->pos.row + 1;
    rect.start_col = state->pos.col;
    if(state->mode.leftrightmargin)
      rect.end_col = SCROLLREGION_RIGHT(state);
    else
      rect.end_col = THISROWWIDTH(state);

    scroll(state, rect, 0, count);

    break;

  case 0x53: // SU - ECMA-48 8.3.147
    count = CSI_ARG_COUNT(args[0]);

    rect.start_row = state->scrollregion_top;
    rect.end_row   = SCROLLREGION_BOTTOM(state);
    rect.start_col = SCROLLREGION_LEFT(state);
    rect.end_col   = SCROLLREGION_RIGHT(state);

    scroll(state, rect, count, 0);

    break;

  case 0x54: // SD - ECMA-48 8.3.113
    count = CSI_ARG_COUNT(args[0]);

    rect.start_row = state->scrollregion_top;
    rect.end_row   = SCROLLREGION_BOTTOM(state);
    rect.start_col = SCROLLREGION_LEFT(state);
    rect.end_col   = SCROLLREGION_RIGHT(state);

    scroll(state, rect, -count, 0);

    break;

  case 0x58: // ECH - ECMA-48 8.3.38
    count = CSI_ARG_COUNT(args[0]);

    rect.start_row = state->pos.row;
    rect.end_row   = state->pos.row + 1;
    rect.start_col = state->pos.col;
    rect.end_col   = state->pos.col + count;
    UBOUND(rect.end_col, THISROWWIDTH(state));

    erase(state, rect, 0);
    break;

  case 0x5a: // CBT - ECMA-48 8.3.7
    count = CSI_ARG_COUNT(args[0]);
    tab(state, count, -1);
    break;

  case 0x60: // HPA - ECMA-48 8.3.57
    col = CSI_ARG_OR(args[0], 1);
    state->pos.col = col-1;
    state->at_phantom = 0;
    break;

  case 0x61: // HPR - ECMA-48 8.3.59
    count = CSI_ARG_COUNT(args[0]);
    state->pos.col += count;
    state->at_phantom = 0;
    break;

  case 0x62: { // REP - ECMA-48 8.3.103
    const int row_width = THISROWWIDTH(state);
    count = CSI_ARG_COUNT(args[0]);
    col = state->pos.col + count;
    UBOUND(col, row_width);
    while (state->pos.col < col) {
      putglyph(state, state->combine_chars, state->combine_width, state->pos);
      state->pos.col += state->combine_width;
    }
    if (state->pos.col + state->combine_width >= row_width) {
      if (state->mode.autowrap) {
        state->at_phantom = 1;
        cancel_phantom = 0;
      }
    }
    break;
  }

  case 0x63: // DA - ECMA-48 8.3.24
    val = CSI_ARG_OR(args[0], 0);
    if(val == 0)
      // DEC VT100 response
      vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?1;2c");
    break;

  case LEADER('>', 0x63): // DEC secondary Device Attributes
    vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, ">%d;%d;%dc", 0, 100, 0);
    break;

  case 0x64: // VPA - ECMA-48 8.3.158
    row = CSI_ARG_OR(args[0], 1);
    state->pos.row = row-1;
    if(state->mode.origin)
      state->pos.row += state->scrollregion_top;
    state->at_phantom = 0;
    break;

  case 0x65: // VPR - ECMA-48 8.3.160
    count = CSI_ARG_COUNT(args[0]);
    state->pos.row += count;
    state->at_phantom = 0;
    break;

  case 0x66: // HVP - ECMA-48 8.3.63
    row = CSI_ARG_OR(args[0], 1);
    col = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? 1 : CSI_ARG(args[1]);
    // zero-based
    state->pos.row = row-1;
    state->pos.col = col-1;
    if(state->mode.origin) {
      state->pos.row += state->scrollregion_top;
      state->pos.col += SCROLLREGION_LEFT(state);
    }
    state->at_phantom = 0;
    break;

  case 0x67: // TBC - ECMA-48 8.3.154
    val = CSI_ARG_OR(args[0], 0);

    switch(val) {
    case 0:
      clear_col_tabstop(state, state->pos.col);
      break;
    case 3:
    case 5:
      for(col = 0; col < state->cols; col++)
        clear_col_tabstop(state, col);
      break;
    case 1:
    case 2:
    case 4:
      break;
    /* TODO: 1, 2 and 4 aren't meaningful yet without line tab stops */
    default:
      return 0;
    }
    break;

  case 0x68: // SM - ECMA-48 8.3.125
    if(!CSI_ARG_IS_MISSING(args[0]))
      set_mode(state, CSI_ARG(args[0]), 1);
    break;

  case LEADER('?', 0x68): // DEC private mode set
    for(int i = 0; i < argcount; i++) {
      if(!CSI_ARG_IS_MISSING(args[i]))
        set_dec_mode(state, CSI_ARG(args[i]), 1);
    }
    break;

  case 0x6a: // HPB - ECMA-48 8.3.58
    count = CSI_ARG_COUNT(args[0]);
    state->pos.col -= count;
    state->at_phantom = 0;
    break;

  case 0x6b: // VPB - ECMA-48 8.3.159
    count = CSI_ARG_COUNT(args[0]);
    state->pos.row -= count;
    state->at_phantom = 0;
    break;

  case 0x6c: // RM - ECMA-48 8.3.106
    if(!CSI_ARG_IS_MISSING(args[0]))
      set_mode(state, CSI_ARG(args[0]), 0);
    break;

  case LEADER('?', 0x6c): // DEC private mode reset
    for(int i = 0; i < argcount; i++) {
      if(!CSI_ARG_IS_MISSING(args[i]))
        set_dec_mode(state, CSI_ARG(args[i]), 0);
    }
    break;

  case 0x6d: // SGR - ECMA-48 8.3.117
    vterm_state_setpen(state, args, argcount);
    break;

  case LEADER('?', 0x6d): // DECSGR
    /* No actual DEC terminal recognised these, but some printers did. These
     * are alternative ways to request subscript/superscript/off
     */
    for(int argi = 0; argi < argcount; argi++) {
      long arg;
      switch(arg = CSI_ARG(args[argi])) {
        case 4: // Superscript on
          arg = 73;
          vterm_state_setpen(state, &arg, 1);
          break;
        case 5: // Subscript on
          arg = 74;
          vterm_state_setpen(state, &arg, 1);
          break;
        case 24: // Super+subscript off
          arg = 75;
          vterm_state_setpen(state, &arg, 1);
          break;
      }
    }
    break;

  case 0x6e: // DSR - ECMA-48 8.3.35
  case LEADER('?', 0x6e): // DECDSR
    val = CSI_ARG_OR(args[0], 0);

    {
      char *qmark = (leader_byte == '?') ? "?" : "";

      switch(val) {
      case 0: case 1: case 2: case 3: case 4:
        // ignore - these are replies
        break;
      case 5:
        vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "%s0n", qmark);
        break;
      case 6: // CPR - cursor position report
        vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "%s%d;%dR", qmark, state->pos.row + 1, state->pos.col + 1);
        break;
      }
    }
    break;


  case INTERMED('!', 0x70): // DECSTR - DEC soft terminal reset
    vterm_state_reset(state, 0);
    break;

  case LEADER('?', INTERMED('$', 0x70)):
    request_dec_mode(state, CSI_ARG(args[0]));
    break;

  case LEADER('>', 0x71): // XTVERSION - xterm query version string
    request_version_string(state);
    break;

  case INTERMED(' ', 0x71): // DECSCUSR - DEC set cursor shape
    val = CSI_ARG_OR(args[0], 1);

    switch(val) {
    case 0: case 1:
      settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1);
      settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BLOCK);
      break;
    case 2:
      settermprop_bool(state, VTERM_PROP_CURSORBLINK, 0);
      settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BLOCK);
      break;
    case 3:
      settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1);
      settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_UNDERLINE);
      break;
    case 4:
      settermprop_bool(state, VTERM_PROP_CURSORBLINK, 0);
      settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_UNDERLINE);
      break;
    case 5:
      settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1);
      settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BAR_LEFT);
      break;
    case 6:
      settermprop_bool(state, VTERM_PROP_CURSORBLINK, 0);
      settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BAR_LEFT);
      break;
    }

    break;

  case INTERMED('"', 0x71): // DECSCA - DEC select character protection attribute
    val = CSI_ARG_OR(args[0], 0);

    switch(val) {
    case 0: case 2:
      state->protected_cell = 0;
      break;
    case 1:
      state->protected_cell = 1;
      break;
    }

    break;

  case 0x72: // DECSTBM - DEC custom
    state->scrollregion_top = CSI_ARG_OR(args[0], 1) - 1;
    state->scrollregion_bottom = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? -1 : CSI_ARG(args[1]);
    LBOUND(state->scrollregion_top, 0);
    UBOUND(state->scrollregion_top, state->rows);
    LBOUND(state->scrollregion_bottom, -1);
    if(state->scrollregion_top == 0 && state->scrollregion_bottom == state->rows)
      state->scrollregion_bottom = -1;
    else
      UBOUND(state->scrollregion_bottom, state->rows);

    if(SCROLLREGION_BOTTOM(state) <= state->scrollregion_top) {
      // Invalid
      state->scrollregion_top    = 0;
      state->scrollregion_bottom = -1;
    }

    // Setting the scrolling region restores the cursor to the home position
    state->pos.row = 0;
    state->pos.col = 0;
    if(state->mode.origin) {
      state->pos.row += state->scrollregion_top;
      state->pos.col += SCROLLREGION_LEFT(state);
    }

    break;

  case 0x73: // DECSLRM - DEC custom
    // Always allow setting these margins, just they won't take effect without DECVSSM
    state->scrollregion_left = CSI_ARG_OR(args[0], 1) - 1;
    state->scrollregion_right = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? -1 : CSI_ARG(args[1]);
    LBOUND(state->scrollregion_left, 0);
    UBOUND(state->scrollregion_left, state->cols);
    LBOUND(state->scrollregion_right, -1);
    if(state->scrollregion_left == 0 && state->scrollregion_right == state->cols)
      state->scrollregion_right = -1;
    else
      UBOUND(state->scrollregion_right, state->cols);

    if(state->scrollregion_right > -1 &&
       state->scrollregion_right <= state->scrollregion_left) {
      // Invalid
      state->scrollregion_left  = 0;
      state->scrollregion_right = -1;
    }

    // Setting the scrolling region restores the cursor to the home position
    state->pos.row = 0;
    state->pos.col = 0;
    if(state->mode.origin) {
      state->pos.row += state->scrollregion_top;
      state->pos.col += SCROLLREGION_LEFT(state);
    }

    break;

  case INTERMED('\'', 0x7D): // DECIC
    count = CSI_ARG_COUNT(args[0]);

    if(!is_cursor_in_scrollregion(state))
      break;

    rect.start_row = state->scrollregion_top;
    rect.end_row   = SCROLLREGION_BOTTOM(state);
    rect.start_col = state->pos.col;
    rect.end_col   = SCROLLREGION_RIGHT(state);

    scroll(state, rect, 0, -count);

    break;

  case INTERMED('\'', 0x7E): // DECDC
    count = CSI_ARG_COUNT(args[0]);

    if(!is_cursor_in_scrollregion(state))
      break;

    rect.start_row = state->scrollregion_top;
    rect.end_row   = SCROLLREGION_BOTTOM(state);
    rect.start_col = state->pos.col;
    rect.end_col   = SCROLLREGION_RIGHT(state);

    scroll(state, rect, 0, count);

    break;

  default:
    if(state->fallbacks && state->fallbacks->csi)
      if((*state->fallbacks->csi)(leader, args, argcount, intermed, command, state->fbdata))
        return 1;

    return 0;
  }

  if(state->mode.origin) {
    LBOUND(state->pos.row, state->scrollregion_top);
    UBOUND(state->pos.row, SCROLLREGION_BOTTOM(state)-1);
    LBOUND(state->pos.col, SCROLLREGION_LEFT(state));
    UBOUND(state->pos.col, SCROLLREGION_RIGHT(state)-1);
  }
  else {
    LBOUND(state->pos.row, 0);
    UBOUND(state->pos.row, state->rows-1);
    LBOUND(state->pos.col, 0);
    UBOUND(state->pos.col, THISROWWIDTH(state)-1);
  }

  updatecursor(state, &oldpos, cancel_phantom);

#ifdef DEBUG
  if(state->pos.row < 0 || state->pos.row >= state->rows ||
     state->pos.col < 0 || state->pos.col >= state->cols) {
    fprintf(stderr, "Position out of bounds after CSI %c: (%d,%d)\n",
        command, state->pos.row, state->pos.col);
    abort();
  }

  if(SCROLLREGION_BOTTOM(state) <= state->scrollregion_top) {
    fprintf(stderr, "Scroll region height out of bounds after CSI %c: %d <= %d\n",
        command, SCROLLREGION_BOTTOM(state), state->scrollregion_top);
    abort();
  }

  if(SCROLLREGION_RIGHT(state) <= SCROLLREGION_LEFT(state)) {
    fprintf(stderr, "Scroll region width out of bounds after CSI %c: %d <= %d\n",
        command, SCROLLREGION_RIGHT(state), SCROLLREGION_LEFT(state));
    abort();
  }
#endif

  return 1;
}

static char base64_one(uint8_t b)
{
  if(b < 26)
    return 'A' + b;
  else if(b < 52)
    return 'a' + b - 26;
  else if(b < 62)
    return '0' + b - 52;
  else if(b == 62)
    return '+';
  else if(b == 63)
    return '/';
  return 0;
}

static uint8_t unbase64one(char c)
{
  if(c >= 'A' && c <= 'Z')
    return c - 'A';
  else if(c >= 'a' && c <= 'z')
    return c - 'a' + 26;
  else if(c >= '0' && c <= '9')
    return c - '0' + 52;
  else if(c == '+')
    return 62;
  else if(c == '/')
    return 63;

  return 0xFF;
}

static void osc_selection(VTermState *state, VTermStringFragment frag)
{
  if(frag.initial) {
    state->tmp.selection.mask = 0;
    state->tmp.selection.state = SELECTION_INITIAL;
  }

  while(!state->tmp.selection.state && frag.len) {
    /* Parse selection parameter */
    switch(frag.str[0]) {
      case 'c':
        state->tmp.selection.mask |= VTERM_SELECTION_CLIPBOARD;
        break;
      case 'p':
        state->tmp.selection.mask |= VTERM_SELECTION_PRIMARY;
        break;
      case 'q':
        state->tmp.selection.mask |= VTERM_SELECTION_SECONDARY;
        break;
      case 's':
        state->tmp.selection.mask |= VTERM_SELECTION_SELECT;
        break;
      case '0':
      case '1':
      case '2':
      case '3':
      case '4':
      case '5':
      case '6':
      case '7':
        state->tmp.selection.mask |= (VTERM_SELECTION_CUT0 << (frag.str[0] - '0'));
        break;

      case ';':
        state->tmp.selection.state = SELECTION_SELECTED;
        if(!state->tmp.selection.mask)
          state->tmp.selection.mask = VTERM_SELECTION_SELECT|VTERM_SELECTION_CUT0;
        break;
    }

    frag.str++;
    frag.len--;
  }

  if(!frag.len) {
    /* Clear selection if we're already finished but didn't do anything */
    if(frag.final && state->selection.callbacks->set) {
      (*state->selection.callbacks->set)(state->tmp.selection.mask, (VTermStringFragment){
              .str     = NULL,
              .len     = 0,
              .initial = state->tmp.selection.state != SELECTION_SET,
              .final   = true,
            }, state->selection.user);
    }
    return;
  }

  if(state->tmp.selection.state == SELECTION_SELECTED) {
    if(frag.str[0] == '?') {
      state->tmp.selection.state = SELECTION_QUERY;
    }
    else {
      state->tmp.selection.state = SELECTION_SET_INITIAL;
      state->tmp.selection.recvpartial = 0;
    }
  }

  if(state->tmp.selection.state == SELECTION_QUERY) {
    if(state->selection.callbacks->query)
      (*state->selection.callbacks->query)(state->tmp.selection.mask, state->selection.user);
    return;
  }

  if(state->tmp.selection.state == SELECTION_INVALID)
    return;

  if(state->selection.callbacks->set) {
    size_t bufcur = 0;
    char *buffer = state->selection.buffer;

    uint32_t x = 0; /* Current decoding value */
    int n = 0;      /* Number of sextets consumed */

    if(state->tmp.selection.recvpartial) {
      n = state->tmp.selection.recvpartial >> 24;
      x = state->tmp.selection.recvpartial & 0x03FFFF; /* could be up to 18 bits of state in here */

      state->tmp.selection.recvpartial = 0;
    }

    while((state->selection.buflen - bufcur) >= 3 && frag.len) {
      if(frag.str[0] == '=') {
        if(n == 2) {
          buffer[0] = (x >> 4) & 0xFF;
          buffer
Download .txt
gitextract_h34518pg/

├── .bzrignore
├── .github/
│   ├── dependabot.yml
│   └── workflows/
│       └── sync.yml
├── CODE-MAP
├── CONTRIBUTING
├── LICENSE
├── Makefile
├── README.md
├── bin/
│   ├── unterm.c
│   ├── vterm-ctrl.c
│   └── vterm-dump.c
├── doc/
│   ├── URLs
│   └── seqs.txt
├── find-wide-chars.pl
├── include/
│   ├── vterm.h
│   └── vterm_keycodes.h
├── src/
│   ├── encoding/
│   │   ├── DECdrawing.inc
│   │   ├── DECdrawing.tbl
│   │   ├── uk.inc
│   │   └── uk.tbl
│   ├── encoding.c
│   ├── fullwidth.inc
│   ├── keyboard.c
│   ├── mouse.c
│   ├── parser.c
│   ├── pen.c
│   ├── rect.h
│   ├── screen.c
│   ├── state.c
│   ├── unicode.c
│   ├── utf8.h
│   ├── vterm.c
│   └── vterm_internal.h
├── t/
│   ├── 02parser.test
│   ├── 03encoding_utf8.test
│   ├── 10state_putglyph.test
│   ├── 11state_movecursor.test
│   ├── 12state_scroll.test
│   ├── 13state_edit.test
│   ├── 14state_encoding.test
│   ├── 15state_mode.test
│   ├── 16state_resize.test
│   ├── 17state_mouse.test
│   ├── 18state_termprops.test
│   ├── 20state_wrapping.test
│   ├── 21state_tabstops.test
│   ├── 22state_save.test
│   ├── 25state_input.test
│   ├── 26state_query.test
│   ├── 27state_reset.test
│   ├── 28state_dbl_wh.test
│   ├── 29state_fallback.test
│   ├── 30state_pen.test
│   ├── 31state_rep.test
│   ├── 32state_flow.test
│   ├── 40state_selection.test
│   ├── 60screen_ascii.test
│   ├── 61screen_unicode.test
│   ├── 62screen_damage.test
│   ├── 63screen_resize.test
│   ├── 64screen_pen.test
│   ├── 65screen_protect.test
│   ├── 66screen_extent.test
│   ├── 67screen_dbl_wh.test
│   ├── 68screen_termprops.test
│   ├── 69screen_pushline.test
│   ├── 69screen_reflow.test
│   ├── 90vttest_01-movement-1.test
│   ├── 90vttest_01-movement-2.test
│   ├── 90vttest_01-movement-3.test
│   ├── 90vttest_01-movement-4.test
│   ├── 90vttest_02-screen-1.test
│   ├── 90vttest_02-screen-2.test
│   ├── 90vttest_02-screen-3.test
│   ├── 90vttest_02-screen-4.test
│   ├── 92lp1640917.test
│   ├── harness.c
│   └── run-test.pl
├── tbl2inc_c.pl
└── vterm.pc.in
Download .txt
SYMBOL INDEX (284 symbols across 18 files)

FILE: bin/unterm.c
  function dump_cell_color (line 38) | static int dump_cell_color(const VTermColor *col, int sgri, int sgr[], i...
  function dump_cell (line 75) | static void dump_cell(const VTermScreenCell *cell, const VTermScreenCell...
  function dump_eol (line 155) | static void dump_eol(const VTermScreenCell *prevcell)
  function dump_row (line 171) | void dump_row(int row)
  function screen_sb_pushline (line 190) | static int screen_sb_pushline(int cols, const VTermScreenCell *cells, vo...
  function screen_resize (line 205) | static int screen_resize(int new_rows, int new_cols, void *user)
  function main (line 217) | int main(int argc, char *argv[])

FILE: bin/vterm-ctrl.c
  function getchoice (line 21) | static int getchoice(int *argip, int argc, char *argv[], const char *opt...
  type BoolQuery (line 34) | typedef enum {
  function BoolQuery (line 40) | static BoolQuery getboolq(int *argip, int argc, char *argv[])
  function seticanon (line 63) | static bool seticanon(bool icanon, bool echo)
  function await_c1 (line 82) | static void await_c1(unsigned char c1)
  function usage (line 145) | static void usage(int exitcode)
  function query_dec_mode (line 157) | static bool query_dec_mode(int mode)
  function do_dec_mode (line 195) | static void do_dec_mode(int mode, BoolQuery val, const char *name)
  function query_rqss_numeric (line 212) | static int query_rqss_numeric(char *cmd)
  function restoreicanon (line 244) | void restoreicanon(void)
  function main (line 249) | int main(int argc, char *argv[])

FILE: bin/vterm-dump.c
  function parser_text (line 19) | static int parser_text(const char bytes[], size_t len, void *user)
  function parser_control (line 64) | static int parser_control(unsigned char control, void *user)
  function parser_escape (line 80) | static int parser_escape(const char bytes[], size_t len, void *user)
  function parser_csi (line 110) | static int parser_csi(const char *leader, const long args[], int argcoun...
  function parser_osc (line 159) | static int parser_osc(int command, VTermStringFragment frag, void *user)
  function parser_dcs (line 176) | static int parser_dcs(const char *command, size_t commandlen, VTermStrin...
  function main (line 198) | int main(int argc, char *argv[])

FILE: include/vterm.h
  type VTerm (line 26) | typedef struct VTerm VTerm;
  type VTermState (line 27) | typedef struct VTermState VTermState;
  type VTermScreen (line 28) | typedef struct VTermScreen VTermScreen;
  type VTermPos (line 30) | typedef struct {
  function vterm_pos_cmp (line 38) | static inline int vterm_pos_cmp(VTermPos a, VTermPos b)
  type VTermRect (line 43) | typedef struct {
  function vterm_rect_contains (line 51) | static inline int vterm_rect_contains(VTermRect r, VTermPos p)
  function vterm_rect_move (line 58) | static inline void vterm_rect_move(VTermRect *rect, int row_delta, int c...
  type VTermColorType (line 67) | typedef enum {
  type VTermColor (line 142) | typedef union {
  function vterm_color_rgb (line 186) | static inline void vterm_color_rgb(VTermColor *col, uint8_t red, uint8_t...
  function vterm_color_indexed (line 199) | static inline void vterm_color_indexed(VTermColor *col, uint8_t idx)
  type VTermValueType (line 210) | typedef enum {
  type VTermStringFragment (line 220) | typedef struct {
  type VTermValue (line 227) | typedef union {
  type VTermAttr (line 234) | typedef enum {
  type VTermProp (line 252) | typedef enum {
  type VTermSelectionMask (line 284) | typedef enum {
  type VTermGlyphInfo (line 292) | typedef struct {
  type VTermLineInfo (line 300) | typedef struct {
  type VTermStateFields (line 311) | typedef struct {
  type VTermAllocatorFunctions (line 316) | typedef struct {
  type VTermBuilder (line 325) | struct VTermBuilder {
  type VTermBuilder (line 338) | struct VTermBuilder
  type VTermParserCallbacks (line 402) | typedef struct {
  type VTermStateCallbacks (line 427) | typedef struct {
  type VTermStateFallbacks (line 444) | typedef struct {
  type VTermSelectionCallbacks (line 454) | typedef struct {
  type VTermScreenCellAttrs (line 503) | typedef struct {
  type VTermScreenCell (line 531) | typedef struct {
  type VTermScreenCallbacks (line 538) | typedef struct {
  type VTermDamageSize (line 569) | typedef enum {
  type VTermAttrMask (line 587) | typedef enum {

FILE: include/vterm_keycodes.h
  type VTermModifier (line 4) | typedef enum {
  type VTermKey (line 13) | typedef enum {

FILE: src/encoding.c
  type UTF8DecoderData (line 9) | struct UTF8DecoderData {
  function init_utf8 (line 20) | static void init_utf8(VTermEncoding *enc, void *data_)
  function decode_utf8 (line 28) | static void decode_utf8(VTermEncoding *enc, void *data_,
  function decode_usascii (line 162) | static void decode_usascii(VTermEncoding *enc, void *data,
  type StaticTableEncoding (line 182) | struct StaticTableEncoding {
  function decode_table (line 187) | static void decode_table(VTermEncoding *enc, void *data,
  function VTermEncoding (line 224) | VTermEncoding *vterm_lookup_encoding(VTermEncodingType type, char design...

FILE: src/keyboard.c
  function vterm_keyboard_unichar (line 7) | void vterm_keyboard_unichar(VTerm *vt, uint32_t c, VTermModifier mod)
  type keycodes_s (line 54) | typedef struct {
  function vterm_keyboard_key (line 128) | void vterm_keyboard_key(VTerm *vt, VTermKey key, VTermModifier mod)
  function vterm_keyboard_start_paste (line 216) | void vterm_keyboard_start_paste(VTerm *vt)
  function vterm_keyboard_end_paste (line 222) | void vterm_keyboard_end_paste(VTerm *vt)

FILE: src/mouse.c
  function output_mouse (line 5) | static void output_mouse(VTermState *state, int code, int pressed, int m...
  function vterm_mouse_move (line 54) | void vterm_mouse_move(VTerm *vt, int row, int col, VTermModifier mod)
  function vterm_mouse_button (line 73) | void vterm_mouse_button(VTerm *vt, int button, bool pressed, VTermModifi...

FILE: src/parser.c
  function is_intermed (line 8) | static bool is_intermed(unsigned char c)
  function do_control (line 13) | static void do_control(VTerm *vt, unsigned char control)
  function do_csi (line 22) | static void do_csi(VTerm *vt, char command)
  function do_escape (line 48) | static void do_escape(VTerm *vt, char command)
  function string_fragment (line 64) | static void string_fragment(VTerm *vt, const char *str, size_t len, bool...
  function vterm_input_write (line 111) | size_t vterm_input_write(VTerm *vt, const char *bytes, size_t len)
  function vterm_parser_set_callbacks (line 388) | void vterm_parser_set_callbacks(VTerm *vt, const VTermParserCallbacks *c...
  function vterm_parser_set_emit_nul (line 399) | void vterm_parser_set_emit_nul(VTerm *vt, bool emit)

FILE: src/pen.c
  type VTermRGB (line 9) | typedef struct {
  function lookup_default_colour_ansi (line 44) | static void lookup_default_colour_ansi(long idx, VTermColor *col)
  function lookup_colour_ansi (line 53) | static bool lookup_colour_ansi(const VTermState *state, long index, VTer...
  function lookup_colour_palette (line 63) | static bool lookup_colour_palette(const VTermState *state, long index, V...
  function lookup_colour (line 91) | static int lookup_colour(const VTermState *state, int palette, const lon...
  function setpenattr (line 119) | static void setpenattr(VTermState *state, VTermAttr attr, VTermValueType...
  function setpenattr_bool (line 132) | static void setpenattr_bool(VTermState *state, VTermAttr attr, int boolean)
  function setpenattr_int (line 138) | static void setpenattr_int(VTermState *state, VTermAttr attr, int number)
  function setpenattr_col (line 144) | static void setpenattr_col(VTermState *state, VTermAttr attr, VTermColor...
  function set_pen_col_ansi (line 150) | static void set_pen_col_ansi(VTermState *state, VTermAttr attr, long col)
  function INTERNAL (line 159) | INTERNAL void vterm_state_newpen(VTermState *state)
  function INTERNAL (line 170) | INTERNAL void vterm_state_resetpen(VTermState *state)
  function INTERNAL (line 187) | INTERNAL void vterm_state_savepen(VTermState *state, int save)
  function vterm_color_is_equal (line 211) | int vterm_color_is_equal(const VTermColor *a, const VTermColor *b)
  function vterm_state_get_default_colors (line 231) | void vterm_state_get_default_colors(const VTermState *state, VTermColor ...
  function vterm_state_get_palette_color (line 237) | void vterm_state_get_palette_color(const VTermState *state, int index, V...
  function vterm_state_set_default_colors (line 242) | void vterm_state_set_default_colors(VTermState *state, const VTermColor ...
  function vterm_state_set_palette_color (line 257) | void vterm_state_set_palette_color(VTermState *state, int index, const V...
  function vterm_state_convert_color_to_rgb (line 263) | void vterm_state_convert_color_to_rgb(const VTermState *state, VTermColo...
  function vterm_state_set_bold_highbright (line 271) | void vterm_state_set_bold_highbright(VTermState *state, int bold_is_high...
  function INTERNAL (line 276) | INTERNAL void vterm_state_setpen(VTermState *state, const long args[], i...
  function vterm_state_getpen_color (line 471) | static int vterm_state_getpen_color(const VTermColor *col, int argi, lon...
  function INTERNAL (line 504) | INTERNAL int vterm_state_getpen(VTermState *state, long args[], int argc...
  function vterm_state_get_penattr (line 551) | int vterm_state_get_penattr(const VTermState *state, VTermAttr attr, VTe...

FILE: src/rect.h
  function rect_expand (line 9) | static void rect_expand(VTermRect *dst, VTermRect *src)
  function rect_clip (line 18) | static void rect_clip(VTermRect *dst, VTermRect *bounds)
  function rect_equal (line 30) | static int rect_equal(VTermRect *a, VTermRect *b)
  function rect_contains (line 39) | static int rect_contains(VTermRect *big, VTermRect *small)
  function rect_intersects (line 49) | static int rect_intersects(VTermRect *a, VTermRect *b)

FILE: src/screen.c
  type ScreenPen (line 15) | typedef struct
  type ScreenCell (line 38) | typedef struct
  type VTermScreen (line 44) | struct VTermScreen
  function clearcell (line 77) | static inline void clearcell(const VTermScreen *screen, ScreenCell *cell)
  function ScreenCell (line 83) | static inline ScreenCell *getcell(const VTermScreen *screen, int row, in...
  function ScreenCell (line 92) | static ScreenCell *alloc_buffer(VTermScreen *screen, int rows, int cols)
  function damagerect (line 105) | static void damagerect(VTermScreen *screen, VTermRect rect)
  function damagescreen (line 162) | static void damagescreen(VTermScreen *screen)
  function putglyph (line 174) | static int putglyph(VTermGlyphInfo *info, VTermPos pos, void *user)
  function sb_pushline_from_row (line 209) | static void sb_pushline_from_row(VTermScreen *screen, int row, bool cont...
  function premove (line 221) | static int premove(VTermRect rect, void *user)
  function moverect_internal (line 239) | static int moverect_internal(VTermRect dest, VTermRect src, void *user)
  function moverect_user (line 266) | static int moverect_user(VTermRect dest, VTermRect src, void *user)
  function erase_internal (line 284) | static int erase_internal(VTermRect rect, int selective, void *user)
  function erase_user (line 311) | static int erase_user(VTermRect rect, int selective, void *user)
  function erase (line 320) | static int erase(VTermRect rect, int selective, void *user)
  function scrollrect (line 326) | static int scrollrect(VTermRect rect, int downward, int rightward, void ...
  function movecursor (line 410) | static int movecursor(VTermPos pos, VTermPos oldpos, int visible, void *...
  function setpenattr (line 420) | static int setpenattr(VTermAttr attr, VTermValue *val, void *user)
  function settermprop (line 469) | static int settermprop(VTermProp prop, VTermValue *val, void *user)
  function bell (line 499) | static int bell(void *user)
  function line_popcount (line 511) | static int line_popcount(ScreenCell *buffer, int row, int rows, int cols)
  function resize_buffer (line 519) | static void resize_buffer(VTermScreen *screen, int bufidx, int new_rows,...
  function resize (line 761) | static int resize(int new_rows, int new_cols, VTermStateFields *fields, ...
  function setlineinfo (line 814) | static int setlineinfo(int row, const VTermLineInfo *newinfo, const VTer...
  function sb_clear (line 845) | static int sb_clear(void *user) {
  function VTermScreen (line 869) | static VTermScreen *screen_new(VTerm *vt)
  function INTERNAL (line 909) | INTERNAL void vterm_screen_free(VTermScreen *screen)
  function vterm_screen_reset (line 920) | void vterm_screen_reset(VTermScreen *screen, int hard)
  function _get_chars (line 928) | static size_t _get_chars(const VTermScreen *screen, const int utf8, void...
  function vterm_screen_get_chars (line 978) | size_t vterm_screen_get_chars(const VTermScreen *screen, uint32_t *chars...
  function vterm_screen_get_text (line 983) | size_t vterm_screen_get_text(const VTermScreen *screen, char *str, size_...
  function vterm_screen_get_cell (line 989) | int vterm_screen_get_cell(const VTermScreen *screen, VTermPos pos, VTerm...
  function vterm_screen_is_eol (line 1027) | int vterm_screen_is_eol(const VTermScreen *screen, VTermPos pos)
  function VTermScreen (line 1039) | VTermScreen *vterm_obtain_screen(VTerm *vt)
  function vterm_screen_enable_reflow (line 1050) | void vterm_screen_enable_reflow(VTermScreen *screen, bool reflow)
  function vterm_screen_set_reflow (line 1056) | void vterm_screen_set_reflow(VTermScreen *screen, bool reflow)
  function vterm_screen_enable_altscreen (line 1061) | void vterm_screen_enable_altscreen(VTermScreen *screen, int altscreen)
  function vterm_screen_set_callbacks (line 1071) | void vterm_screen_set_callbacks(VTermScreen *screen, const VTermScreenCa...
  function vterm_screen_callbacks_has_pushline4 (line 1082) | void vterm_screen_callbacks_has_pushline4(VTermScreen *screen)
  function vterm_screen_set_unrecognised_fallbacks (line 1087) | void vterm_screen_set_unrecognised_fallbacks(VTermScreen *screen, const ...
  function vterm_screen_flush_damage (line 1097) | void vterm_screen_flush_damage(VTermScreen *screen)
  function vterm_screen_set_damage_merge (line 1114) | void vterm_screen_set_damage_merge(VTermScreen *screen, VTermDamageSize ...
  function attrs_differ (line 1120) | static int attrs_differ(VTermAttrMask attrs, ScreenCell *a, ScreenCell *b)
  function vterm_screen_get_attrs_extent (line 1150) | int vterm_screen_get_attrs_extent(const VTermScreen *screen, VTermRect *...
  function vterm_screen_convert_color_to_rgb (line 1178) | void vterm_screen_convert_color_to_rgb(const VTermScreen *screen, VTermC...
  function reset_default_colours (line 1183) | static void reset_default_colours(VTermScreen *screen, ScreenCell *buffer)
  function vterm_screen_set_default_colors (line 1195) | void vterm_screen_set_default_colors(VTermScreen *screen, const VTermCol...

FILE: src/state.c
  function putglyph (line 14) | static void putglyph(VTermState *state, const uint32_t chars[], int widt...
  function updatecursor (line 31) | static void updatecursor(VTermState *state, VTermPos *oldpos, int cancel...
  function erase (line 44) | static void erase(VTermState *state, VTermRect rect, int selective)
  function VTermState (line 59) | static VTermState *vterm_state_new(VTerm *vt)
  function INTERNAL (line 103) | INTERNAL void vterm_state_free(VTermState *state)
  function scroll (line 113) | static void scroll(VTermState *state, VTermRect rect, int downward, int ...
  function linefeed (line 186) | static void linefeed(VTermState *state)
  function grow_combine_buffer (line 202) | static void grow_combine_buffer(VTermState *state)
  function set_col_tabstop (line 215) | static void set_col_tabstop(VTermState *state, int col)
  function clear_col_tabstop (line 221) | static void clear_col_tabstop(VTermState *state, int col)
  function is_col_tabstop (line 227) | static int is_col_tabstop(VTermState *state, int col)
  function is_cursor_in_scrollregion (line 233) | static int is_cursor_in_scrollregion(const VTermState *state)
  function tab (line 245) | static void tab(VTermState *state, int count, int direction)
  function set_lineinfo (line 276) | static void set_lineinfo(VTermState *state, int row, int force, int dwl,...
  function on_text (line 300) | static int on_text(const char bytes[], size_t len, void *user)
  function on_control (line 477) | static int on_control(unsigned char control, void *user)
  function settermprop_bool (line 576) | static int settermprop_bool(VTermState *state, VTermProp prop, int v)
  function settermprop_int (line 582) | static int settermprop_int(VTermState *state, VTermProp prop, int v)
  function settermprop_string (line 588) | static int settermprop_string(VTermState *state, VTermProp prop, VTermSt...
  function savecursor (line 594) | static void savecursor(VTermState *state, int save)
  function on_escape (line 619) | static int on_escape(const char *bytes, size_t len, void *user)
  function set_mode (line 760) | static void set_mode(VTermState *state, int num, int val)
  function set_dec_mode (line 777) | static void set_dec_mode(VTermState *state, int num, int val)
  function request_dec_mode (line 871) | static void request_dec_mode(VTermState *state, int num)
  function request_version_string (line 948) | static void request_version_string(VTermState *state)
  function on_csi (line 954) | static int on_csi(const char *leader, const long args[], int argcount, c...
  function base64_one (line 1614) | static char base64_one(uint8_t b)
  function unbase64one (line 1629) | static uint8_t unbase64one(char c)
  function osc_selection (line 1645) | static void osc_selection(VTermState *state, VTermStringFragment frag)
  function on_osc (line 1805) | static int on_osc(int command, VTermStringFragment frag, void *user)
  function request_status_string (line 1838) | static void request_status_string(VTermState *state, VTermStringFragment...
  function on_dcs (line 1925) | static int on_dcs(const char *command, size_t commandlen, VTermStringFra...
  function on_apc (line 1941) | static int on_apc(VTermStringFragment frag, void *user)
  function on_pm (line 1953) | static int on_pm(VTermStringFragment frag, void *user)
  function on_sos (line 1965) | static int on_sos(VTermStringFragment frag, void *user)
  function on_resize (line 1977) | static int on_resize(int rows, int cols, void *user)
  function VTermState (line 2087) | VTermState *vterm_obtain_state(VTerm *vt)
  function vterm_state_reset (line 2100) | void vterm_state_reset(VTermState *state, int hard)
  function vterm_state_get_cursorpos (line 2167) | void vterm_state_get_cursorpos(const VTermState *state, VTermPos *cursor...
  function vterm_state_set_callbacks (line 2172) | void vterm_state_set_callbacks(VTermState *state, const VTermStateCallba...
  function vterm_state_callbacks_has_premove (line 2187) | void vterm_state_callbacks_has_premove(VTermState *state)
  function vterm_state_set_unrecognised_fallbacks (line 2197) | void vterm_state_set_unrecognised_fallbacks(VTermState *state, const VTe...
  function vterm_state_set_termprop (line 2214) | int vterm_state_set_termprop(VTermState *state, VTermProp prop, VTermVal...
  function vterm_state_focus_in (line 2272) | void vterm_state_focus_in(VTermState *state)
  function vterm_state_focus_out (line 2278) | void vterm_state_focus_out(VTermState *state)
  function VTermLineInfo (line 2284) | const VTermLineInfo *vterm_state_get_lineinfo(const VTermState *state, i...
  function vterm_state_set_selection_callbacks (line 2289) | void vterm_state_set_selection_callbacks(VTermState *state, const VTermS...
  function vterm_state_send_selection (line 2301) | void vterm_state_send_selection(VTermState *state, VTermSelectionMask ma...

FILE: src/unicode.c
  type interval (line 70) | struct interval {
  type interval (line 77) | struct interval
  function bisearch (line 130) | static int bisearch(uint32_t ucs, const struct interval *table, int max) {
  function mk_wcwidth (line 183) | static int mk_wcwidth(uint32_t ucs)
  function mk_wcwidth_cjk (line 226) | static int mk_wcwidth_cjk(uint32_t ucs)
  type interval (line 298) | struct interval
  function INTERNAL (line 302) | INTERNAL int vterm_unicode_width(uint32_t codepoint)
  function INTERNAL (line 310) | INTERNAL int vterm_unicode_is_combining(uint32_t codepoint)

FILE: src/utf8.h
  function utf8_seqlen (line 5) | static inline unsigned int utf8_seqlen(long codepoint)
  function fill_utf8 (line 16) | static int fill_utf8(long codepoint, char *str)

FILE: src/vterm.c
  function default_free (line 20) | static void default_free(void *ptr, void *allocdata)
  function VTerm (line 30) | VTerm *vterm_new(int rows, int cols)
  function VTerm (line 38) | VTerm *vterm_new_with_allocator(int rows, int cols, VTermAllocatorFuncti...
  function VTerm (line 51) | VTerm *vterm_build(const struct VTermBuilder *builder)
  function vterm_free (line 84) | void vterm_free(VTerm *vt)
  function INTERNAL (line 98) | INTERNAL void *vterm_allocator_malloc(VTerm *vt, size_t size)
  function INTERNAL (line 103) | INTERNAL void vterm_allocator_free(VTerm *vt, void *ptr)
  function vterm_get_size (line 108) | void vterm_get_size(const VTerm *vt, int *rowsp, int *colsp)
  function vterm_set_size (line 116) | void vterm_set_size(VTerm *vt, int rows, int cols)
  function vterm_get_utf8 (line 128) | int vterm_get_utf8(const VTerm *vt)
  function vterm_set_utf8 (line 133) | void vterm_set_utf8(VTerm *vt, int is_utf8)
  function vterm_output_set_callback (line 138) | void vterm_output_set_callback(VTerm *vt, VTermOutputCallback *func, voi...
  function INTERNAL (line 144) | INTERNAL void vterm_push_output_bytes(VTerm *vt, const char *bytes, size...
  function INTERNAL (line 158) | INTERNAL void vterm_push_output_vsprintf(VTerm *vt, const char *format, ...
  function INTERNAL (line 166) | INTERNAL void vterm_push_output_sprintf(VTerm *vt, const char *format, ...)
  function INTERNAL (line 174) | INTERNAL void vterm_push_output_sprintf_ctrl(VTerm *vt, unsigned char ct...
  function INTERNAL (line 200) | INTERNAL void vterm_push_output_sprintf_str(VTerm *vt, unsigned char ctr...
  function vterm_output_get_buffer_size (line 236) | size_t vterm_output_get_buffer_size(const VTerm *vt)
  function vterm_output_get_buffer_current (line 241) | size_t vterm_output_get_buffer_current(const VTerm *vt)
  function vterm_output_get_buffer_remaining (line 246) | size_t vterm_output_get_buffer_remaining(const VTerm *vt)
  function vterm_output_read (line 251) | size_t vterm_output_read(VTerm *vt, char *buffer, size_t len)
  function VTermValueType (line 266) | VTermValueType vterm_get_attr_type(VTermAttr attr)
  function VTermValueType (line 287) | VTermValueType vterm_get_prop_type(VTermProp prop)
  function vterm_scroll_rect (line 305) | void vterm_scroll_rect(VTermRect rect,
  function vterm_copy_cells (line 374) | void vterm_copy_cells(VTermRect dest,
  function vterm_check_version (line 415) | void vterm_check_version(int major, int minor)

FILE: src/vterm_internal.h
  type VTermEncoding (line 30) | typedef struct VTermEncoding VTermEncoding;
  type VTermEncodingInstance (line 32) | typedef struct {
  type VTermPen (line 39) | struct VTermPen
  type VTermState (line 55) | struct VTermState
  type VTerm (line 177) | struct VTerm
  type VTermEncoding (line 252) | struct VTermEncoding {
  type VTermEncodingType (line 259) | typedef enum {

FILE: t/harness.c
  function inplace_hex2bytes (line 11) | static size_t inplace_hex2bytes(char *s)
  function VTermModifier (line 26) | static VTermModifier strpe_modifiers(char **strp)
  function VTermKey (line 42) | static VTermKey strp_key(char *str)
  function print_color (line 64) | static void print_color(const VTermColor *col)
  function VTermColor (line 84) | static VTermColor strpe_color(char **strp)
  function term_output (line 110) | static void term_output(const char *s, size_t len, void *user)
  function printhex (line 117) | static void printhex(const char *s, size_t len)
  function parser_text (line 123) | static int parser_text(const char bytes[], size_t len, void *user)
  function parser_control (line 138) | static int parser_control(unsigned char control, void *user)
  function parser_escape (line 145) | static int parser_escape(const char bytes[], size_t len, void *user)
  function parser_csi (line 163) | static int parser_csi(const char *leader, const long args[], int argcoun...
  function parser_osc (line 193) | static int parser_osc(int command, VTermStringFragment frag, void *user)
  function parser_dcs (line 214) | static int parser_dcs(const char *command, size_t commandlen, VTermStrin...
  function parser_apc (line 234) | static int parser_apc(VTermStringFragment frag, void *user)
  function parser_pm (line 251) | static int parser_pm(VTermStringFragment frag, void *user)
  function parser_sos (line 268) | static int parser_sos(VTermStringFragment frag, void *user)
  function movecursor (line 311) | static int movecursor(VTermPos pos, VTermPos oldpos, int visible, void *...
  function premove (line 322) | static int premove(VTermRect rect, void *user)
  function scrollrect (line 334) | static int scrollrect(VTermRect rect, int downward, int rightward, void ...
  function moverect (line 347) | static int moverect(VTermRect dest, VTermRect src, void *user)
  function settermprop (line 360) | static int settermprop(VTermProp prop, VTermValue *val, void *user)
  function state_putglyph (line 393) | static int state_putglyph(VTermGlyphInfo *info, VTermPos pos, void *user)
  function state_erase (line 414) | static int state_erase(VTermRect rect, int selective, void *user)
  function state_setpenattr (line 440) | static int state_setpenattr(VTermAttr attr, VTermValue *val, void *user)
  function state_setlineinfo (line 487) | static int state_setlineinfo(int row, const VTermLineInfo *newinfo, cons...
  function state_sb_clear (line 493) | static int state_sb_clear(void *user) {
  function selection_set (line 514) | static int selection_set(VTermSelectionMask mask, VTermStringFragment fr...
  function selection_query (line 527) | static int selection_query(VTermSelectionMask mask, void *user)
  function screen_damage (line 541) | static int screen_damage(VTermRect rect, void *user)
  function screen_sb_pushline4 (line 585) | static int screen_sb_pushline4(int cols, const VTermScreenCell *cells, b...
  function screen_sb_popline (line 602) | static int screen_sb_popline(int cols, VTermScreenCell *cells, void *user)
  function screen_sb_clear (line 621) | static int screen_sb_clear(void *user)
  function main (line 640) | int main(int argc, char **argv)
Condensed preview — 80 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (376K chars).
[
  {
    "path": ".bzrignore",
    "chars": 102,
    "preview": ".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",
    "chars": 156,
    "preview": "version: 2\nupdates:\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"daily\"\n   "
  },
  {
    "path": ".github/workflows/sync.yml",
    "chars": 1041,
    "preview": "name: sync\non:\n  schedule:\n    - cron: '30 1 * * *' # Run every day at 01:30\n  workflow_dispatch:\n\npermissions:\n  conten"
  },
  {
    "path": "CODE-MAP",
    "chars": 1873,
    "preview": "bin/\n - contains some standalone programs\n\nbin/unterm.c\n - an example program using libvterm to reconstruct the final st"
  },
  {
    "path": "CONTRIBUTING",
    "chars": 643,
    "preview": "How to Contribute\n-----------------\n\nThe main resources for this library are:\n\n  Launchpad\n    https://launchpad.net/lib"
  },
  {
    "path": "LICENSE",
    "chars": 1098,
    "preview": "\n\nThe MIT License\n\nCopyright (c) 2008 Paul Evans <leonerd@leonerd.org.uk>\n\nPermission is hereby granted, free of charge,"
  },
  {
    "path": "Makefile",
    "chars": 3566,
    "preview": "ifeq ($(shell uname),Darwin)\n  LIBTOOL ?= glibtool\nelse\n  LIBTOOL ?= libtool\nendif\n\nifneq ($(VERBOSE),1)\n  LIBTOOL +=--q"
  },
  {
    "path": "README.md",
    "chars": 359,
    "preview": "# libvterm fork\n\n**Important**: This is currently not used by neovim. Vterm has instead been\nbundled into the neovim rep"
  },
  {
    "path": "bin/unterm.c",
    "chars": 6815,
    "preview": "#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#"
  },
  {
    "path": "bin/vterm-ctrl.c",
    "chars": 7603,
    "preview": "#define _XOPEN_SOURCE 600  /* strdup */\n\n#include <stdbool.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>"
  },
  {
    "path": "bin/vterm-dump.c",
    "chars": 6169,
    "preview": "// 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"
  },
  {
    "path": "doc/URLs",
    "chars": 391,
    "preview": "ECMA-48:\n  http://www.ecma-international.org/publications/standards/Ecma-048.htm\n\nXterm Control Sequences:\n  http://invi"
  },
  {
    "path": "doc/seqs.txt",
    "chars": 11293,
    "preview": "Sequences documented in parens are implicit ones from parser.c, which move\nbetween states.\n\n1 = VT100\n2 = VT220\n3 = VT32"
  },
  {
    "path": "find-wide-chars.pl",
    "chars": 551,
    "preview": "#!/usr/bin/perl\n\nuse strict;\nuse warnings;\n\nSTDOUT->autoflush(1);\n\nsub iswide\n{\n   my ( $cp ) = @_;\n   return chr($cp) ="
  },
  {
    "path": "include/vterm.h",
    "chars": 21584,
    "preview": "#ifndef __VTERM_H__\n#define __VTERM_H__\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n#include <stdint.h>\n#include <stdlib.h>"
  },
  {
    "path": "include/vterm_keycodes.h",
    "chars": 1106,
    "preview": "#ifndef __VTERM_INPUT_H__\n#define __VTERM_INPUT_H__\n\ntypedef enum {\n  VTERM_MOD_NONE  = 0x00,\n  VTERM_MOD_SHIFT = 0x01,\n"
  },
  {
    "path": "src/encoding/DECdrawing.inc",
    "chars": 757,
    "preview": "static const struct StaticTableEncoding encoding_DECdrawing = {\n  { .decode = &decode_table },\n  {\n    [0x60] = 0x25C6,\n"
  },
  {
    "path": "src/encoding/DECdrawing.tbl",
    "chars": 1392,
    "preview": "6/0 = U+25C6 # BLACK DIAMOND\n6/1 = U+2592 # MEDIUM SHADE (checkerboard)\n6/2 = U+2409 # SYMBOL FOR HORIZONTAL TAB\n6/3 = U"
  },
  {
    "path": "src/encoding/uk.inc",
    "chars": 119,
    "preview": "static const struct StaticTableEncoding encoding_uk = {\n  { .decode = &decode_table },\n  {\n    [0x23] = 0x00a3,\n  }\n};\n"
  },
  {
    "path": "src/encoding/uk.tbl",
    "chars": 10,
    "preview": "2/3 = \"£\"\n"
  },
  {
    "path": "src/encoding.c",
    "chars": 5752,
    "preview": "#include \"vterm_internal.h\"\n\n#define UNICODE_INVALID 0xFFFD\n\n#if defined(DEBUG) && DEBUG > 1\n# define DEBUG_PRINT_UTF8\n#"
  },
  {
    "path": "src/fullwidth.inc",
    "chars": 2544,
    "preview": "  { 0x1100, 0x115f },\n  { 0x231a, 0x231b },\n  { 0x2329, 0x232a },\n  { 0x23e9, 0x23ec },\n  { 0x23f0, 0x23f0 },\n  { 0x23f3"
  },
  {
    "path": "src/keyboard.c",
    "chars": 6159,
    "preview": "#include \"vterm_internal.h\"\n\n#include <stdio.h>\n\n#include \"utf8.h\"\n\nvoid vterm_keyboard_unichar(VTerm *vt, uint32_t c, V"
  },
  {
    "path": "src/mouse.c",
    "chars": 2507,
    "preview": "#include \"vterm_internal.h\"\n\n#include \"utf8.h\"\n\nstatic void output_mouse(VTermState *state, int code, int pressed, int m"
  },
  {
    "path": "src/parser.c",
    "chars": 10837,
    "preview": "#include \"vterm_internal.h\"\n\n#include <stdio.h>\n#include <string.h>\n\n#undef DEBUG_PARSER\n\nstatic bool is_intermed(unsign"
  },
  {
    "path": "src/pen.c",
    "chars": 17535,
    "preview": "#include \"vterm_internal.h\"\n\n#include <stdio.h>\n\n/**\n * Structure used to store RGB triples without the additional metad"
  },
  {
    "path": "src/rect.h",
    "chars": 2022,
    "preview": "/*\n * Some utility functions on VTermRect structures\n */\n\n#define STRFrect \"(%d,%d-%d,%d)\"\n#define ARGSrect(r) (r).start"
  },
  {
    "path": "src/screen.c",
    "chars": 35807,
    "preview": "#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 UNICOD"
  },
  {
    "path": "src/state.c",
    "chars": 63690,
    "preview": "#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 defi"
  },
  {
    "path": "src/unicode.c",
    "chars": 14254,
    "preview": "#include \"vterm_internal.h\"\n\n// ### The following from http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c\n// With modification"
  },
  {
    "path": "src/utf8.h",
    "chars": 1040,
    "preview": "/* The following functions copied and adapted from libtermkey\n *\n * http://www.leonerd.org.uk/code/libtermkey/\n */\nstati"
  },
  {
    "path": "src/vterm.c",
    "chars": 10703,
    "preview": "#include \"vterm_internal.h\"\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <stdarg.h>\n#include <string.h>\n\n/**********"
  },
  {
    "path": "src/vterm_internal.h",
    "chars": 7140,
    "preview": "#ifndef __VTERM_INTERNAL_H__\n#define __VTERM_INTERNAL_H__\n\n#include \"vterm.h\"\n\n#include <stdarg.h>\n\n#if defined(__GNUC__"
  },
  {
    "path": "t/02parser.test",
    "chars": 3531,
    "preview": "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 "
  },
  {
    "path": "t/03encoding_utf8.test",
    "chars": 3221,
    "preview": "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 t"
  },
  {
    "path": "t/10state_putglyph.test",
    "chars": 1367,
    "preview": "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"
  },
  {
    "path": "t/11state_movecursor.test",
    "chars": 2955,
    "preview": "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 \"\\"
  },
  {
    "path": "t/12state_scroll.test",
    "chars": 2623,
    "preview": "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 ="
  },
  {
    "path": "t/13state_edit.test",
    "chars": 4844,
    "preview": "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 \""
  },
  {
    "path": "t/14state_encoding.test",
    "chars": 1371,
    "preview": "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 "
  },
  {
    "path": "t/15state_mode.test",
    "chars": 1415,
    "preview": "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"
  },
  {
    "path": "t/16state_resize.test",
    "chars": 778,
    "preview": "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"
  },
  {
    "path": "t/17state_mouse.test",
    "chars": 3509,
    "preview": "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["
  },
  {
    "path": "t/18state_termprops.test",
    "chars": 694,
    "preview": "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 "
  },
  {
    "path": "t/20state_wrapping.test",
    "chars": 1259,
    "preview": "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"
  },
  {
    "path": "t/21state_tabstops.test",
    "chars": 898,
    "preview": "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!H"
  },
  {
    "path": "t/22state_save.test",
    "chars": 1011,
    "preview": "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  ?cur"
  },
  {
    "path": "t/25state_input.test",
    "chars": 2283,
    "preview": "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\nINC"
  },
  {
    "path": "t/26state_query.test",
    "chars": 1002,
    "preview": "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\\"
  },
  {
    "path": "t/27state_reset.test",
    "chars": 430,
    "preview": "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\nWANTSTAT"
  },
  {
    "path": "t/28state_dbl_wh.test",
    "chars": 1247,
    "preview": "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,"
  },
  {
    "path": "t/29state_fallback.test",
    "chars": 408,
    "preview": "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"
  },
  {
    "path": "t/30state_pen.test",
    "chars": 2311,
    "preview": "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"
  },
  {
    "path": "t/31state_rep.test",
    "chars": 2669,
    "preview": "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 (zer"
  },
  {
    "path": "t/32state_flow.test",
    "chars": 493,
    "preview": "INIT\nWANTSTATE\n\n# Many of these test cases inspired by\n#   https://blueprints.launchpad.net/libvterm/+spec/reflow-cases\n"
  },
  {
    "path": "t/40state_selection.test",
    "chars": 1904,
    "preview": "INIT\nUTF8 1\nWANTSTATE\n\n!Set clipboard; final chunk len 4\nPUSH \"\\e]52;c;SGVsbG8s\\e\\\\\"\n  selection-set mask=0001 [\"Hello,\""
  },
  {
    "path": "t/60screen_ascii.test",
    "chars": 1496,
    "preview": "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 = \"A"
  },
  {
    "path": "t/61screen_unicode.test",
    "chars": 1950,
    "preview": "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 = 0xC"
  },
  {
    "path": "t/62screen_damage.test",
    "chars": 2972,
    "preview": "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<3"
  },
  {
    "path": "t/63screen_resize.test",
    "chars": 2391,
    "preview": "INIT\nWANTSTATE\nWANTSCREEN\n\n!Resize wider preserves cells\nRESET\nRESIZE 25,80\nPUSH \"AB\\r\\nCD\"\n  ?screen_chars 0,0,1,80 = \""
  },
  {
    "path": "t/64screen_pen.test",
    "chars": 2445,
    "preview": "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"
  },
  {
    "path": "t/65screen_protect.test",
    "chars": 242,
    "preview": "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 "
  },
  {
    "path": "t/66screen_extent.test",
    "chars": 245,
    "preview": "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"
  },
  {
    "path": "t/67screen_dbl_wh.test",
    "chars": 1003,
    "preview": "INIT\nWANTSCREEN\n\nRESET\n\n!Single Width, Single Height\nRESET\nPUSH \"\\e#5\"\nPUSH \"abcde\"\n  ?screen_cell 0,0 = {0x61} width=1 "
  },
  {
    "path": "t/68screen_termprops.test",
    "chars": 254,
    "preview": "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"
  },
  {
    "path": "t/69screen_pushline.test",
    "chars": 508,
    "preview": "INIT\nWANTSTATE\nWANTSCREEN b\n\nRESET\n\n!Spillover text marks continuation on second line\nPUSH \"A\"x85\nPUSH \"\\r\\n\"\n  ?lineinf"
  },
  {
    "path": "t/69screen_reflow.test",
    "chars": 1782,
    "preview": "INIT\n# Run these tests on a much smaller default screen, so debug output is\n# nowhere near as noisy\nRESIZE 5,10\nWANTSTAT"
  },
  {
    "path": "t/90vttest_01-movement-1.test",
    "chars": 2764,
    "preview": "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$SE"
  },
  {
    "path": "t/90vttest_01-movement-2.test",
    "chars": 2728,
    "preview": "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\\"
  },
  {
    "path": "t/90vttest_01-movement-3.test",
    "chars": 559,
    "preview": "# 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"
  },
  {
    "path": "t/90vttest_01-movement-4.test",
    "chars": 1022,
    "preview": "# Test of leading zeroes in ESC sequences\nINIT\nWANTSCREEN\n\nRESET\n\nPUSH \"\\e[00000000004;000000001HT\"\nPUSH \"\\e[00000000004"
  },
  {
    "path": "t/90vttest_02-screen-1.test",
    "chars": 339,
    "preview": "# 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 "
  },
  {
    "path": "t/90vttest_02-screen-2.test",
    "chars": 490,
    "preview": "# 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\nPU"
  },
  {
    "path": "t/90vttest_02-screen-3.test",
    "chars": 181,
    "preview": "# Origin mode\nINIT\nWANTSCREEN\n\nRESET\n\nPUSH \"\\e[?6h\"\nPUSH \"\\e[23;24r\"\nPUSH \"\\n\"\nPUSH \"Bottom\"\nPUSH \"\\e[1;1H\"\nPUSH \"Above\""
  },
  {
    "path": "t/90vttest_02-screen-4.test",
    "chars": 188,
    "preview": "# 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\"\nPU"
  },
  {
    "path": "t/92lp1640917.test",
    "chars": 257,
    "preview": "INIT\nWANTSTATE \n\n!Mouse reporting should not break by idempotent DECSM 1002\nPUSH \"\\e[?1002h\"\nMOUSEMOVE 0,0 0\nMOUSEBTN d "
  },
  {
    "path": "t/harness.c",
    "chars": 32321,
    "preview": "#include \"vterm.h\"\n#include \"../src/vterm_internal.h\" // We pull in some internal bits too\n\n#include <assert.h>\n#include"
  },
  {
    "path": "t/run-test.pl",
    "chars": 6678,
    "preview": "#!/usr/bin/perl\n\nuse strict;\nuse warnings;\nuse Getopt::Long;\nuse IO::Handle;\nuse IPC::Open2 qw( open2 );\nuse POSIX qw( W"
  },
  {
    "path": "tbl2inc_c.pl",
    "chars": 622,
    "preview": "#!/usr/bin/perl\n\nuse strict;\nuse warnings;\n\nmy ( $encname ) = $ARGV[0] =~ m{/([^/.]+).tbl}\n   or die \"Cannot parse encod"
  },
  {
    "path": "vterm.pc.in",
    "chars": 178,
    "preview": "libdir=@LIBDIR@\nincludedir=@INCDIR@\n\nName: vterm\nDescription: Abstract VT220/Xterm/ECMA-48 emulation library\nVersion: @V"
  }
]

About this extraction

This page contains the full source code of the neovim/libvterm GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 80 files (344.2 KB), approximately 121.3k tokens, and a symbol index with 284 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!