Snap to current upstream libvterm.

Has updated scrollback API and bugfixes.

Change-Id: I92c097393d57f3ae04dfddb745c618c145e8ff1a
diff --git a/include/vterm.h b/include/vterm.h
index 68e4feb..2e2b32b 100644
--- a/include/vterm.h
+++ b/include/vterm.h
@@ -48,37 +48,6 @@
   rect->start_col += col_delta; rect->end_col += col_delta;
 }
 
-/* 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 (1<<31)
-#define CSI_ARG_MASK      (~(1<<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)(const char *command, size_t cmdlen, void *user);
-  int (*dcs)(const char *command, size_t cmdlen, void *user);
-  int (*resize)(int rows, int cols, void *user);
-} VTermParserCallbacks;
-
 typedef struct {
   uint8_t red, green, blue;
 } VTermColor;
@@ -133,35 +102,10 @@
 typedef struct {
   const uint32_t *chars;
   int             width;
-  int             protected_cell;  /* DECSCA-protected against DECSEL/DECSED */
+  unsigned int    protected_cell:1;  /* DECSCA-protected against DECSEL/DECSED */
 } VTermGlyphInfo;
 
 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 (*setmousefunc)(VTermMouseFunc func, void *data, void *user);
-  int (*bell)(void *user);
-  int (*resize)(int rows, int cols, void *user);
-} VTermStateCallbacks;
-
-typedef struct {
-  int (*damage)(VTermRect rect, void *user);
-  int (*prescroll)(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 (*setmousefunc)(VTermMouseFunc func, void *data, void *user);
-  int (*bell)(void *user);
-  int (*resize)(int rows, int cols, void *user);
-} VTermScreenCallbacks;
-
-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);
@@ -175,8 +119,76 @@
 void vterm_get_size(const VTerm *vt, int *rowsp, int *colsp);
 void vterm_set_size(VTerm *vt, int rows, int cols);
 
+void vterm_push_bytes(VTerm *vt, const char *bytes, size_t len);
+
+void vterm_input_push_char(VTerm *vt, VTermModifier state, uint32_t c);
+void vterm_input_push_key(VTerm *vt, VTermModifier state, VTermKey key);
+
+size_t vterm_output_bufferlen(VTerm *vt); /* deprecated */
+
+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);
+
+size_t vterm_output_bufferread(VTerm *vt, char *buffer, size_t len);
+
+// ------------
+// 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 (1<<31)
+#define CSI_ARG_MASK      (~(1<<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)(const char *command, size_t cmdlen, void *user);
+  int (*dcs)(const char *command, size_t cmdlen, void *user);
+  int (*resize)(int rows, int cols, void *user);
+} VTermParserCallbacks;
+
 void vterm_set_parser_callbacks(VTerm *vt, const VTermParserCallbacks *callbacks, void *user);
 
+void vterm_parser_set_utf8(VTerm *vt, int is_utf8);
+
+// -----------
+// 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 (*setmousefunc)(VTermMouseFunc func, void *data, void *user);
+  int (*bell)(void *user);
+  int (*resize)(int rows, int cols, VTermPos *delta, void *user);
+} VTermStateCallbacks;
+
 VTermState *vterm_obtain_state(VTerm *vt);
 
 void vterm_state_reset(VTermState *state, int hard);
@@ -189,8 +201,37 @@
 int  vterm_state_get_penattr(const VTermState *state, VTermAttr attr, VTermValue *val);
 int  vterm_state_set_termprop(VTermState *state, VTermProp prop, VTermValue *val);
 
-VTermValueType vterm_get_attr_type(VTermAttr attr);
-VTermValueType vterm_get_prop_type(VTermProp prop);
+// ------------
+// Screen layer
+// ------------
+
+typedef struct {
+#define VTERM_MAX_CHARS_PER_CELL 6
+  uint32_t chars[VTERM_MAX_CHARS_PER_CELL];
+  char     width;
+  struct {
+    unsigned int bold      : 1;
+    unsigned int underline : 2;
+    unsigned int italic    : 1;
+    unsigned int blink     : 1;
+    unsigned int reverse   : 1;
+    unsigned int strike    : 1;
+    unsigned int font      : 4; /* 0 to 9 */
+  } 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 (*setmousefunc)(VTermMouseFunc func, void *data, 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);
+} VTermScreenCallbacks;
 
 VTermScreen *vterm_obtain_screen(VTerm *vt);
 
@@ -211,39 +252,30 @@
 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 struct {
-#define VTERM_MAX_CHARS_PER_CELL 6
-  uint32_t chars[VTERM_MAX_CHARS_PER_CELL];
-  char     width;
-  struct {
-    unsigned int bold      : 1;
-    unsigned int underline : 2;
-    unsigned int italic    : 1;
-    unsigned int blink     : 1;
-    unsigned int reverse   : 1;
-    unsigned int strike    : 1;
-    unsigned int font      : 4; /* 0 to 9 */
-  } attrs;
-  VTermColor fg, bg;
-} VTermScreenCell;
+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,
+} 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);
 
-void vterm_input_push_char(VTerm *vt, VTermModifier state, uint32_t c);
-void vterm_input_push_key(VTerm *vt, VTermModifier state, VTermKey key);
+// ---------
+// Utilities
+// ---------
 
-void vterm_parser_set_utf8(VTerm *vt, int is_utf8);
-void vterm_push_bytes(VTerm *vt, const char *bytes, size_t len);
-
-size_t vterm_output_bufferlen(VTerm *vt); /* deprecated */
-
-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);
-
-size_t vterm_output_bufferread(VTerm *vt, char *buffer, size_t len);
+VTermValueType vterm_get_attr_type(VTermAttr attr);
+VTermValueType vterm_get_prop_type(VTermProp prop);
 
 void vterm_scroll_rect(VTermRect rect,
                        int downward,
diff --git a/src/encoding.c b/src/encoding.c
index 130cfff..a7629f9 100644
--- a/src/encoding.c
+++ b/src/encoding.c
@@ -209,7 +209,7 @@
   { ENC_SINGLE_94, '0', (VTermEncoding*)&encoding_DECdrawing },
   { ENC_SINGLE_94, 'A', (VTermEncoding*)&encoding_uk },
   { ENC_SINGLE_94, 'B', &encoding_usascii },
-  { 0, 0 },
+  { 0 },
 };
 
 VTermEncoding *vterm_lookup_encoding(VTermEncodingType type, char designation)
diff --git a/src/pen.c b/src/pen.c
index eed63c0..044e9aa 100644
--- a/src/pen.c
+++ b/src/pen.c
@@ -64,7 +64,7 @@
   }
 }
 
-static int lookup_colour(int palette, const long args[], int argcount, VTermColor *col)
+static int lookup_colour(int palette, const long args[], int argcount, VTermColor *col, int *index)
 {
   switch(palette) {
   case 2: // RGB mode - 3 args contain colour values directly
@@ -78,6 +78,9 @@
     return 3;
 
   case 5: // XTerm 256-colour mode
+    if(index)
+      *index = CSI_ARG_OR(args[0], -1);
+
     lookup_colour_palette(argcount ? CSI_ARG_OR(args[0], -1) : -1, col);
 
     return argcount ? 1 : 0;
@@ -140,7 +143,8 @@
   state->pen.strike = 0;    setpenattr_bool(state, VTERM_ATTR_STRIKE, 0);
   state->pen.font = 0;      setpenattr_int( state, VTERM_ATTR_FONT, 0);
 
-  state->fg_ansi = -1;
+  state->fg_index = -1;
+  state->bg_index = -1;
   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);
 }
@@ -209,8 +213,8 @@
     case 1: // Bold on
       state->pen.bold = 1;
       setpenattr_bool(state, VTERM_ATTR_BOLD, 1);
-      if(state->fg_ansi > -1 && state->bold_is_highbright)
-        set_pen_col_ansi(state, VTERM_ATTR_FOREGROUND, state->fg_ansi + (state->pen.bold ? 8 : 0));
+      if(state->fg_index > -1 && state->fg_index < 8 && state->bold_is_highbright)
+        set_pen_col_ansi(state, VTERM_ATTR_FOREGROUND, state->fg_index + (state->pen.bold ? 8 : 0));
       break;
 
     case 3: // Italic on
@@ -282,52 +286,59 @@
     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;
-      state->fg_ansi = value;
+      state->fg_index = value;
       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
-      state->fg_ansi = -1;
+      state->fg_index = -1;
       if(argcount - argi < 1)
         return;
-      argi += 1 + lookup_colour(CSI_ARG(args[argi+1]), args+argi+2, argcount-argi-2, &state->pen.fg);
+      argi += 1 + lookup_colour(CSI_ARG(args[argi+1]), args+argi+2, argcount-argi-2, &state->pen.fg, &state->fg_index);
       setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->pen.fg);
       break;
 
     case 39: // Foreground colour default
-      state->fg_ansi = -1;
+      state->fg_index = -1;
       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
-      set_pen_col_ansi(state, VTERM_ATTR_BACKGROUND, CSI_ARG(args[argi]) - 40);
+      value = CSI_ARG(args[argi]) - 40;
+      state->bg_index = value;
+      set_pen_col_ansi(state, VTERM_ATTR_BACKGROUND, value);
       break;
 
     case 48: // Background colour alternative palette
+      state->bg_index = -1;
       if(argcount - argi < 1)
         return;
-      argi += 1 + lookup_colour(CSI_ARG(args[argi+1]), args+argi+2, argcount-argi-2, &state->pen.bg);
+      argi += 1 + lookup_colour(CSI_ARG(args[argi+1]), args+argi+2, argcount-argi-2, &state->pen.bg, &state->bg_index);
       setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->pen.bg);
       break;
 
     case 49: // Default background
+      state->bg_index = -1;
       state->pen.bg = state->default_bg;
       setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->pen.bg);
       break;
 
     case 90: case 91: case 92: case 93:
     case 94: case 95: case 96: case 97: // Foreground colour high-intensity palette
-      state->fg_ansi = -1;
-      set_pen_col_ansi(state, VTERM_ATTR_FOREGROUND, CSI_ARG(args[argi]) - 90 + 8);
+      value = CSI_ARG(args[argi]) - 90 + 8;
+      state->fg_index = value;
+      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
-      set_pen_col_ansi(state, VTERM_ATTR_BACKGROUND, CSI_ARG(args[argi]) - 100 + 8);
+      value = CSI_ARG(args[argi]) - 100 + 8;
+      state->bg_index = value;
+      set_pen_col_ansi(state, VTERM_ATTR_BACKGROUND, value);
       break;
 
     default:
@@ -342,6 +353,57 @@
   }
 }
 
+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 == 1)
+    args[argi++] = 4;
+
+  if(state->pen.blink)
+    args[argi++] = 5;
+
+  if(state->pen.reverse)
+    args[argi++] = 7;
+
+  if(state->pen.strike)
+    args[argi++] = 9;
+
+  if(state->pen.font)
+    args[argi++] = 10 + state->pen.font;
+
+  if(state->pen.underline == 2)
+    args[argi++] = 21;
+
+  if(state->fg_index >= 0 && state->fg_index < 8)
+    args[argi++] = 30 + state->fg_index;
+  else if(state->fg_index >= 8 && state->fg_index < 16)
+    args[argi++] = 90 + state->fg_index - 8;
+  else if(state->fg_index >= 16 && state->fg_index < 256) {
+    args[argi++] = CSI_ARG_FLAG_MORE|38;
+    args[argi++] = CSI_ARG_FLAG_MORE|5;
+    args[argi++] = state->fg_index;
+  }
+
+  if(state->bg_index >= 0 && state->bg_index < 8)
+    args[argi++] = 40 + state->bg_index;
+  else if(state->bg_index >= 8 && state->bg_index < 16)
+    args[argi++] = 100 + state->bg_index - 8;
+  else if(state->bg_index >= 16 && state->bg_index < 256) {
+    args[argi++] = CSI_ARG_FLAG_MORE|48;
+    args[argi++] = CSI_ARG_FLAG_MORE|5;
+    args[argi++] = state->bg_index;
+  }
+
+  return argi;
+}
+
 int vterm_state_get_penattr(const VTermState *state, VTermAttr attr, VTermValue *val)
 {
   switch(attr) {
diff --git a/src/screen.c b/src/screen.c
index 64bedf2..db11c6d 100644
--- a/src/screen.c
+++ b/src/screen.c
@@ -34,6 +34,8 @@
   ScreenPen pen;
 } ScreenCell;
 
+static int vterm_screen_set_cell(VTermScreen *screen, VTermPos pos, const VTermScreenCell *cell);
+
 struct VTermScreen
 {
   VTerm *vt;
@@ -58,6 +60,9 @@
   /* 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;
 };
 
@@ -199,45 +204,16 @@
 {
   VTermScreen *screen = user;
 
-  if(screen->callbacks && screen->callbacks->prescroll) {
-    // TODO: These calculations don't properly take account of combined
-    // horizontal and vertical movements
-    if(dest.start_row < src.start_row) {
-      VTermRect rect = {
-        .start_row = dest.start_row,
-        .end_row   = src.start_row,
-        .start_col = dest.start_col,
-        .end_col   = dest.end_col,
-      };
-      (*screen->callbacks->prescroll)(rect, screen->cbdata);
-    }
-    else if(dest.start_row > src.start_row) {
-      VTermRect rect = {
-        .start_row = src.end_row,
-        .end_row   = dest.end_row,
-        .start_col = dest.start_col,
-        .end_col   = dest.end_col,
-      };
-      (*screen->callbacks->prescroll)(rect, screen->cbdata);
-    }
+  if(screen->callbacks && screen->callbacks->sb_pushline &&
+     dest.start_row == 0 && dest.start_col == 0 &&  // starts top-left corner
+     dest.end_col == screen->cols &&                // full width
+     screen->buffer == screen->buffers[0]) {        // not altscreen
+    VTermPos pos;
+    for(pos.row = 0; pos.row < src.start_row; pos.row++) {
+      for(pos.col = 0; pos.col < screen->cols; pos.col++)
+        vterm_screen_get_cell(screen, pos, screen->sb_buffer + pos.col);
 
-    if(dest.start_col < src.start_col) {
-      VTermRect rect = {
-        .start_row = dest.start_row,
-        .end_row   = dest.end_row,
-        .start_col = dest.start_col,
-        .end_col   = src.start_col,
-      };
-      (*screen->callbacks->prescroll)(rect, screen->cbdata);
-    }
-    else if(dest.start_col > src.start_col) {
-      VTermRect rect = {
-        .start_row = dest.start_row,
-        .end_row   = dest.end_row,
-        .start_col = src.end_col,
-        .end_col   = dest.end_col,
-      };
-      (*screen->callbacks->prescroll)(rect, screen->cbdata);
+      (screen->callbacks->sb_pushline)(screen->cols, screen->sb_buffer, screen->cbdata);
     }
   }
 
@@ -492,24 +468,52 @@
   return 0;
 }
 
-static int resize(int new_rows, int new_cols, void *user)
+static int resize(int new_rows, int new_cols, VTermPos *delta, void *user)
 {
   VTermScreen *screen = user;
 
   int is_altscreen = (screen->buffers[1] && screen->buffer == screen->buffers[1]);
 
+  int old_rows = screen->rows;
+  int old_cols = screen->cols;
+
+  if(!is_altscreen && new_rows < old_rows) {
+    // Fewer rows - determine if we're going to scroll at all, and if so, push
+    // those lines to scrollback
+    VTermPos pos = { 0, 0 };
+    for(pos.row = old_rows - 1; pos.row >= new_rows; pos.row--)
+      if(!vterm_screen_is_eol(screen, pos))
+        break;
+
+    int first_blank_row = pos.row + 1;
+    if(first_blank_row > new_rows) {
+      VTermRect rect = {
+        .start_row = 0,
+        .end_row   = old_rows,
+        .start_col = 0,
+        .end_col   = old_cols,
+      };
+      scrollrect(rect, first_blank_row - new_rows, 0, user);
+      vterm_screen_flush_damage(screen);
+
+      delta->row -= first_blank_row - new_rows;
+    }
+  }
+
   screen->buffers[0] = realloc_buffer(screen, screen->buffers[0], new_rows, new_cols);
   if(screen->buffers[1])
     screen->buffers[1] = realloc_buffer(screen, screen->buffers[1], new_rows, new_cols);
 
   screen->buffer = is_altscreen ? screen->buffers[1] : screen->buffers[0];
 
-  int old_rows = screen->rows;
-  int old_cols = screen->cols;
-
   screen->rows = new_rows;
   screen->cols = new_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);
+
   if(new_cols > old_cols) {
     VTermRect rect = {
       .start_row = 0,
@@ -521,6 +525,34 @@
   }
 
   if(new_rows > old_rows) {
+    if(!is_altscreen && screen->callbacks && screen->callbacks->sb_popline) {
+      int rows = new_rows - old_rows;
+      while(rows) {
+        if(!(screen->callbacks->sb_popline(screen->cols, screen->sb_buffer, screen->cbdata)))
+          break;
+
+        VTermRect rect = {
+          .start_row = 0,
+          .end_row   = screen->rows,
+          .start_col = 0,
+          .end_col   = screen->cols,
+        };
+        scrollrect(rect, -1, 0, user);
+
+        VTermPos pos = { 0, 0 };
+        for(pos.col = 0; pos.col < screen->cols; pos.col += screen->sb_buffer[pos.col].width)
+          vterm_screen_set_cell(screen, pos, screen->sb_buffer + pos.col);
+
+        rect.end_row = 1;
+        damagerect(screen, rect);
+
+        vterm_screen_flush_damage(screen);
+
+        rows--;
+        delta->row++;
+      }
+    }
+
     VTermRect rect = {
       .start_row = old_rows,
       .end_row   = new_rows,
@@ -573,6 +605,8 @@
 
   screen->buffer = screen->buffers[0];
 
+  screen->sb_buffer = vterm_allocator_malloc(screen->vt, sizeof(VTermScreenCell) * cols);
+
   vterm_state_set_callbacks(screen->state, &state_cbs, screen);
 
   return screen;
@@ -584,6 +618,8 @@
   if(screen->buffers[1])
     vterm_allocator_free(screen->vt, screen->buffers[1]);
 
+  vterm_allocator_free(screen->vt, screen->sb_buffer);
+
   vterm_allocator_free(screen->vt, screen);
 }
 
@@ -688,6 +724,37 @@
   return 1;
 }
 
+/* Copy external to internal representation of a screen cell */
+/* static because it's only used internally for sb_popline during resize */
+static int vterm_screen_set_cell(VTermScreen *screen, VTermPos pos, const VTermScreenCell *cell)
+{
+  ScreenCell *intcell = getcell(screen, pos.row, pos.col);
+  if(!intcell)
+    return 0;
+
+  for(int i = 0; ; i++) {
+    intcell->chars[i] = cell->chars[i];
+    if(!cell->chars[i])
+      break;
+  }
+
+  intcell->pen.bold      = cell->attrs.bold;
+  intcell->pen.underline = cell->attrs.underline;
+  intcell->pen.italic    = cell->attrs.italic;
+  intcell->pen.blink     = cell->attrs.blink;
+  intcell->pen.reverse   = cell->attrs.reverse ^ screen->global_reverse;
+  intcell->pen.strike    = cell->attrs.strike;
+  intcell->pen.font      = cell->attrs.font;
+
+  intcell->pen.fg = cell->fg;
+  intcell->pen.bg = cell->bg;
+
+  if(cell->width == 2)
+    getcell(screen, pos.row, pos.col + 1)->chars[0] = (uint32_t)-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 */
@@ -750,3 +817,55 @@
   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_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_equal(a->pen.fg, b->pen.fg))
+    return 1;
+  if((attrs & VTERM_ATTR_BACKGROUND_MASK) && !vterm_color_equal(a->pen.bg, b->pen.bg))
+    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;
+}
diff --git a/src/state.c b/src/state.c
index 26d5262..f5348c9 100644
--- a/src/state.c
+++ b/src/state.c
@@ -72,6 +72,7 @@
 
 void vterm_state_free(VTermState *state)
 {
+  vterm_allocator_free(state->vt, state->tabstops);
   vterm_allocator_free(state->vt, state->combine_chars);
   vterm_allocator_free(state->vt, state);
 }
@@ -242,7 +243,7 @@
     printf("}, onscreen width %d\n", width);
 #endif
 
-    if(state->at_phantom) {
+    if(state->at_phantom || state->pos.col + width > state->cols) {
       linefeed(state);
       state->pos.col = 0;
       state->at_phantom = 0;
@@ -1346,11 +1347,26 @@
 {
   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]));
+          vterm_push_output_sprintf_ctrl(state->vt, C1_DCS, "1$r");
+          for(int argi = 0; argi < argc; argi++)
+            vterm_push_output_sprintf(state->vt,
+                argi == argc - 1             ? "%d" :
+                CSI_ARG_HAS_MORE(args[argi]) ? "%d:" :
+                                               "%d;",
+                CSI_ARG(args[argi]));
+          vterm_push_output_sprintf(state->vt, "m");
+          vterm_push_output_sprintf_ctrl(state->vt, C1_ST, "");
+        }
+        return;
       case 'r': // Query DECSTBM
-        vterm_push_output_sprintf_ctrl(state->vt, C1_DCS, "1$r%d;%dr", state->scrollregion_top+1, SCROLLREGION_BOTTOM(state));
+        vterm_push_output_sprintf_dcs(state->vt, "1$r%d;%dr", state->scrollregion_top+1, SCROLLREGION_BOTTOM(state));
         return;
       case 's': // Query DECSLRM
-        vterm_push_output_sprintf_ctrl(state->vt, C1_DCS, "1$r%d;%ds", SCROLLREGION_LEFT(state)+1, SCROLLREGION_RIGHT(state));
+        vterm_push_output_sprintf_dcs(state->vt, "1$r%d;%ds", SCROLLREGION_LEFT(state)+1, SCROLLREGION_RIGHT(state));
         return;
     }
 
@@ -1364,16 +1380,16 @@
       }
       if(state->mode.cursor_blink)
         reply--;
-      vterm_push_output_sprintf_ctrl(state->vt, C1_DCS, "1$r%d q", reply);
+      vterm_push_output_sprintf_dcs(state->vt, "1$r%d q", reply);
       return;
     }
     else if(strneq(command, "\"q", 2)) {
-      vterm_push_output_sprintf_ctrl(state->vt, C1_DCS, "1$r%d\"q", state->protected_cell ? 1 : 2);
+      vterm_push_output_sprintf_dcs(state->vt, "1$r%d\"q", state->protected_cell ? 1 : 2);
       return;
     }
   }
 
-  vterm_push_output_sprintf_ctrl(state->vt, C1_DCS, "0$r%.s", (int)cmdlen, command);
+  vterm_push_output_sprintf_dcs(state->vt, "0$r%.s", (int)cmdlen, command);
 }
 
 static int on_dcs(const char *command, size_t cmdlen, void *user)
@@ -1421,18 +1437,23 @@
   state->rows = rows;
   state->cols = cols;
 
-  if(state->pos.row >= rows)
-    state->pos.row = rows - 1;
-  if(state->pos.col >= cols)
-    state->pos.col = cols - 1;
+  VTermPos delta = { 0, 0 };
+
+  if(state->callbacks && state->callbacks->resize)
+    (*state->callbacks->resize)(rows, cols, &delta, state->cbdata);
 
   if(state->at_phantom && state->pos.col < cols-1) {
     state->at_phantom = 0;
     state->pos.col++;
   }
 
-  if(state->callbacks && state->callbacks->resize)
-    (*state->callbacks->resize)(rows, cols, state->cbdata);
+  state->pos.row += delta.row;
+  state->pos.col += delta.col;
+
+  if(state->pos.row >= rows)
+    state->pos.row = rows - 1;
+  if(state->pos.col >= cols)
+    state->pos.col = cols - 1;
 
   updatecursor(state, &oldpos, 1);
 
diff --git a/src/vterm.c b/src/vterm.c
index b8d7bd1..940d1fb 100644
--- a/src/vterm.c
+++ b/src/vterm.c
@@ -146,9 +146,21 @@
   va_start(args, fmt);
   vterm_push_output_vsprintf(vt, fmt, args);
   va_end(args);
+}
 
-  if(ctrl == C1_DCS)
-    vterm_push_output_sprintf_ctrl(vt, C1_ST, "");
+void vterm_push_output_sprintf_dcs(VTerm *vt, const char *fmt, ...)
+{
+  if(!vt->mode.ctrl8bit)
+    vterm_push_output_sprintf(vt, "\e%c", C1_DCS - 0x40);
+  else
+    vterm_push_output_sprintf(vt, "%c", C1_DCS);
+
+  va_list args;
+  va_start(args, fmt);
+  vterm_push_output_vsprintf(vt, fmt, args);
+  va_end(args);
+
+  vterm_push_output_sprintf_ctrl(vt, C1_ST, "");
 }
 
 size_t vterm_output_bufferlen(VTerm *vt)
diff --git a/src/vterm_internal.h b/src/vterm_internal.h
index 2139d0a..9a0183e 100644
--- a/src/vterm_internal.h
+++ b/src/vterm_internal.h
@@ -27,6 +27,11 @@
   unsigned int font:4; /* To store 0-9 */
 };
 
+static inline int vterm_color_equal(VTermColor a, VTermColor b)
+{
+  return a.red == b.red && a.green == b.green && a.blue == b.blue;
+}
+
 struct VTermState
 {
   VTerm *vt;
@@ -87,10 +92,11 @@
 
   VTermColor default_fg;
   VTermColor default_bg;
-  int fg_ansi;
+  int fg_index;
+  int bg_index;
   int bold_is_highbright;
 
-  int protected_cell;
+  unsigned int protected_cell : 1;
 
   /* Saved state under DEC mode 1048/1049 */
   struct {
@@ -162,11 +168,13 @@
 void vterm_push_output_vsprintf(VTerm *vt, const char *format, va_list args);
 void vterm_push_output_sprintf(VTerm *vt, const char *format, ...);
 void vterm_push_output_sprintf_ctrl(VTerm *vt, unsigned char ctrl, const char *fmt, ...);
+void vterm_push_output_sprintf_dcs(VTerm *vt, const char *fmt, ...);
 
 void vterm_state_free(VTermState *state);
 
 void vterm_state_resetpen(VTermState *state);
 void vterm_state_setpen(VTermState *state, const long args[], int argcount);
+int  vterm_state_getpen(VTermState *state, long args[], int argcount);
 void vterm_state_savepen(VTermState *state, int save);
 
 enum {