changeset 20488:1d595fada804 v8.2.0798

patch 8.2.0798: libvterm code lags behind the upstream version Commit: https://github.com/vim/vim/commit/be593bf135f6967335b14ba188bd5f8f32175c75 Author: Bram Moolenaar <Bram@vim.org> Date: Tue May 19 21:20:04 2020 +0200 patch 8.2.0798: libvterm code lags behind the upstream version Problem: Libvterm code lags behind the upstream version. Solution: Include revisions 755 - 758.
author Bram Moolenaar <Bram@vim.org>
date Tue, 19 May 2020 21:30:07 +0200
parents f4ada29cdf4b
children 863438017d49
files src/libvterm/include/vterm.h src/libvterm/src/parser.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/02parser.test src/libvterm/t/18state_termprops.test src/libvterm/t/29state_fallback.test src/libvterm/t/68screen_termprops.test src/libvterm/t/harness.c src/libvterm/t/run-test.pl src/terminal.c src/version.c
diffstat 14 files changed, 395 insertions(+), 311 deletions(-) [+]
line wrap: on
line diff
--- a/src/libvterm/include/vterm.h
+++ b/src/libvterm/include/vterm.h
@@ -107,10 +107,17 @@ typedef enum {
   VTERM_N_VALUETYPES
 } VTermValueType;
 
+typedef struct {
+  const char *str;
+  size_t      len : 30;
+  unsigned int  initial : 1;
+  unsigned int  final : 1;
+} VTermStringFragment;
+
 typedef union {
   int boolean;
   int number;
-  char *string;
+  VTermStringFragment string;
   VTermColor color;
 } VTermValue;
 
@@ -257,8 +264,8 @@ typedef struct {
   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)(const char *command, size_t cmdlen, void *user);
-  int (*dcs)(const char *command, size_t cmdlen, void *user);
+  int (*osc)(int command, VTermStringFragment frag, void *user);
+  int (*dcs)(const char *command, size_t commandlen, VTermStringFragment frag, void *user);
   int (*resize)(int rows, int cols, void *user);
 } VTermParserCallbacks;
 
--- a/src/libvterm/src/parser.c
+++ b/src/libvterm/src/parser.c
@@ -23,10 +23,10 @@ static void do_csi(VTerm *vt, char comma
 {
 #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(" 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);
   }
@@ -34,9 +34,9 @@ static void do_csi(VTerm *vt, char comma
 
   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.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))
@@ -61,65 +61,36 @@ static void do_escape(VTerm *vt, char co
   DEBUG_LOG1("libvterm: Unhandled escape ESC 0x%02x\n", command);
 }
 
-static void append_strbuffer(VTerm *vt, const char *str, size_t len)
+static void string_fragment(VTerm *vt, const char *str, size_t len, int final)
 {
-  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 %zu bytes\n", len);
+  VTermStringFragment frag;
+
+  frag.str = str;
+  frag.len = len;
+  frag.initial = vt->parser.string_initial;
+  frag.final = final;
+
+  switch(vt->parser.state) {
+    case OSC:
+      if(vt->parser.callbacks && vt->parser.callbacks->osc)
+        (*vt->parser.callbacks->osc)(vt->parser.v.osc.command, frag, vt->parser.cbdata);
+      break;
+
+    case DCS:
+      if(len && 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 NORMAL:
+    case CSI_LEADER:
+    case CSI_ARGS:
+    case CSI_INTERMED:
+    case OSC_COMMAND:
+    case DCS_COMMAND:
+      break;
   }
 
-  if(len > 0) {
-    strncpy(vt->parser.strbuffer + vt->parser.strbuffer_cur, str, len);
-    vt->parser.strbuffer_cur += len;
-  }
-}
-
-static void start_string(VTerm *vt, VTermParserStringType type)
-{
-  vt->parser.stringtype = type;
-
-  vt->parser.strbuffer_cur = 0;
-}
-
-static void more_string(VTerm *vt, const char *str, size_t len)
-{
-  append_strbuffer(vt, str, len);
-}
-
-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) {
-    DEBUG_LOG("parser.c: TODO: No strbuffer _and_ no final fragment???\n");
-    len = 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;
-
-    DEBUG_LOG2("libvterm: Unhandled OSC %.*s\n", (int)len, str);
-    return;
-
-  case VTERM_PARSER_DCS:
-    if(vt->parser.callbacks && vt->parser.callbacks->dcs)
-      if((*vt->parser.callbacks->dcs)(str, len, vt->parser.cbdata))
-        return;
-
-    DEBUG_LOG2("libvterm: Unhandled DCS %.*s\n", (int)len, str);
-    return;
-
-  case VTERM_N_PARSER_TYPES:
-    return;
-  }
+  vt->parser.string_initial = FALSE;
 }
 
 size_t vterm_input_write(VTerm *vt, const char *bytes, size_t len)
@@ -135,43 +106,45 @@ size_t vterm_input_write(VTerm *vt, cons
   case CSI_LEADER:
   case CSI_ARGS:
   case CSI_INTERMED:
-  case ESC:
+  case OSC_COMMAND:
+  case DCS_COMMAND:
     string_start = NULL;
     break;
-  case STRING:
-  case ESC_IN_STRING:
+  case OSC:
+  case DCS:
     string_start = bytes;
     break;
   }
 
-#define ENTER_STRING_STATE()   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];
+    int c1_allowed = !vt->mode.utf8;
+    size_t string_len;
 
     if(c == 0x00 || c == 0x7f) { // NUL, DEL
-      if(vt->parser.state >= STRING) {
-        more_string(vt, string_start, bytes + pos - string_start);
+      if(vt->parser.state >= OSC) {
+        string_fragment(vt, string_start, bytes + pos - string_start, FALSE);
         string_start = bytes + pos + 1;
       }
       continue;
     }
     if(c == 0x18 || c == 0x1a) { // CAN, SUB
+      vt->parser.in_esc = FALSE;
       ENTER_NORMAL_STATE();
       continue;
     }
     else if(c == 0x1b) { // ESC
       vt->parser.intermedlen = 0;
-      if(vt->parser.state == STRING)
-        vt->parser.state = ESC_IN_STRING;
-      else
-        ENTER_STATE(ESC);
+      if(vt->parser.state < OSC)
+        vt->parser.state = NORMAL;
+      vt->parser.in_esc = TRUE;
       continue;
     }
     else if(c == 0x07 &&  // BEL, can stand for ST in OSC or DCS state
-            vt->parser.state == STRING) {
+            vt->parser.state >= OSC) {
       // fallthrough
     }
     else if(c < 0x20) { // other C0
@@ -182,96 +155,72 @@ size_t vterm_input_write(VTerm *vt, cons
           if(pos + 2 < len && bytes[pos + 1] == 0x20 && bytes[pos + 2] == 0x08)
             vt->in_backspace = 2; // Trigger when count down to 1
       }
-      if(vt->parser.state >= STRING)
-        more_string(vt, string_start, bytes + pos - string_start);
+      if(vt->parser.state >= OSC)
+        string_fragment(vt, string_start, bytes + pos - string_start, FALSE);
       do_control(vt, c);
-      if(vt->parser.state >= STRING)
+      if(vt->parser.state >= OSC)
         string_start = bytes + pos + 1;
       continue;
     }
     // else fallthrough
 
-    switch(vt->parser.state) {
-    case ESC_IN_STRING:
-      if(c == 0x5c) { // ST
-        vt->parser.state = STRING;
-        done_string(vt, string_start, bytes + pos - string_start - 1);
-        ENTER_NORMAL_STATE();
-        break;
-      }
-      vt->parser.state = ESC;
-      // else fallthrough
+    string_len = bytes + pos - string_start;
 
-    case ESC:
-      switch(c) {
-      case 0x50: // DCS
-        start_string(vt, VTERM_PARSER_DCS);
-        ENTER_STRING_STATE();
-        break;
-      case 0x5b: // CSI
-        vt->parser.csi_leaderlen = 0;
-        ENTER_STATE(CSI_LEADER);
-        break;
-      case 0x5d: // OSC
-        start_string(vt, VTERM_PARSER_OSC);
-        ENTER_STRING_STATE();
-        break;
-      default:
-        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 >= 0x30 && c < 0x7f) {
-          do_escape(vt, c);
-          ENTER_NORMAL_STATE();
-        }
-        else {
-          DEBUG_LOG1("TODO: Unhandled byte %02x in Escape\n", c);
-        }
+    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 &&
+          ((vt->parser.state < OSC || c == 0x5c))) {
+        c += 0x40;
+        c1_allowed = TRUE;
+        string_len -= 1;
+        vt->parser.in_esc = FALSE;
       }
-      break;
+      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.csi_leaderlen < CSI_LEADER_MAX-1)
-          vt->parser.csi_leader[vt->parser.csi_leaderlen++] = c;
+        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.csi_leader[vt->parser.csi_leaderlen] = 0;
+      vt->parser.v.csi.leader[vt->parser.v.csi.leaderlen] = 0;
 
-      vt->parser.csi_argi = 0;
-      vt->parser.csi_args[0] = CSI_ARG_MISSING;
+      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.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';
+        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.csi_args[vt->parser.csi_argi] |= CSI_ARG_FLAG_MORE;
+        vt->parser.v.csi.args[vt->parser.v.csi.argi] |= CSI_ARG_FLAG_MORE;
         c = ';';
       }
       if(c == ';') {
-        vt->parser.csi_argi++;
-        vt->parser.csi_args[vt->parser.csi_argi] = CSI_ARG_MISSING;
+        vt->parser.v.csi.argi++;
+        vt->parser.v.csi.args[vt->parser.v.csi.argi] = CSI_ARG_MISSING;
         break;
       }
 
       // else fallthrough
-      vt->parser.csi_argi++;
+      vt->parser.v.csi.argi++;
       vt->parser.intermedlen = 0;
       vt->parser.state = CSI_INTERMED;
       // fallthrough
@@ -293,31 +242,77 @@ size_t vterm_input_write(VTerm *vt, cons
       ENTER_NORMAL_STATE();
       break;
 
-    case STRING:
-      if(c == 0x07 || (c == 0x9c && !vt->mode.utf8)) {
-        done_string(vt, string_start, bytes + pos - string_start);
-        ENTER_NORMAL_STATE();
+    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 if (pos + 1 == len) {
-	// end of input but OSC string isn't finished yet, copy it to
-	// vt->parser.strbuffer to continue it later
-        more_string(vt, string_start, bytes + pos + 1 - string_start);
+
+      /* else fallthrough */
+      string_start = bytes + pos;
+      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:
+      if(c == 0x07 || (c1_allowed && c == 0x9c)) {
+        string_fragment(vt, string_start, string_len, TRUE);
+        ENTER_NORMAL_STATE();
       }
       break;
 
     case NORMAL:
-      if(c >= 0x80 && c < 0xa0 && !vt->mode.utf8) {
+      if(vt->parser.in_esc) {
+        if(is_intermed(c)) {
+          if(vt->parser.intermedlen < INTERMED_MAX-1)
+            vt->parser.intermed[vt->parser.intermedlen++] = c;
+        }
+        else if(c >= 0x30 && c < 0x7f) {
+          do_escape(vt, c);
+          vt->parser.in_esc = 0;
+          ENTER_NORMAL_STATE();
+        }
+        else {
+          DEBUG_LOG1("TODO: Unhandled byte %02x in Escape\n", c);
+        }
+        break;
+      }
+      if(c1_allowed && c >= 0x80 && c < 0xa0) {
         switch(c) {
         case 0x90: // DCS
-          start_string(vt, VTERM_PARSER_DCS);
-          ENTER_STRING_STATE();
+          vt->parser.string_initial = TRUE;
+          vt->parser.v.dcs.commandlen = 0;
+          ENTER_STATE(DCS_COMMAND);
           break;
         case 0x9b: // CSI
+          vt->parser.v.csi.leaderlen = 0;
           ENTER_STATE(CSI_LEADER);
           break;
         case 0x9d: // OSC
-          start_string(vt, VTERM_PARSER_OSC);
-          ENTER_STRING_STATE();
+          vt->parser.v.osc.command = -1;
+          vt->parser.string_initial = TRUE;
+          ENTER_STATE(OSC_COMMAND);
           break;
         default:
           do_control(vt, c);
@@ -341,6 +336,9 @@ size_t vterm_input_write(VTerm *vt, cons
     }
   }
 
+  if(string_start)
+    string_fragment(vt, string_start, bytes + pos - string_start, FALSE);
+
   return len;
 }
 
--- a/src/libvterm/src/screen.c
+++ b/src/libvterm/src/screen.c
@@ -533,7 +533,7 @@ static void resize_buffer(VTermScreen *s
         ScreenCell *dst = &new_buffer[pos.row * new_cols + pos.col];
 	int i;
 
-        for(i = 0; ; i++) {
+        for(i = 0; i < VTERM_MAX_CHARS_PER_CELL; i++) {
           dst->chars[i] = src->chars[i];
           if(!src->chars[i])
             break;
@@ -804,7 +804,7 @@ int vterm_screen_get_cell(const VTermScr
   if(!intcell)
     return 0;
 
-  for(i = 0; ; i++) {
+  for(i = 0; i < VTERM_MAX_CHARS_PER_CELL; i++) {
     cell->chars[i] = intcell->chars[i];
     if(!intcell->chars[i])
       break;
--- a/src/libvterm/src/state.c
+++ b/src/libvterm/src/state.c
@@ -582,19 +582,12 @@ static int settermprop_int(VTermState *s
   return vterm_state_set_termprop(state, prop, &val);
 }
 
-static int settermprop_string(VTermState *state, VTermProp prop, const char *str, size_t len)
+static int settermprop_string(VTermState *state, VTermProp prop, VTermStringFragment frag)
 {
-  char *strvalue;
-  int r;
   VTermValue val;
-  strvalue = vterm_allocator_malloc(state->vt, (len+1) * sizeof(char));
-  strncpy(strvalue, str, len);
-  strvalue[len] = 0;
 
-  val.string = strvalue;
-  r = vterm_state_set_termprop(state, prop, &val);
-  vterm_allocator_free(state->vt, strvalue);
-  return r;
+  val.string = frag;
+  return vterm_state_set_termprop(state, prop, &val);
 }
 
 static void savecursor(VTermState *state, int save)
@@ -1602,100 +1595,121 @@ static int on_csi(const char *leader, co
   return 1;
 }
 
-static int on_osc(const char *command, size_t cmdlen, void *user)
+static int on_osc(int command, VTermStringFragment frag, void *user)
 {
   VTermState *state = user;
 
-  if(cmdlen < 2)
-    return 0;
+  switch(command) {
+    case 0:
+      settermprop_string(state, VTERM_PROP_ICONNAME, frag);
+      settermprop_string(state, VTERM_PROP_TITLE, frag);
+      return 1;
+
+    case 1:
+      settermprop_string(state, VTERM_PROP_ICONNAME, frag);
+      return 1;
+
+    case 2:
+      settermprop_string(state, VTERM_PROP_TITLE, frag);
+      return 1;
 
-  if(strneq(command, "0;", 2)) {
-    settermprop_string(state, VTERM_PROP_ICONNAME, command + 2, cmdlen - 2);
-    settermprop_string(state, VTERM_PROP_TITLE, command + 2, cmdlen - 2);
-    return 1;
-  }
-  else if(strneq(command, "1;", 2)) {
-    settermprop_string(state, VTERM_PROP_ICONNAME, command + 2, cmdlen - 2);
-    return 1;
-  }
-  else if(strneq(command, "2;", 2)) {
-    settermprop_string(state, VTERM_PROP_TITLE, command + 2, cmdlen - 2);
-    return 1;
+    case 10:
+      {
+        // request foreground color: <Esc>]10;?<0x07>
+        int red = state->default_fg.red;
+        int blue = state->default_fg.blue;
+        int green = state->default_fg.green;
+        vterm_push_output_sprintf_ctrl(state->vt, C1_OSC, "10;rgb:%02x%02x/%02x%02x/%02x%02x\x07", red, red, green, green, blue, blue);
+        return 1;
+      }
+
+    case 11:
+      {
+	// request background color: <Esc>]11;?<0x07>
+	int red = state->default_bg.red;
+	int blue = state->default_bg.blue;
+	int green = state->default_bg.green;
+	vterm_push_output_sprintf_ctrl(state->vt, C1_OSC, "11;rgb:%02x%02x/%02x%02x/%02x%02x\x07", red, red, green, green, blue, blue);
+	return 1;
+      }
+    case 12:
+      settermprop_string(state, VTERM_PROP_CURSORCOLOR, frag);
+      return 1;
+
+    default:
+      if(state->fallbacks && state->fallbacks->osc)
+        if((*state->fallbacks->osc)(command, frag, state->fbdata))
+          return 1;
   }
-  else if(strneq(command, "10;", 3)) {
-    // request foreground color: <Esc>]10;?<0x07>
-    int red = state->default_fg.red;
-    int blue = state->default_fg.blue;
-    int green = state->default_fg.green;
-    vterm_push_output_sprintf_ctrl(state->vt, C1_OSC, "10;rgb:%02x%02x/%02x%02x/%02x%02x\x07", red, red, green, green, blue, blue);
-    return 1;
-  }
-  else if(strneq(command, "11;", 3)) {
-    // request background color: <Esc>]11;?<0x07>
-    int red = state->default_bg.red;
-    int blue = state->default_bg.blue;
-    int green = state->default_bg.green;
-    vterm_push_output_sprintf_ctrl(state->vt, C1_OSC, "11;rgb:%02x%02x/%02x%02x/%02x%02x\x07", red, red, green, green, blue, blue);
-    return 1;
-  }
-  else if(strneq(command, "12;", 3)) {
-    settermprop_string(state, VTERM_PROP_CURSORCOLOR, command + 3, cmdlen - 3);
-    return 1;
-  }
-  else if(state->fallbacks && state->fallbacks->osc)
-    if((*state->fallbacks->osc)(command, cmdlen, state->fbdata))
-      return 1;
 
   return 0;
 }
 
-static void request_status_string(VTermState *state, const char *command, size_t cmdlen)
+static void request_status_string(VTermState *state, VTermStringFragment frag)
 {
   VTerm *vt = state->vt;
 
-  if(cmdlen == 1)
-    switch(command[0]) {
-      case 'm': // Query SGR
-        {
-          long args[20];
-          int argc = vterm_state_getpen(state, args, sizeof(args)/sizeof(args[0]));
-	  int argi;
-          size_t cur = 0;
+  char *tmp = state->tmp.decrqss;
+  size_t i = 0;
+
+  if(frag.initial)
+    tmp[0] = tmp[1] = tmp[2] = tmp[3] = 0;
 
-          cur += SNPRINTF(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur,
-              vt->mode.ctrl8bit ? "\x90" "1$r" : ESC_S "P" "1$r"); // DCS 1$r ...
-          if(cur >= vt->tmpbuffer_len)
-            return;
+  while(i < sizeof(state->tmp.decrqss)-1 && tmp[i])
+    i++;
+  while(i < sizeof(state->tmp.decrqss)-1 && frag.len--)
+    tmp[i++] = (frag.str++)[0];
+  tmp[i] = 0;
+
+  if(!frag.final)
+    return;
+
+  fprintf(stderr, "DECRQSS on <%s>\n", tmp);
 
-          for(argi = 0; argi < argc; argi++) {
-            cur += SNPRINTF(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur,
-                argi == argc - 1             ? "%ld" :
-                CSI_ARG_HAS_MORE(args[argi]) ? "%ld:" :
-                                               "%ld;",
-                 CSI_ARG(args[argi]));
+  switch(tmp[0] | tmp[1]<<8 | tmp[2]<<16) {
+    case 'm': {
+      // Query SGR
+      long args[20];
+      int argc = vterm_state_getpen(state, args, sizeof(args)/sizeof(args[0]));
+      size_t cur = 0;
+      int argi;
 
-            if(cur >= vt->tmpbuffer_len)
-              return;
-          }
+      cur += snprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur,
+          vt->mode.ctrl8bit ? "\x90" "1$r" : ESC_S "P" "1$r"); // DCS 1$r ...
+      if(cur >= vt->tmpbuffer_len)
+        return;
 
-          cur += SNPRINTF(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur,
-              vt->mode.ctrl8bit ? "m" "\x9C" : "m" ESC_S "\\"); // ... m ST
-          if(cur >= vt->tmpbuffer_len)
-            return;
+      for(argi = 0; argi < argc; argi++) {
+        cur += snprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur,
+            argi == argc - 1             ? "%ld" :
+            CSI_ARG_HAS_MORE(args[argi]) ? "%ld:" :
+                                           "%ld;",
+            CSI_ARG(args[argi]));
+        if(cur >= vt->tmpbuffer_len)
+          return;
+      }
 
-          vterm_push_output_bytes(vt, vt->tmpbuffer, cur);
-        }
+      cur += snprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur,
+          vt->mode.ctrl8bit ? "m" "\x9C" : "m" ESC_S "\\"); // ... m ST
+      if(cur >= vt->tmpbuffer_len)
         return;
-      case 'r': // Query DECSTBM
-        vterm_push_output_sprintf_dcs(vt, "1$r%d;%dr", state->scrollregion_top+1, SCROLLREGION_BOTTOM(state));
-        return;
-      case 's': // Query DECSLRM
-        vterm_push_output_sprintf_dcs(vt, "1$r%d;%ds", SCROLLREGION_LEFT(state)+1, SCROLLREGION_RIGHT(state));
-        return;
+
+      vterm_push_output_bytes(vt, vt->tmpbuffer, cur);
+      return;
     }
 
-  if(cmdlen == 2) {
-    if(strneq(command, " q", 2)) {
+    case 'r':
+      // Query DECSTBM
+      vterm_push_output_sprintf_dcs(vt, "1$r%d;%dr", state->scrollregion_top+1, SCROLLREGION_BOTTOM(state));
+      return;
+
+    case 's':
+      // Query DECSLRM
+      vterm_push_output_sprintf_dcs(vt, "1$r%d;%ds", SCROLLREGION_LEFT(state)+1, SCROLLREGION_RIGHT(state));
+      return;
+
+    case ' '|('q'<<8): {
+      // Query DECSCUSR
       int reply;
       switch(state->mode.cursor_shape) {
         case VTERM_PROP_CURSORSHAPE_BLOCK:     reply = 2; break;
@@ -1707,27 +1721,29 @@ static void request_status_string(VTermS
       vterm_push_output_sprintf_dcs(vt, "1$r%d q", reply);
       return;
     }
-    else if(strneq(command, "\"q", 2)) {
+
+    case '\"'|('q'<<8):
+      // Query DECSCA
       vterm_push_output_sprintf_dcs(vt, "1$r%d\"q", state->protected_cell ? 1 : 2);
       return;
-    }
   }
 
-  vterm_push_output_sprintf_dcs(state->vt, "0$r%.s", (int)cmdlen, command);
+  vterm_push_output_sprintf_dcs(state->vt, "0$r%s", tmp);
 }
 
-static int on_dcs(const char *command, size_t cmdlen, void *user)
+static int on_dcs(const char *command, size_t commandlen, VTermStringFragment frag, void *user)
 {
   VTermState *state = user;
 
-  if(cmdlen >= 2 && strneq(command, "$q", 2)) {
-    request_status_string(state, command+2, cmdlen-2);
+  if(commandlen == 2 && strneq(command, "$q", 2)) {
+    request_status_string(state, frag);
     return 1;
   }
   else if(state->fallbacks && state->fallbacks->dcs)
-    if((*state->fallbacks->dcs)(command, cmdlen, state->fbdata))
+    if((*state->fallbacks->dcs)(command, commandlen, frag, state->fbdata))
       return 1;
 
+  DEBUG_LOG2("libvterm: Unhandled DCS %.*s\n", (int)commandlen, command);
   return 0;
 }
 
--- a/src/libvterm/src/vterm.c
+++ b/src/libvterm/src/vterm.c
@@ -55,35 +55,23 @@ VTerm *vterm_new_with_allocator(int rows
   vt->parser.callbacks = NULL;
   vt->parser.cbdata    = NULL;
 
-  vt->parser.strbuffer_len = 500; // should be able to hold an OSC string
-  vt->parser.strbuffer_cur = 0;
-  vt->parser.strbuffer = vterm_allocator_malloc(vt, vt->parser.strbuffer_len);
-  if (vt->parser.strbuffer == NULL)
-  {
-    vterm_allocator_free(vt, vt);
-    return NULL;
-  }
-
   vt->outfunc = NULL;
   vt->outdata = NULL;
 
   vt->outbuffer_len = 200;
   vt->outbuffer_cur = 0;
   vt->outbuffer = vterm_allocator_malloc(vt, vt->outbuffer_len);
-  if (vt->outbuffer == NULL)
-  {
-    vterm_allocator_free(vt, vt->parser.strbuffer);
-    vterm_allocator_free(vt, vt);
-    return NULL;
-  }
 
   vt->tmpbuffer_len = 64;
   vt->tmpbuffer = vterm_allocator_malloc(vt, vt->tmpbuffer_len);
-  if (vt->tmpbuffer == NULL)
+
+  if (vt->tmpbuffer == NULL
+      || vt->outbuffer == NULL
+      || vt->tmpbuffer == NULL)
   {
-    vterm_allocator_free(vt, vt->parser.strbuffer);
+    vterm_allocator_free(vt, vt->outbuffer);
+    vterm_allocator_free(vt, vt->tmpbuffer);
     vterm_allocator_free(vt, vt);
-    vterm_allocator_free(vt, vt->outbuffer);
     return NULL;
   }
 
@@ -98,7 +86,6 @@ void vterm_free(VTerm *vt)
   if(vt->state)
     vterm_state_free(vt->state);
 
-  vterm_allocator_free(vt, vt->parser.strbuffer);
   vterm_allocator_free(vt, vt->outbuffer);
   vterm_allocator_free(vt, vt->tmpbuffer);
 
--- a/src/libvterm/src/vterm_internal.h
+++ b/src/libvterm/src/vterm_internal.h
@@ -160,15 +160,13 @@ struct VTermState
       unsigned int cursor_shape:2;
     } mode;
   } saved;
+
+  /* Temporary state for DECRQSS parsing */
+  union {
+    char decrqss[4];
+  } tmp;
 };
 
-typedef enum {
-  VTERM_PARSER_OSC,
-  VTERM_PARSER_DCS,
-
-  VTERM_N_PARSER_TYPES
-} VTermParserStringType;
-
 struct VTerm
 {
   VTermAllocatorFunctions *allocator;
@@ -188,28 +186,39 @@ struct VTerm
       CSI_LEADER,
       CSI_ARGS,
       CSI_INTERMED,
-      ESC,
+      OSC_COMMAND,
+      DCS_COMMAND,
       // below here are the "string states"
-      STRING,
-      ESC_IN_STRING,
+      OSC,
+      DCS,
     } state;
 
+    unsigned int in_esc : 1;
+
     int intermedlen;
     char intermed[INTERMED_MAX];
 
-    int csi_leaderlen;
-    char csi_leader[CSI_LEADER_MAX];
+    union {
+      struct {
+        int leaderlen;
+        char leader[CSI_LEADER_MAX];
 
-    int csi_argi;
-    long csi_args[CSI_ARGS_MAX];
+        int argi;
+        long args[CSI_ARGS_MAX];
+      } csi;
+      struct {
+        int command;
+      } osc;
+      struct {
+        int commandlen;
+        char command[CSI_LEADER_MAX];
+      } dcs;
+    } v;
 
     const VTermParserCallbacks *callbacks;
     void *cbdata;
 
-    VTermParserStringType stringtype;
-    char  *strbuffer;
-    size_t strbuffer_len;
-    size_t strbuffer_cur;
+    int string_initial;
   } parser;
 
   // len == malloc()ed size; cur == number of valid bytes
--- a/src/libvterm/t/02parser.test
+++ b/src/libvterm/t/02parser.test
@@ -132,15 +132,23 @@ PUSH "\e[12\n;3X"
 
 !OSC BEL
 PUSH "\e]1;Hello\x07"
-  osc "1;Hello"
+  osc [1 "Hello"]
 
 !OSC ST (7bit)
 PUSH "\e]1;Hello\e\\"
-  osc "1;Hello"
+  osc [1 "Hello"]
 
 !OSC ST (8bit)
 PUSH "\x{9d}1;Hello\x9c"
-  osc "1;Hello"
+  osc [1 "Hello"]
+
+!OSC in parts
+PUSH "\e]52;abc"
+  osc [52 "abc"
+PUSH "def"
+  osc "def"
+PUSH "ghi\e\\"
+  osc "ghi"]
 
 !Escape cancels OSC, starts Escape
 PUSH "\e]Something\e9"
@@ -152,20 +160,21 @@ PUSH "\e]12\x{18}AB"
 
 !C0 in OSC interrupts and continues
 PUSH "\e]2;\nBye\x07"
+  osc [2 ""
   control 10
-  osc "2;Bye"
+  osc "Bye"]
 
 !DCS BEL
 PUSH "\ePHello\x07"
-  dcs "Hello"
+  dcs ["Hello"]
 
 !DCS ST (7bit)
 PUSH "\ePHello\e\\"
-  dcs "Hello"
+  dcs ["Hello"]
 
 !DCS ST (8bit)
 PUSH "\x{90}Hello\x9c"
-  dcs "Hello"
+  dcs ["Hello"]
 
 !Escape cancels DCS, starts Escape
 PUSH "\ePSomething\e9"
@@ -177,8 +186,9 @@ PUSH "\eP12\x{18}AB"
 
 !C0 in OSC interrupts and continues
 PUSH "\ePBy\ne\x07"
+  dcs ["By"
   control 10
-  dcs "Bye"
+  dcs "e"]
 
 !NUL ignored
 PUSH "\x{00}"
--- a/src/libvterm/t/18state_termprops.test
+++ b/src/libvterm/t/18state_termprops.test
@@ -33,4 +33,10 @@ PUSH "\e[3 q"
 
 !Title
 PUSH "\e]2;Here is my title\a"
-  settermprop 4 "Here is my title"
+  settermprop 4 ["Here is my title"]
+
+!Title split write
+PUSH "\e]2;Here is"
+  settermprop 4 ["Here is"
+PUSH " another title\a"
+  settermprop 4 " another title"]
--- a/src/libvterm/t/29state_fallback.test
+++ b/src/libvterm/t/29state_fallback.test
@@ -12,8 +12,8 @@ PUSH "\e[?15;2z"
 
 !Unrecognised OSC
 PUSH "\e]27;Something\e\\"
-  osc "27;Something"
+  osc [27 "Something"]
 
 !Unrecognised DCS
 PUSH "\ePz123\e\\"
-  dcs "z123"
+  dcs ["z123"]
--- a/src/libvterm/t/68screen_termprops.test
+++ b/src/libvterm/t/68screen_termprops.test
@@ -14,4 +14,4 @@ PUSH "\e[?25l"
 
 !Title
 PUSH "\e]2;Here is my title\a"
-  settermprop 4 "Here is my title"
+  settermprop 4 ["Here is my title"]
--- a/src/libvterm/t/harness.c
+++ b/src/libvterm/t/harness.c
@@ -153,21 +153,44 @@ static int parser_csi(const char *leader
   return 1;
 }
 
-static int parser_osc(const char *command, size_t cmdlen, void *user UNUSED)
+static int parser_osc(int command, VTermStringFragment frag, void *user UNUSED)
 {
 
   printf("osc ");
-  printhex(command, cmdlen);
+
+  if(frag.initial) {
+    if(command == -1)
+      printf("[");
+    else
+      printf("[%d;", command);
+  }
+
+  printhex(frag.str, frag.len);
+
+  if(frag.final)
+    printf("]");
+
   printf("\n");
 
   return 1;
 }
 
-static int parser_dcs(const char *command, size_t cmdlen, void *user UNUSED)
+static int parser_dcs(const char *command, size_t commandlen, VTermStringFragment frag, void *user UNUSED)
 {
+  printf("dcs ");
 
-  printf("dcs ");
-  printhex(command, cmdlen);
+  if(frag.initial) {
+    size_t i;
+    printf("[");
+    for(i = 0; i < commandlen; i++)
+      printf("%02x", command[i]);
+  }
+
+  printhex(frag.str, frag.len);
+
+  if(frag.final)
+    printf("]");
+
   printf("\n");
 
   return 1;
@@ -239,7 +262,8 @@ static int settermprop(VTermProp prop, V
     printf("settermprop %d %d\n", prop, val->number);
     return 1;
   case VTERM_VALUETYPE_STRING:
-    printf("settermprop %d \"%s\"\n", prop, val->string);
+    printf("settermprop %d %s\"%.*s\"%s\n", prop,
+        val->string.initial ? "[" : "", val->string.len, val->string.str, val->string.final ? "]" : "");
     return 1;
   case VTERM_VALUETYPE_COLOR:
     printf("settermprop %d rgb(%d,%d,%d)\n", prop, val->color.red, val->color.green, val->color.blue);
@@ -262,7 +286,7 @@ static int state_putglyph(VTermGlyphInfo
     return 1;
 
   printf("putglyph ");
-  for(i = 0; info->chars[i]; i++)
+  for(i = 0; i < VTERM_MAX_CHARS_PER_CELL && info->chars[i]; i++)
     printf(i ? ",%x" : "%x", info->chars[i]);
   printf(" %d %d,%d", info->width, pos.row, pos.col);
   if(info->protected_cell)
--- a/src/libvterm/t/run-test.pl
+++ b/src/libvterm/t/run-test.pl
@@ -11,7 +11,8 @@ my $VALGRIND = 0;
 my $EXECUTABLE = "t/.libs/harness";
 GetOptions(
    'valgrind|v+' => \$VALGRIND,
-   'executable|e=s' => \$EXECUTABLE
+   'executable|e=s' => \$EXECUTABLE,
+   'fail-early|F' => \(my $FAIL_EARLY),
 ) or exit 1;
 
 my ( $hin, $hout, $hpid );
@@ -65,6 +66,7 @@ sub do_onetest
    }
 
    $exitcode = 1 if $fail_printed;
+   exit $exitcode if $exitcode and $FAIL_EARLY;
 }
 
 sub do_line
@@ -105,8 +107,15 @@ sub do_line
       elsif( $line =~ m/^csi (\S+) (.*)$/ ) {
          $line = sprintf "csi %02x %s", eval($1), $2; # TODO
       }
-      elsif( $line =~ m/^(escape|osc|dcs) (.*)$/ ) {
-         $line = "$1 " . join "", map sprintf("%02x", $_), unpack "C*", eval($2);
+      elsif( $line =~ m/^(osc) (\[\d+)? *(.*?)(\]?)$/ ) {
+         my ( $cmd, $initial, $data, $final ) = ( $1, $2, $3, $4 );
+         $initial //= "";
+         $initial .= ";" if $initial =~ m/\d+/;
+
+         $line = "$cmd $initial" . join( "", map sprintf("%02x", $_), unpack "C*", eval($data) ) . "$final";
+      }
+      elsif( $line =~ m/^(escape|dcs) (\[?)(.*?)(\]?)$/ ) {
+         $line = "$1 $2" . join( "", map sprintf("%02x", $_), unpack "C*", eval($3) ) . "$4";
       }
       elsif( $line =~ m/^putglyph (\S+) (.*)$/ ) {
          $line = "putglyph " . join( ",", map sprintf("%x", $_), eval($1) ) . " $2";
@@ -139,6 +148,7 @@ sub do_line
                "# Expected: $want\n" .
                "# Actual:   $response\n";
          $exitcode = 1;
+         exit $exitcode if $exitcode and $FAIL_EARLY;
       }
    }
    # Assertions start with '?'
@@ -162,6 +172,7 @@ sub do_line
                "# Expected: $expectation\n" .
                "# Actual:   $response\n";
          $exitcode = 1;
+         exit $exitcode if $exitcode and $FAIL_EARLY;
       }
    }
    # Test controls start with '$'
--- a/src/terminal.c
+++ b/src/terminal.c
@@ -2998,22 +2998,27 @@ handle_settermprop(
 	void *user)
 {
     term_T	*term = (term_T *)user;
+    char_u	*strval = NULL;
 
     switch (prop)
     {
 	case VTERM_PROP_TITLE:
+	    strval = vim_strnsave((char_u *)value->string.str,
+						       (int)value->string.len);
+	    if (strval == NULL)
+		break;
 	    vim_free(term->tl_title);
 	    // a blank title isn't useful, make it empty, so that "running" is
 	    // displayed
-	    if (*skipwhite((char_u *)value->string) == NUL)
+	    if (*skipwhite(strval) == NUL)
 		term->tl_title = NULL;
 	    // Same as blank
 	    else if (term->tl_arg0_cmd != NULL
-		    && STRNCMP(term->tl_arg0_cmd, (char_u *)value->string,
+		    && STRNCMP(term->tl_arg0_cmd, strval,
 					  (int)STRLEN(term->tl_arg0_cmd)) == 0)
 		term->tl_title = NULL;
 	    // Empty corrupted data of winpty
-	    else if (STRNCMP("  - ", (char_u *)value->string, 4) == 0)
+	    else if (STRNCMP("  - ", strval, 4) == 0)
 		term->tl_title = NULL;
 #ifdef MSWIN
 	    else if (!enc_utf8 && enc_codepage > 0)
@@ -3022,8 +3027,8 @@ handle_settermprop(
 		int	length = 0;
 
 		MultiByteToWideChar_alloc(CP_UTF8, 0,
-			(char*)value->string, (int)STRLEN(value->string),
-								&ret, &length);
+			(char*)value->string.str,
+					(int)value->string.len, &ret, &length);
 		if (ret != NULL)
 		{
 		    WideCharToMultiByte_alloc(enc_codepage, 0,
@@ -3034,7 +3039,10 @@ handle_settermprop(
 	    }
 #endif
 	    else
-		term->tl_title = vim_strsave((char_u *)value->string);
+	    {
+		term->tl_title = vim_strsave(strval);
+		strval = NULL;
+	    }
 	    VIM_CLEAR(term->tl_status_text);
 	    if (term == curbuf->b_term)
 		maketitle();
@@ -3057,7 +3065,11 @@ handle_settermprop(
 	    break;
 
 	case VTERM_PROP_CURSORCOLOR:
-	    cursor_color_copy(&term->tl_cursor_color, (char_u*)value->string);
+	    strval = vim_strnsave((char_u *)value->string.str,
+						       (int)value->string.len);
+	    if (strval == NULL)
+		break;
+	    cursor_color_copy(&term->tl_cursor_color, strval);
 	    may_set_cursor_props(term);
 	    break;
 
@@ -3069,6 +3081,8 @@ handle_settermprop(
 	default:
 	    break;
     }
+    vim_free(strval);
+
     // Always return 1, otherwise vterm doesn't store the value internally.
     return 1;
 }
@@ -4181,7 +4195,7 @@ handle_call_command(term_T *term, channe
  * We recognize a terminal API command.
  */
     static int
-parse_osc(const char *command, size_t cmdlen, void *user)
+parse_osc(int command, VTermStringFragment frag, void *user)
 {
     term_T	*term = (term_T *)user;
     js_read_T	reader;
@@ -4190,10 +4204,10 @@ parse_osc(const char *command, size_t cm
 						    : term->tl_job->jv_channel;
 
     // We recognize only OSC 5 1 ; {command}
-    if (cmdlen < 3 || STRNCMP(command, "51;", 3) != 0)
-	return 0; // not handled
-
-    reader.js_buf = vim_strnsave((char_u *)command + 3, (int)(cmdlen - 3));
+    if (command != 51)
+	return 0;
+
+    reader.js_buf = vim_strnsave((char_u *)frag.str, (int)(frag.len));
     if (reader.js_buf == NULL)
 	return 1;
     reader.js_fill = NULL;
--- a/src/version.c
+++ b/src/version.c
@@ -747,6 +747,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    798,
+/**/
     797,
 /**/
     796,