changeset 13531:9f857e6310b6 v8.0.1639

patch 8.0.1639: libvterm code lags behind master commit https://github.com/vim/vim/commit/b5b49a3b430ea0aaf8cce6b7bc0e444f0211ddfb Author: Bram Moolenaar <Bram@vim.org> Date: Sun Mar 25 16:20:37 2018 +0200 patch 8.0.1639: libvterm code lags behind master Problem: Libvterm code lags behind master. Solution: Sync to head, solve merge problems.
author Christian Brabandt <cb@256bit.org>
date Sun, 25 Mar 2018 16:30:06 +0200
parents 28031e9f0da4
children 76d71093e910
files src/libvterm/README src/libvterm/bin/unterm.c src/libvterm/bin/vterm-ctrl.c src/libvterm/bin/vterm-dump.c src/libvterm/doc/URLs src/libvterm/doc/seqs.txt src/libvterm/include/vterm.h src/libvterm/include/vterm_keycodes.h src/libvterm/src/mouse.c src/libvterm/src/parser.c src/libvterm/src/pen.c src/libvterm/src/screen.c src/libvterm/src/state.c src/libvterm/src/vterm.c src/libvterm/src/vterm_internal.h src/libvterm/t/10state_putglyph.test src/libvterm/t/25state_input.test src/libvterm/t/26state_query.test src/libvterm/t/harness.c src/version.c
diffstat 20 files changed, 391 insertions(+), 263 deletions(-) [+]
line wrap: on
line diff
--- a/src/libvterm/README
+++ b/src/libvterm/README
@@ -10,3 +10,21 @@ Modifications:
 - Add a .gitignore file.
 - Convert from C99 to C90.
 - Other changes to support embedding in Vim.
+
+
+To merge in changes from Github, do this:
+- Commit any pending changes.
+- Setup the merge tool:
+    git config merge.tool vimdiff
+    git config merge.conflictstyle diff3
+    git config mergetool.prompt false
+- Run the merge tool:
+    git mergetool
+  This will open a four-way diff between:
+     LOCAL  - your current version
+     BASE   - version as it was at your last sync
+     REMOTE - version at head on Github
+     MERGED - best-effort merge of LOCAL and REMOTE
+  Now find places where automatic merge didn't work, they are marked with
+  <<<<<<<<, ======= and >>>>>>>
+  Fix those places in MERGED, remove the markers, and save the file :wqall.
--- a/src/libvterm/bin/unterm.c
+++ b/src/libvterm/bin/unterm.c
@@ -95,8 +95,8 @@ static void dump_cell(const VTermScreenC
             sgr[sgri++] = 90 + (index - 8);
           else {
             sgr[sgri++] = 38;
-            sgr[sgri++] = 5 | (1<<31);
-            sgr[sgri++] = index | (1<<31);
+            sgr[sgri++] = 5 | CSI_ARG_FLAG_MORE;
+            sgr[sgri++] = index | CSI_ARG_FLAG_MORE;
           }
         }
 
@@ -112,8 +112,8 @@ static void dump_cell(const VTermScreenC
             sgr[sgri++] = 100 + (index - 8);
           else {
             sgr[sgri++] = 48;
-            sgr[sgri++] = 5 | (1<<31);
-            sgr[sgri++] = index | (1<<31);
+            sgr[sgri++] = 5 | CSI_ARG_FLAG_MORE;
+            sgr[sgri++] = index | CSI_ARG_FLAG_MORE;
           }
         }
 
@@ -125,9 +125,9 @@ static void dump_cell(const VTermScreenC
 	  int i;
 	  for(i = 0; i < sgri; i++)
 	    printf(!i               ? "%d" :
-		sgr[i] & (1<<31) ? ":%d" :
+		CSI_ARG_HAS_MORE(sgr[i]) ? ":%d" :
 		";%d",
-		sgr[i] & ~(1<<31));
+		CSI_ARG(sgr[i]));
 	}
         printf("m");
       }
@@ -283,5 +283,6 @@ int main(int argc, char *argv[])
   close(fd);
 
   vterm_free(vt);
+
   return 0;
 }
--- a/src/libvterm/bin/vterm-ctrl.c
+++ b/src/libvterm/bin/vterm-ctrl.c
@@ -53,6 +53,7 @@ static char *helptext[] = {
   "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]",
@@ -81,9 +82,9 @@ static int seticanon(int icanon, int ech
   return ret;
 }
 
-static void await_c1(int c1)
+static void await_c1(unsigned char c1)
 {
-  int c;
+  unsigned char c;
 
   /* await CSI - 8bit or 2byte 7bit form */
   int in_esc = FALSE;
@@ -340,6 +341,9 @@ int main(int argc, char *argv[])
         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");
     }
--- a/src/libvterm/bin/vterm-dump.c
+++ b/src/libvterm/bin/vterm-dump.c
@@ -227,5 +227,6 @@ int main(int argc, char *argv[])
 
   close(fd);
   vterm_free(vt);
+
   return 0;
 }
--- a/src/libvterm/doc/URLs
+++ b/src/libvterm/doc/URLs
@@ -9,3 +9,6 @@ Digital VT100 User Guide:
 
 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
--- a/src/libvterm/doc/seqs.txt
+++ b/src/libvterm/doc/seqs.txt
@@ -151,6 +151,7 @@ 123   DECSM 7          = DECAWM, autowra
       DECSM 1000       = Mouse click/release tracking
       DECSM 1002       = Mouse click/release/drag tracking
       DECSM 1003       = Mouse all movements tracking
+      DECSM 1004       = Focus in/out reporting
       DECSM 1005       = Mouse protocol extended (UTF-8) - not recommended
       DECSM 1006       = Mouse protocol SGR
       DECSM 1015       = Mouse protocol rxvt
--- a/src/libvterm/include/vterm.h
+++ b/src/libvterm/include/vterm.h
@@ -96,7 +96,9 @@ typedef enum {
   VTERM_VALUETYPE_BOOL = 1,
   VTERM_VALUETYPE_INT,
   VTERM_VALUETYPE_STRING,
-  VTERM_VALUETYPE_COLOR
+  VTERM_VALUETYPE_COLOR,
+
+  VTERM_N_VALUETYPES
 } VTermValueType;
 
 typedef union {
@@ -116,7 +118,9 @@ typedef enum {
   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_BACKGROUND, /* color:  40-49 100-107 */
+
+  VTERM_N_ATTRS
 } VTermAttr;
 
 typedef enum {
@@ -129,20 +133,26 @@ typedef enum {
   VTERM_PROP_REVERSE,           /* bool */
   VTERM_PROP_CURSORSHAPE,       /* number */
   VTERM_PROP_MOUSE,             /* number */
-  VTERM_PROP_CURSORCOLOR        /* string */
+  VTERM_PROP_CURSORCOLOR,       /* string */
+
+  VTERM_N_PROPS
 } VTermProp;
 
 enum {
   VTERM_PROP_CURSORSHAPE_BLOCK = 1,
   VTERM_PROP_CURSORSHAPE_UNDERLINE,
-  VTERM_PROP_CURSORSHAPE_BAR_LEFT
+  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_PROP_MOUSE_MOVE,
+
+  VTERM_N_PROP_MOUSES
 };
 
 typedef struct {
@@ -213,8 +223,8 @@ void vterm_mouse_button(VTerm *vt, int b
  *
  * Don't confuse this with the final byte of the CSI escape; 'a' in this case.
  */
-#define CSI_ARG_FLAG_MORE (1<<30)
-#define CSI_ARG_MASK      (~(1<<30))
+#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)
@@ -293,6 +303,8 @@ void vterm_state_set_palette_color(VTerm
 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);
 
 /* ------------
@@ -357,7 +369,9 @@ typedef enum {
   VTERM_DAMAGE_CELL,    /* every cell */
   VTERM_DAMAGE_ROW,     /* entire rows */
   VTERM_DAMAGE_SCREEN,  /* entire screen */
-  VTERM_DAMAGE_SCROLL   /* entire screen + scrollrect */
+  VTERM_DAMAGE_SCROLL,  /* entire screen + scrollrect */
+
+  VTERM_N_DAMAGES
 } VTermDamageSize;
 
 /* Invoke the relevant callbacks to update the screen. */
@@ -384,7 +398,9 @@ typedef enum {
   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_BACKGROUND_MASK = 1 << 8,
+
+  VTERM_ALL_ATTRS_MASK = (1 << 9) - 1
 } VTermAttrMask;
 
 int vterm_screen_get_attrs_extent(const VTermScreen *screen, VTermRect *extent, VTermPos pos, VTermAttrMask attrs);
--- a/src/libvterm/include/vterm_keycodes.h
+++ b/src/libvterm/include/vterm_keycodes.h
@@ -5,7 +5,9 @@ typedef enum {
   VTERM_MOD_NONE  = 0x00,
   VTERM_MOD_SHIFT = 0x01,
   VTERM_MOD_ALT   = 0x02,
-  VTERM_MOD_CTRL  = 0x04
+  VTERM_MOD_CTRL  = 0x04,
+
+  VTERM_ALL_MODS_MASK = 0x07 
 } VTermModifier;
 
 /* The order here must match keycodes[] in src/keyboard.c! */
@@ -53,7 +55,8 @@ typedef enum {
   VTERM_KEY_KP_ENTER,
   VTERM_KEY_KP_EQUAL,
 
-  VTERM_KEY_MAX /* Must be last */
+  VTERM_KEY_MAX, /* Must be last */
+  VTERM_N_KEYS = VTERM_KEY_MAX
 } VTermKey;
 
 #define VTERM_KEY_FUNCTION(n) (VTERM_KEY_FUNCTION_0+(n))
--- a/src/libvterm/src/mouse.c
+++ b/src/libvterm/src/mouse.c
@@ -63,9 +63,9 @@ void vterm_mouse_move(VTerm *vt, int 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;
+    int button = state->mouse_buttons & MOUSE_BUTTON_LEFT ? 1 :
+                 state->mouse_buttons & MOUSE_BUTTON_MIDDLE ? 2 :
+                 state->mouse_buttons & MOUSE_BUTTON_RIGHT ? 3 : 4;
     output_mouse(state, button-1 + 0x20, 1, mod, col, row);
   }
 }
--- a/src/libvterm/src/parser.c
+++ b/src/libvterm/src/parser.c
@@ -3,188 +3,123 @@
 #include <stdio.h>
 #include <string.h>
 
-#define CSI_ARGS_MAX 16
-#define CSI_LEADER_MAX 16
-#define CSI_INTERMED_MAX 16
+#undef DEBUG_PARSER
+
+static int is_intermed(unsigned char c)
+{
+  return c >= 0x20 && c <= 0x2f;
+}
 
 static void do_control(VTerm *vt, unsigned char control)
 {
-  if(vt->parser_callbacks && vt->parser_callbacks->control)
-    if((*vt->parser_callbacks->control)(control, vt->cbdata))
+  if(vt->parser.callbacks && vt->parser.callbacks->control)
+    if((*vt->parser.callbacks->control)(control, vt->parser.cbdata))
       return;
 
   DEBUG_LOG1("libvterm: Unhandled control 0x%02x\n", control);
 }
 
-static void do_string_csi(VTerm *vt, const char *args, size_t arglen, char command)
+static void do_csi(VTerm *vt, char command)
 {
-  int i = 0;
-
-  int leaderlen = 0;
-  char leader[CSI_LEADER_MAX];
-  int argcount = 1; /* Always at least 1 arg */
-  long csi_args[CSI_ARGS_MAX];
-  int argi;
-  int intermedlen = 0;
-  char intermed[CSI_INTERMED_MAX];
-
-  /* Extract leader bytes 0x3c to 0x3f */
-  for( ; i < (int)arglen; i++) {
-    if(args[i] < 0x3c || args[i] > 0x3f)
-      break;
-    if(leaderlen < CSI_LEADER_MAX-1)
-      leader[leaderlen++] = args[i];
-  }
-
-  leader[leaderlen] = 0;
-
-  for( ; i < (int)arglen; i++)
-    if(args[i] == 0x3b || args[i] == 0x3a) /* ; or : */
-      argcount++;
-
-  /* TODO: Consider if these buffers should live in the VTerm struct itself */
-  if(argcount > CSI_ARGS_MAX)
-    argcount = CSI_ARGS_MAX;
-
-  for(argi = 0; argi < argcount; argi++)
-    csi_args[argi] = CSI_ARG_MISSING;
-
-  argi = 0;
-  for(i = leaderlen; i < (int)arglen && argi < argcount; i++) {
-    switch(args[i]) {
-    case 0x30: case 0x31: case 0x32: case 0x33: case 0x34:
-    case 0x35: case 0x36: case 0x37: case 0x38: case 0x39:
-      if(csi_args[argi] == CSI_ARG_MISSING)
-        csi_args[argi] = 0;
-      csi_args[argi] *= 10;
-      csi_args[argi] += args[i] - '0';
-      break;
-    case 0x3a:
-      csi_args[argi] |= CSI_ARG_FLAG_MORE;
-      /* FALLTHROUGH */
-    case 0x3b:
-      argi++;
-      break;
-    default:
-      goto done_leader;
-    }
-  }
-done_leader: ;
-
-  for( ; i < (int)arglen; i++) {
-    if((args[i] & 0xf0) != 0x20)
-      break;
-
-    if(intermedlen < CSI_INTERMED_MAX-1)
-      intermed[intermedlen++] = args[i];
-  }
-
-  intermed[intermedlen] = 0;
-
-  if(i < (int)arglen) {
-    DEBUG_LOG2("libvterm: TODO unhandled CSI bytes \"%.*s\"\n", (int)(arglen - i), args + i);
-  }
-
-#if 0
-  printf("Parsed CSI args %.*s as:\n", arglen, args);
-  printf(" leader: %s\n", leader);
-  for(argi = 0; argi < argcount; argi++) {
-    printf(" %lu", CSI_ARG(csi_args[argi]));
-    if(!CSI_ARG_HAS_MORE(csi_args[argi]))
+#ifdef DEBUG_PARSER
+  printf("Parsed CSI args as:\n", arglen, args);
+  printf(" leader: %s\n", vt->parser.csi_leader);
+  for(int argi = 0; argi < vt->parser.csi_argi; argi++) {
+    printf(" %lu", CSI_ARG(vt->parser.csi_args[argi]));
+    if(!CSI_ARG_HAS_MORE(vt->parser.csi_args[argi]))
       printf("\n");
-  printf(" intermed: %s\n", intermed);
+  printf(" intermed: %s\n", vt->parser.intermed);
   }
 #endif
 
-  if(vt->parser_callbacks && vt->parser_callbacks->csi)
-    if((*vt->parser_callbacks->csi)(leaderlen ? leader : NULL, csi_args, argcount, intermedlen ? intermed : NULL, command, vt->cbdata))
+  if(vt->parser.callbacks && vt->parser.callbacks->csi)
+    if((*vt->parser.callbacks->csi)(
+          vt->parser.csi_leaderlen ? vt->parser.csi_leader : NULL,
+          vt->parser.csi_args,
+          vt->parser.csi_argi,
+          vt->parser.intermedlen ? vt->parser.intermed : NULL,
+          command,
+          vt->parser.cbdata))
       return;
 
-  DEBUG_LOG3("libvterm: Unhandled CSI %.*s %c\n", (int)arglen, args, command);
+  DEBUG_LOG1("libvterm: Unhandled CSI %c\n", command);
+}
+
+static void do_escape(VTerm *vt, char command)
+{
+  char seq[INTERMED_MAX+1];
+
+  size_t len = vt->parser.intermedlen;
+  strncpy(seq, vt->parser.intermed, len);
+  seq[len++] = command;
+  seq[len]   = 0;
+
+  if(vt->parser.callbacks && vt->parser.callbacks->escape)
+    if((*vt->parser.callbacks->escape)(seq, len, vt->parser.cbdata))
+      return;
+
+  DEBUG_LOG1("libvterm: Unhandled escape ESC 0x%02x\n", command);
 }
 
 static void append_strbuffer(VTerm *vt, const char *str, size_t len)
 {
-  if(len > vt->strbuffer_len - vt->strbuffer_cur) {
-    len = vt->strbuffer_len - vt->strbuffer_cur;
+  if(len > vt->parser.strbuffer_len - vt->parser.strbuffer_cur) {
+    len = vt->parser.strbuffer_len - vt->parser.strbuffer_cur;
     DEBUG_LOG1("Truncating strbuffer preserve to %zd bytes\n", len);
   }
 
   if(len > 0) {
-    strncpy(vt->strbuffer + vt->strbuffer_cur, str, len);
-    vt->strbuffer_cur += len;
+    strncpy(vt->parser.strbuffer + vt->parser.strbuffer_cur, str, len);
+    vt->parser.strbuffer_cur += len;
   }
 }
 
-static size_t do_string(VTerm *vt, const char *str_frag, size_t len)
+static void start_string(VTerm *vt, VTermParserStringType type)
 {
-  size_t eaten;
+  vt->parser.stringtype = type;
+
+  vt->parser.strbuffer_cur = 0;
+}
 
-  if(vt->strbuffer_cur) {
-    if(str_frag)
-      append_strbuffer(vt, str_frag, len);
+static void more_string(VTerm *vt, const char *str, size_t len)
+{
+  append_strbuffer(vt, str, len);
+}
 
-    str_frag = vt->strbuffer;
-    len = vt->strbuffer_cur;
+static void done_string(VTerm *vt, const char *str, size_t len)
+{
+  if(vt->parser.strbuffer_cur) {
+    if(str)
+      append_strbuffer(vt, str, len);
+
+    str = vt->parser.strbuffer;
+    len = vt->parser.strbuffer_cur;
   }
-  else if(!str_frag) {
+  else if(!str) {
     DEBUG_LOG("parser.c: TODO: No strbuffer _and_ no final fragment???\n");
     len = 0;
   }
 
-  vt->strbuffer_cur = 0;
-
-  switch(vt->parser_state) {
-  case NORMAL:
-    if(vt->parser_callbacks && vt->parser_callbacks->text)
-      if((eaten = (*vt->parser_callbacks->text)(str_frag, len, vt->cbdata)))
-        return eaten;
-
-    DEBUG_LOG1("libvterm: Unhandled text (%zu chars)\n", len);
-    return 0;
+  switch(vt->parser.stringtype) {
+  case VTERM_PARSER_OSC:
+    if(vt->parser.callbacks && vt->parser.callbacks->osc)
+      if((*vt->parser.callbacks->osc)(str, len, vt->parser.cbdata))
+        return;
 
-  case ESC:
-    if(len == 1 && str_frag[0] >= 0x40 && str_frag[0] < 0x60) {
-      /* C1 emulations using 7bit clean */
-      /* ESC 0x40 == 0x80 */
-      do_control(vt, str_frag[0] + 0x40);
-      return 0;
-    }
-
-    if(vt->parser_callbacks && vt->parser_callbacks->escape)
-      if((*vt->parser_callbacks->escape)(str_frag, len, vt->cbdata))
-        return 0;
-
-    DEBUG_LOG1("libvterm: Unhandled escape ESC 0x%02x\n", str_frag[len-1]);
-    return 0;
+    DEBUG_LOG2("libvterm: Unhandled OSC %.*s\n", (int)len, str);
+    return;
 
-  case CSI:
-    do_string_csi(vt, str_frag, len - 1, str_frag[len - 1]);
-    return 0;
-
-  case OSC:
-    if(vt->parser_callbacks && vt->parser_callbacks->osc)
-      if((*vt->parser_callbacks->osc)(str_frag, len, vt->cbdata))
-        return 0;
-
-    DEBUG_LOG2("libvterm: Unhandled OSC %.*s\n", (int)len, str_frag);
-    return 0;
+  case VTERM_PARSER_DCS:
+    if(vt->parser.callbacks && vt->parser.callbacks->dcs)
+      if((*vt->parser.callbacks->dcs)(str, len, vt->parser.cbdata))
+        return;
 
-  case DCS:
-    if(vt->parser_callbacks && vt->parser_callbacks->dcs)
-      if((*vt->parser_callbacks->dcs)(str_frag, len, vt->cbdata))
-        return 0;
-
-    DEBUG_LOG2("libvterm: Unhandled DCS %.*s\n", (int)len, str_frag);
-    return 0;
+    DEBUG_LOG2("libvterm: Unhandled DCS %.*s\n", (int)len, str);
+    return;
 
-  case ESC_IN_OSC:
-  case ESC_IN_DCS:
-    DEBUG_LOG("libvterm: ARGH! Should never do_string() in ESC_IN_{OSC,DCS}\n");
-    return 0;
+  case VTERM_N_PARSER_TYPES:
+    return;
   }
-
-  return 0;
 }
 
 size_t vterm_input_write(VTerm *vt, const char *bytes, size_t len)
@@ -192,29 +127,30 @@ size_t vterm_input_write(VTerm *vt, cons
   size_t pos = 0;
   const char *string_start = NULL;  /* init to avoid gcc warning */
 
-  switch(vt->parser_state) {
+  switch(vt->parser.state) {
   case NORMAL:
+  case CSI_LEADER:
+  case CSI_ARGS:
+  case CSI_INTERMED:
+  case ESC:
     string_start = NULL;
     break;
-  case ESC:
-  case ESC_IN_OSC:
-  case ESC_IN_DCS:
-  case CSI:
-  case OSC:
-  case DCS:
+  case STRING:
+  case ESC_IN_STRING:
     string_start = bytes;
     break;
   }
 
-#define ENTER_STRING_STATE(st) do { vt->parser_state = st; string_start = bytes + pos + 1; } while(0)
-#define ENTER_NORMAL_STATE()   do { vt->parser_state = NORMAL; string_start = NULL; } while(0)
+#define ENTER_STRING_STATE(st) do { vt->parser.state = STRING; string_start = bytes + pos + 1; } while(0)
+#define ENTER_STATE(st)        do { vt->parser.state = st; string_start = NULL; } while(0)
+#define ENTER_NORMAL_STATE()   ENTER_STATE(NORMAL)
 
   for( ; pos < len; pos++) {
     unsigned char c = bytes[pos];
 
     if(c == 0x00 || c == 0x7f) { /* NUL, DEL */
-      if(vt->parser_state != NORMAL) {
-        append_strbuffer(vt, string_start, bytes + pos - string_start);
+      if(vt->parser.state >= STRING) {
+        more_string(vt, string_start, bytes + pos - string_start);
         string_start = bytes + pos + 1;
       }
       continue;
@@ -224,64 +160,64 @@ size_t vterm_input_write(VTerm *vt, cons
       continue;
     }
     else if(c == 0x1b) { /* ESC */
-      if(vt->parser_state == OSC)
-        vt->parser_state = ESC_IN_OSC;
-      else if(vt->parser_state == DCS)
-        vt->parser_state = ESC_IN_DCS;
+      vt->parser.intermedlen = 0;
+      if(vt->parser.state == STRING)
+        vt->parser.state = ESC_IN_STRING;
       else
-        ENTER_STRING_STATE(ESC);
+        ENTER_STATE(ESC);
       continue;
     }
     else if(c == 0x07 &&  /* BEL, can stand for ST in OSC or DCS state */
-            (vt->parser_state == OSC || vt->parser_state == DCS)) {
+            vt->parser.state == STRING) {
       /* fallthrough */
     }
     else if(c < 0x20) { /* other C0 */
-      if(vt->parser_state != NORMAL)
-        append_strbuffer(vt, string_start, bytes + pos - string_start);
+      if(vt->parser.state >= STRING)
+        more_string(vt, string_start, bytes + pos - string_start);
       do_control(vt, c);
-      if(vt->parser_state != NORMAL)
+      if(vt->parser.state >= STRING)
         string_start = bytes + pos + 1;
       continue;
     }
     /* else fallthrough */
 
-    switch(vt->parser_state) {
-    case ESC_IN_OSC:
-    case ESC_IN_DCS:
+    switch(vt->parser.state) {
+    case ESC_IN_STRING:
       if(c == 0x5c) { /* ST */
-        switch(vt->parser_state) {
-          case ESC_IN_OSC: vt->parser_state = OSC; break;
-          case ESC_IN_DCS: vt->parser_state = DCS; break;
-          default: break;
-        }
-        do_string(vt, string_start, bytes + pos - string_start - 1);
+        vt->parser.state = STRING;
+        done_string(vt, string_start, bytes + pos - string_start - 1);
         ENTER_NORMAL_STATE();
         break;
       }
-      vt->parser_state = ESC;
-      string_start = bytes + pos;
+      vt->parser.state = ESC;
       /* else fallthrough */
 
     case ESC:
       switch(c) {
       case 0x50: /* DCS */
-        ENTER_STRING_STATE(DCS);
+        start_string(vt, VTERM_PARSER_DCS);
+        ENTER_STRING_STATE();
         break;
       case 0x5b: /* CSI */
-        ENTER_STRING_STATE(CSI);
+        vt->parser.csi_leaderlen = 0;
+        ENTER_STATE(CSI_LEADER);
         break;
       case 0x5d: /* OSC */
-        ENTER_STRING_STATE(OSC);
+        start_string(vt, VTERM_PARSER_OSC);
+        ENTER_STRING_STATE();
         break;
       default:
-        if(c >= 0x30 && c < 0x7f) {
-          /* +1 to pos because we want to include this command byte as well */
-          do_string(vt, string_start, bytes + pos - string_start + 1);
+        if(is_intermed(c)) {
+          if(vt->parser.intermedlen < INTERMED_MAX-1)
+            vt->parser.intermed[vt->parser.intermedlen++] = c;
+        }
+        else if(!vt->parser.intermedlen && c >= 0x40 && c < 0x60) {
+          do_control(vt, c + 0x40);
           ENTER_NORMAL_STATE();
         }
-        else if(c >= 0x20 && c < 0x30) {
-          /* intermediate byte */
+        else if(c >= 0x30 && c < 0x7f) {
+          do_escape(vt, c);
+          ENTER_NORMAL_STATE();
         }
         else {
           DEBUG_LOG1("TODO: Unhandled byte %02x in Escape\n", c);
@@ -289,18 +225,67 @@ size_t vterm_input_write(VTerm *vt, cons
       }
       break;
 
-    case CSI:
-      if(c >= 0x40 && c <= 0x7f) {
-        /* +1 to pos because we want to include this command byte as well */
-        do_string(vt, string_start, bytes + pos - string_start + 1);
-        ENTER_NORMAL_STATE();
+    case CSI_LEADER:
+      /* Extract leader bytes 0x3c to 0x3f */
+      if(c >= 0x3c && c <= 0x3f) {
+        if(vt->parser.csi_leaderlen < CSI_LEADER_MAX-1)
+          vt->parser.csi_leader[vt->parser.csi_leaderlen++] = c;
+        break;
+      }
+
+      /* else fallthrough */
+      vt->parser.csi_leader[vt->parser.csi_leaderlen] = 0;
+
+      vt->parser.csi_argi = 0;
+      vt->parser.csi_args[0] = CSI_ARG_MISSING;
+      vt->parser.state = CSI_ARGS;
+
+      /* fallthrough */
+    case CSI_ARGS:
+      /* Numerical value of argument */
+      if(c >= '0' && c <= '9') {
+        if(vt->parser.csi_args[vt->parser.csi_argi] == CSI_ARG_MISSING)
+          vt->parser.csi_args[vt->parser.csi_argi] = 0;
+        vt->parser.csi_args[vt->parser.csi_argi] *= 10;
+        vt->parser.csi_args[vt->parser.csi_argi] += c - '0';
+        break;
       }
+      if(c == ':') {
+        vt->parser.csi_args[vt->parser.csi_argi] |= CSI_ARG_FLAG_MORE;
+        c = ';';
+      }
+      if(c == ';') {
+        vt->parser.csi_argi++;
+        vt->parser.csi_args[vt->parser.csi_argi] = CSI_ARG_MISSING;
+        break;
+      }
+
+      /* else fallthrough */
+      vt->parser.csi_argi++;
+      vt->parser.intermedlen = 0;
+      vt->parser.state = CSI_INTERMED;
+      /* fallthrough */
+    case CSI_INTERMED:
+      if(is_intermed(c)) {
+        if(vt->parser.intermedlen < INTERMED_MAX-1)
+          vt->parser.intermed[vt->parser.intermedlen++] = c;
+        break;
+      }
+      else if(c == 0x1b) {
+        /* ESC in CSI cancels */
+      }
+      else if(c >= 0x40 && c <= 0x7e) {
+        vt->parser.intermed[vt->parser.intermedlen] = 0;
+        do_csi(vt, c);
+      }
+      /* else was invalid CSI */
+
+      ENTER_NORMAL_STATE();
       break;
 
-    case OSC:
-    case DCS:
+    case STRING:
       if(c == 0x07 || (c == 0x9c && !vt->mode.utf8)) {
-        do_string(vt, string_start, bytes + pos - string_start);
+        done_string(vt, string_start, bytes + pos - string_start);
         ENTER_NORMAL_STATE();
       }
       break;
@@ -309,13 +294,15 @@ size_t vterm_input_write(VTerm *vt, cons
       if(c >= 0x80 && c < 0xa0 && !vt->mode.utf8) {
         switch(c) {
         case 0x90: /* DCS */
-          ENTER_STRING_STATE(DCS);
+          start_string(vt, VTERM_PARSER_DCS);
+          ENTER_STRING_STATE();
           break;
         case 0x9b: /* CSI */
-          ENTER_STRING_STATE(CSI);
+          ENTER_STATE(CSI_LEADER);
           break;
         case 0x9d: /* OSC */
-          ENTER_STRING_STATE(OSC);
+          start_string(vt, VTERM_PARSER_OSC);
+          ENTER_STRING_STATE();
           break;
         default:
           do_control(vt, c);
@@ -323,24 +310,32 @@ size_t vterm_input_write(VTerm *vt, cons
         }
       }
       else {
-        size_t text_eaten = do_string(vt, bytes + pos, len - pos);
+        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(text_eaten == 0) {
-          string_start = bytes + pos;
-          goto pause;
+        if(!eaten) {
+          DEBUG_LOG("libvterm: Text callback did not consume any input\n");
+          /* force it to make progress */
+          eaten = 1;
         }
 
-        pos += (text_eaten - 1); /* we'll ++ it again in a moment */
+        pos += (eaten - 1); /* we'll ++ it again in a moment */
       }
       break;
     }
   }
 
-pause:
-  if(string_start && string_start < len + bytes) {
-    size_t remaining = len - (string_start - bytes);
-    append_strbuffer(vt, string_start, remaining);
-  }
-
   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;
+}
--- a/src/libvterm/src/pen.c
+++ b/src/libvterm/src/pen.c
@@ -507,6 +507,9 @@ int vterm_state_get_penattr(const VTermS
   case VTERM_ATTR_BACKGROUND:
     val->color = state->pen.bg;
     return 1;
+
+  case VTERM_N_ATTRS:
+    return 0;
   }
 
   return 0;
--- a/src/libvterm/src/screen.c
+++ b/src/libvterm/src/screen.c
@@ -429,6 +429,9 @@ static int setpenattr(VTermAttr attr, VT
   case VTERM_ATTR_BACKGROUND:
     screen->pen.bg = val->color;
     return 1;
+
+  case VTERM_N_ATTRS:
+    return 0;
   }
 
   return 0;
--- a/src/libvterm/src/state.c
+++ b/src/libvterm/src/state.c
@@ -268,7 +268,7 @@ static int on_text(const char bytes[], s
   if(!npoints)
   {
     vterm_allocator_free(state->vt, codepoints);
-    return 0;
+    return eaten;
   }
 
   if(state->gsingle_set && npoints)
@@ -781,6 +781,10 @@ static void set_dec_mode(VTermState *sta
                         VTERM_PROP_MOUSE_MOVE);
     break;
 
+  case 1004:
+    state->mode.report_focus = val;
+    break;
+
   case 1005:
     state->mouse_protocol = val ? MOUSE_UTF8 : MOUSE_X10;
     break;
@@ -861,6 +865,10 @@ static void request_dec_mode(VTermState 
       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;
@@ -1728,6 +1736,7 @@ void vterm_state_reset(VTermState *state
   state->mode.origin          = 0;
   state->mode.leftrightmargin = 0;
   state->mode.bracketpaste    = 0;
+  state->mode.report_focus    = 0;
 
   state->vt->mode.ctrl8bit   = 0;
 
@@ -1882,11 +1891,26 @@ int vterm_state_set_termprop(VTermState 
     if(val->number == VTERM_PROP_MOUSE_MOVE)
       state->mouse_flags |= MOUSE_WANT_MOVE;
     return 1;
+
+  case VTERM_N_PROPS:
+    return 0;
   }
 
   return 0;
 }
 
+void vterm_state_focus_in(VTermState *state)
+{
+  if(state->mode.report_focus)
+    vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "I");
+}
+
+void vterm_state_focus_out(VTermState *state)
+{
+  if(state->mode.report_focus)
+    vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "O");
+}
+
 const VTermLineInfo *vterm_state_get_lineinfo(const VTermState *state, int row)
 {
   return state->lineinfo + row;
--- a/src/libvterm/src/vterm.c
+++ b/src/libvterm/src/vterm.c
@@ -47,14 +47,14 @@ VTerm *vterm_new_with_allocator(int rows
   vt->rows = rows;
   vt->cols = cols;
 
-  vt->parser_state = NORMAL;
+  vt->parser.state = NORMAL;
 
-  vt->parser_callbacks = NULL;
-  vt->cbdata           = NULL;
+  vt->parser.callbacks = NULL;
+  vt->parser.cbdata    = NULL;
 
-  vt->strbuffer_len = 64;
-  vt->strbuffer_cur = 0;
-  vt->strbuffer = vterm_allocator_malloc(vt, vt->strbuffer_len);
+  vt->parser.strbuffer_len = 64;
+  vt->parser.strbuffer_cur = 0;
+  vt->parser.strbuffer = vterm_allocator_malloc(vt, vt->parser.strbuffer_len);
 
   vt->outbuffer_len = 200;
   vt->outbuffer_cur = 0;
@@ -71,7 +71,7 @@ void vterm_free(VTerm *vt)
   if(vt->state)
     vterm_state_free(vt->state);
 
-  vterm_allocator_free(vt, vt->strbuffer);
+  vterm_allocator_free(vt, vt->parser.strbuffer);
   vterm_allocator_free(vt, vt->outbuffer);
 
   vterm_allocator_free(vt, vt);
@@ -100,8 +100,8 @@ void vterm_set_size(VTerm *vt, int rows,
   vt->rows = rows;
   vt->cols = cols;
 
-  if(vt->parser_callbacks && vt->parser_callbacks->resize)
-    (*vt->parser_callbacks->resize)(rows, cols, vt->cbdata);
+  if(vt->parser.callbacks && vt->parser.callbacks->resize)
+    (*vt->parser.callbacks->resize)(rows, cols, vt->parser.cbdata);
 }
 
 int vterm_get_utf8(const VTerm *vt)
@@ -257,17 +257,6 @@ size_t vterm_output_read(VTerm *vt, char
   return len;
 }
 
-void vterm_parser_set_callbacks(VTerm *vt, const VTermParserCallbacks *callbacks, void *user)
-{
-  vt->parser_callbacks = callbacks;
-  vt->cbdata = user;
-}
-
-void *vterm_parser_get_cbdata(VTerm *vt)
-{
-  return vt->cbdata;
-}
-
 VTermValueType vterm_get_attr_type(VTermAttr attr)
 {
   switch(attr) {
@@ -280,6 +269,8 @@ VTermValueType vterm_get_attr_type(VTerm
     case VTERM_ATTR_FONT:       return VTERM_VALUETYPE_INT;
     case VTERM_ATTR_FOREGROUND: return VTERM_VALUETYPE_COLOR;
     case VTERM_ATTR_BACKGROUND: return VTERM_VALUETYPE_COLOR;
+
+    case VTERM_N_ATTRS: return 0;
   }
   return 0; /* UNREACHABLE */
 }
@@ -296,6 +287,8 @@ VTermValueType vterm_get_prop_type(VTerm
     case VTERM_PROP_CURSORSHAPE:   return VTERM_VALUETYPE_INT;
     case VTERM_PROP_MOUSE:         return VTERM_VALUETYPE_INT;
     case VTERM_PROP_CURSORCOLOR:   return VTERM_VALUETYPE_STRING;
+
+    case VTERM_N_PROPS: return 0;
   }
   return 0; /* UNREACHABLE */
 }
--- a/src/libvterm/src/vterm_internal.h
+++ b/src/libvterm/src/vterm_internal.h
@@ -27,6 +27,11 @@
 
 #define ESC_S "\x1b"
 
+#define INTERMED_MAX 16
+
+#define CSI_ARGS_MAX 16
+#define CSI_LEADER_MAX 16
+
 typedef struct VTermEncoding VTermEncoding;
 
 typedef struct {
@@ -118,6 +123,7 @@ struct VTermState
     unsigned int screen:1;
     unsigned int leftrightmargin:1;
     unsigned int bracketpaste:1;
+    unsigned int report_focus:1;
   } mode;
 
   VTermEncodingInstance encoding[4], encoding_utf8;
@@ -148,6 +154,13 @@ struct VTermState
   } saved;
 };
 
+typedef enum {
+  VTERM_PARSER_OSC,
+  VTERM_PARSER_DCS,
+
+  VTERM_N_PARSER_TYPES
+} VTermParserStringType;
+
 struct VTerm
 {
   VTermAllocatorFunctions *allocator;
@@ -161,22 +174,37 @@ struct VTerm
     unsigned int ctrl8bit:1;
   } mode;
 
-  enum VTermParserState {
-    NORMAL,
-    CSI,
-    OSC,
-    DCS,
-    ESC,
-    ESC_IN_OSC,
-    ESC_IN_DCS
-  } parser_state;
-  const VTermParserCallbacks *parser_callbacks;
-  void *cbdata;
+  struct {
+    enum VTermParserState {
+      NORMAL,
+      CSI_LEADER,
+      CSI_ARGS,
+      CSI_INTERMED,
+      ESC,
+      /* below here are the "string states" */
+      STRING,
+      ESC_IN_STRING
+    } state;
+
+    int intermedlen;
+    char intermed[INTERMED_MAX];
+
+    int csi_leaderlen;
+    char csi_leader[CSI_LEADER_MAX];
+
+    int csi_argi;
+    long csi_args[CSI_ARGS_MAX];
+
+    const VTermParserCallbacks *callbacks;
+    void *cbdata;
+
+    VTermParserStringType stringtype;
+    char  *strbuffer;
+    size_t strbuffer_len;
+    size_t strbuffer_cur;
+  } parser;
 
   /* len == malloc()ed size; cur == number of valid bytes */
-  char  *strbuffer;
-  size_t strbuffer_len;
-  size_t strbuffer_cur;
 
   char  *outbuffer;
   size_t outbuffer_len;
--- a/src/libvterm/t/10state_putglyph.test
+++ b/src/libvterm/t/10state_putglyph.test
@@ -17,6 +17,12 @@ PUSH "\xC3\x81\xC3\xA9"
   putglyph 0xc1 1 0,0
   putglyph 0xe9 1 0,1
 
+!UTF-8 split writes
+RESET
+PUSH "\xC3"
+PUSH "\x81"
+  putglyph 0xc1 1 0,0
+
 !UTF-8 wide char
 # U+FF10 = 0xEF 0xBC 0x90  name: FULLWIDTH DIGIT ZERO
 RESET
--- a/src/libvterm/t/25state_input.test
+++ b/src/libvterm/t/25state_input.test
@@ -130,3 +130,14 @@ PASTE START
   output "\e[200~"
 PASTE END
   output "\e[201~"
+
+!Focus reporting disabled
+FOCUS IN
+FOCUS OUT
+
+!Focus reporting enabled
+PUSH "\e[?1004h"
+FOCUS IN
+  output "\e[I"
+FOCUS OUT
+  output "\e[O"
--- a/src/libvterm/t/26state_query.test
+++ b/src/libvterm/t/26state_query.test
@@ -58,5 +58,5 @@ PUSH "\e[5n"
 PUSH "\e F"
 
 !Truncation on attempted buffer overflow
-PUSH "\e[6n" x 20
-  output "\e[10;10R" x 7
+PUSH "\e[6n" x 30
+  output "\e[10;10R" x 24
--- a/src/libvterm/t/harness.c
+++ b/src/libvterm/t/harness.c
@@ -233,6 +233,9 @@ static int settermprop(VTermProp prop, V
   case VTERM_VALUETYPE_COLOR:
     printf("settermprop %d rgb(%d,%d,%d)\n", prop, val->color.red, val->color.green, val->color.blue);
     return 1;
+
+  case VTERM_N_VALUETYPES:
+    return 0;
   }
 
   return 0;
@@ -316,6 +319,9 @@ static int state_setpenattr(VTermAttr at
   case VTERM_ATTR_BACKGROUND:
     state_pen.background = val->color;
     break;
+
+  case VTERM_N_ATTRS:
+    return 0;
   }
 
   return 1;
@@ -651,6 +657,16 @@ int main(int argc, char **argv)
         goto abort_line;
     }
 
+    else if(strstartswith(line, "FOCUS ")) {
+      char *linep = line + 6;
+      if(streq(linep, "IN"))
+        vterm_state_focus_in(state);
+      else if(streq(linep, "OUT"))
+        vterm_state_focus_out(state);
+      else
+        goto abort_line;
+    }
+
     else if(strstartswith(line, "MOUSEMOVE ")) {
       char *linep = line + 10;
       int row, col, len;
--- a/src/version.c
+++ b/src/version.c
@@ -767,6 +767,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    1639,
+/**/
     1638,
 /**/
     1637,