changeset 32728:b13f723a7ec6 v9.0.1684

patch 9.0.1684: Update libvterm to rev 839 Commit: https://github.com/vim/vim/commit/b00df7aa388994119346a21d77b0d0db2a0a5e9f Author: zeertzjq <zeertzjq@outlook.com> Date: Tue Aug 8 11:03:00 2023 +0800 patch 9.0.1684: Update libvterm to rev 839 Problem: libvterm slightly outdated Solution: Update libvterm from rev 818 to rev 839 Notable fix: libvterm now handles DECSM/DECRM with multiple arguents, so several ncurses programs (e.g. nnn) can enable mouse properly when run in Vim's terminal in XTerm. closes: #12746 Signed-off-by: Christian Brabandt <cb@256bit.org> Co-authored-by: zeertzjq <zeertzjq@outlook.com>
author Christian Brabandt <cb@256bit.org>
date Fri, 11 Aug 2023 21:30:03 +0200
parents 6beeb2e0105d
children aaacab93d76f
files src/libvterm/CODE-MAP src/libvterm/Makefile src/libvterm/include/vterm.h 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/17state_mouse.test src/libvterm/t/25state_input.test src/libvterm/t/30state_pen.test src/libvterm/t/40state_selection.test src/libvterm/t/64screen_pen.test src/libvterm/t/69screen_reflow.test src/libvterm/t/harness.c src/version.c
diffstat 17 files changed, 312 insertions(+), 37 deletions(-) [+]
line wrap: on
line diff
new file mode 100644
--- /dev/null
+++ b/src/libvterm/CODE-MAP
@@ -0,0 +1,66 @@
+CODE-MAP
+ - high-level list and description of files in the repository
+
+CONTRIBUTING
+ - documentation explaining how developers can contribute fixes and features
+
+doc/
+ - contains documentation
+
+doc/seqs.txt
+ - documents the sequences recognised by the library
+
+include/vterm.h
+ - main include file
+
+include/vterm_keycodes.h
+ - include file containing the keyboard input keycode enumerations
+
+LICENSE
+ - legalese
+
+Makefile
+ - main build file
+
+src/
+ - contains the source code for the library
+
+src/encoding.c
+ - handles mapping ISO/IEC 2022 alternate character sets into Unicode
+   codepoints
+
+src/keyboard.c
+ - handles sending reported keyboard events to the output stream
+
+src/mouse.c
+ - handles sending reported mouse events to the output stream
+
+src/parser.c
+ - parses bytes from the input stream into parser-level events
+
+src/pen.c
+ - interprets SGR sequences and maintains current rendering attributes
+
+src/screen.c
+ - uses state-level events to maintain a buffer of current screen contents
+
+src/state.c
+ - follows parser-level events to keep track of the overall terminal state
+
+src/unicode.c
+ - utility functions for Unicode and UTF-8 handling
+
+src/vterm.c
+ - toplevel object state and miscellaneous functions
+
+src/vterm_internal.h
+ - include file for definitions private to the library's internals
+
+t/
+ - contains unit tests
+
+t/harness.c
+ - standalone program to embed the library into for unit-test purposes
+
+t/run-test.pl
+ - invokes the test harness to run a single unit test script
--- a/src/libvterm/Makefile
+++ b/src/libvterm/Makefile
@@ -36,14 +36,11 @@ INCFILES=$(TBLFILES:.tbl=.inc)
 
 HFILES_INT=$(sort $(wildcard src/*.h)) $(HFILES)
 
-VERSION_MAJOR=0
-VERSION_MINOR=3
-
 VERSION_CURRENT=0
 VERSION_REVISION=0
 VERSION_AGE=0
 
-VERSION=$(VERSION_MAJOR).$(VERSION_MINOR)
+VERSION=0.3.3
 
 PREFIX=/usr/local
 BINDIR=$(PREFIX)/bin
--- a/src/libvterm/include/vterm.h
+++ b/src/libvterm/include/vterm.h
@@ -23,6 +23,7 @@ typedef unsigned int		uint32_t;
 
 #define VTERM_VERSION_MAJOR 0
 #define VTERM_VERSION_MINOR 3
+#define VTERM_VERSION_PATCH 3
 
 #define VTERM_CHECK_VERSION \
         vterm_check_version(VTERM_VERSION_MAJOR, VTERM_VERSION_MINOR)
@@ -255,6 +256,7 @@ typedef enum {
   VTERM_PROP_REVERSE,           // bool
   VTERM_PROP_CURSORSHAPE,       // number
   VTERM_PROP_MOUSE,             // number
+  VTERM_PROP_FOCUSREPORT,       // bool
   VTERM_PROP_CURSORCOLOR,       // VIM - string
 
   VTERM_N_PROPS
@@ -422,6 +424,11 @@ typedef struct {
 void  vterm_parser_set_callbacks(VTerm *vt, const VTermParserCallbacks *callbacks, void *user);
 void *vterm_parser_get_cbdata(VTerm *vt);
 
+/* Normally NUL, CAN, SUB and DEL are ignored. Setting this true causes them
+ * to be emitted by the 'control' callback
+ */
+void vterm_parser_set_emit_nul(VTerm *vt, int emit);
+
 // -----------
 // State layer
 // -----------
@@ -645,6 +652,12 @@ int vterm_screen_is_eol(const VTermScree
  */
 void vterm_screen_convert_color_to_rgb(const VTermScreen *screen, VTermColor *col);
 
+/**
+ * Similar to vterm_state_set_default_colors(), but also resets colours in the
+ * screen buffer(s)
+ */
+void vterm_screen_set_default_colors(VTermScreen *screen, const VTermColor *default_fg, const VTermColor *default_bg);
+
 // ---------
 // Utilities
 // ---------
--- a/src/libvterm/src/parser.c
+++ b/src/libvterm/src/parser.c
@@ -148,11 +148,15 @@ size_t vterm_input_write(VTerm *vt, cons
         string_fragment(vt, string_start, bytes + pos - string_start, FALSE);
         string_start = bytes + pos + 1;
       }
+      if(vt->parser.emit_nul)
+        do_control(vt, c);
       continue;
     }
     if(c == 0x18 || c == 0x1a) { // CAN, SUB
       vt->parser.in_esc = FALSE;
       ENTER_NORMAL_STATE();
+      if(vt->parser.emit_nul)
+        do_control(vt, c);
       continue;
     }
     else if(c == 0x1b) { // ESC
@@ -402,3 +406,8 @@ void *vterm_parser_get_cbdata(VTerm *vt)
 {
   return vt->parser.cbdata;
 }
+
+void vterm_parser_set_emit_nul(VTerm *vt, int emit)
+{
+  vt->parser.emit_nul = emit;
+}
--- a/src/libvterm/src/pen.c
+++ b/src/libvterm/src/pen.c
@@ -259,15 +259,17 @@ void vterm_state_get_palette_color(const
 
 void vterm_state_set_default_colors(VTermState *state, const VTermColor *default_fg, const VTermColor *default_bg)
 {
-  /* Copy the given colors */
-  state->default_fg = *default_fg;
-  state->default_bg = *default_bg;
+  if(default_fg) {
+    state->default_fg = *default_fg;
+    state->default_fg.type = (state->default_fg.type & ~VTERM_COLOR_DEFAULT_MASK)
+                           | VTERM_COLOR_DEFAULT_FG;
+  }
 
-  /* Make sure the correct type flags are set */
-  state->default_fg.type = (state->default_fg.type & ~VTERM_COLOR_DEFAULT_MASK)
-                         | VTERM_COLOR_DEFAULT_FG;
-  state->default_bg.type = (state->default_bg.type & ~VTERM_COLOR_DEFAULT_MASK)
-                         | VTERM_COLOR_DEFAULT_BG;
+  if(default_bg) {
+    state->default_bg = *default_bg;
+    state->default_bg.type = (state->default_bg.type & ~VTERM_COLOR_DEFAULT_MASK)
+                           | VTERM_COLOR_DEFAULT_BG;
+  }
 }
 
 void vterm_state_set_palette_color(VTermState *state, int index, const VTermColor *col)
--- a/src/libvterm/src/screen.c
+++ b/src/libvterm/src/screen.c
@@ -292,7 +292,11 @@ static int erase_internal(VTermRect rect
         continue;
 
       cell->chars[0] = 0;
-      cell->pen = screen->pen;
+      cell->pen = (ScreenPen){
+        /* Only copy .fg and .bg; leave things like rv in reset state */
+        .fg = screen->pen.fg,
+        .bg = screen->pen.bg,
+      };
       cell->pen.dwl = info->doublewidth;
       cell->pen.dhl = info->doubleheight;
     }
@@ -603,8 +607,15 @@ static void resize_buffer(VTermScreen *s
         new_row_start, new_row_end, old_row_start, old_row_end, width);
 #endif
 
-    if(new_row_start < 0)
+    if(new_row_start < 0) {
+      if(old_row_start <= old_cursor.row && old_cursor.row < old_row_end) {
+        new_cursor.row = 0;
+        new_cursor.col = old_cursor.col;
+        if(new_cursor.col >= new_cols)
+          new_cursor.col = new_cols-1;
+      }
       break;
+    }
 
     for(new_row = new_row_start, old_row = old_row_start; new_row <= new_row_end; new_row++) {
       int count = width >= new_cols ? new_cols : width;
@@ -674,8 +685,9 @@ static void resize_buffer(VTermScreen *s
 
   if(old_row >= 0 && bufidx == BUFIDX_PRIMARY) {
     /* Push spare lines to scrollback buffer */
-    for(int row = 0; row <= old_row; row++)
-      sb_pushline_from_row(screen, row);
+    if(screen->callbacks && screen->callbacks->sb_pushline)
+      for(int row = 0; row <= old_row; row++)
+        sb_pushline_from_row(screen, row);
     if(active)
       statefields->pos.row -= (old_row + 1);
   }
@@ -1204,3 +1216,36 @@ void vterm_screen_convert_color_to_rgb(c
 {
   vterm_state_convert_color_to_rgb(screen->state, col);
 }
+
+static void reset_default_colours(VTermScreen *screen, ScreenCell *buffer)
+{
+  for(int row = 0; row <= screen->rows - 1; row++)
+    for(int col = 0; col <= screen->cols - 1; col++) {
+      ScreenCell *cell = &buffer[row * screen->cols + col];
+      if(VTERM_COLOR_IS_DEFAULT_FG(&cell->pen.fg))
+        cell->pen.fg = screen->pen.fg;
+      if(VTERM_COLOR_IS_DEFAULT_BG(&cell->pen.bg))
+        cell->pen.bg = screen->pen.bg;
+    }
+}
+
+void vterm_screen_set_default_colors(VTermScreen *screen, const VTermColor *default_fg, const VTermColor *default_bg)
+{
+  vterm_state_set_default_colors(screen->state, default_fg, default_bg);
+
+  if(default_fg && VTERM_COLOR_IS_DEFAULT_FG(&screen->pen.fg)) {
+    screen->pen.fg = *default_fg;
+    screen->pen.fg.type = (screen->pen.fg.type & ~VTERM_COLOR_DEFAULT_MASK)
+                        | VTERM_COLOR_DEFAULT_FG;
+  }
+
+  if(default_bg && VTERM_COLOR_IS_DEFAULT_BG(&screen->pen.bg)) {
+    screen->pen.bg = *default_bg;
+    screen->pen.bg.type = (screen->pen.bg.type & ~VTERM_COLOR_DEFAULT_MASK)
+                        | VTERM_COLOR_DEFAULT_BG;
+  }
+
+  reset_default_colours(screen, screen->buffers[0]);
+  if(screen->buffers[1])
+    reset_default_colours(screen, screen->buffers[1]);
+}
--- a/src/libvterm/src/state.c
+++ b/src/libvterm/src/state.c
@@ -837,6 +837,7 @@ static void set_dec_mode(VTermState *sta
     break;
 
   case 1004:
+    settermprop_bool(state, VTERM_PROP_FOCUSREPORT, val);
     state->mode.report_focus = val;
     break;
 
@@ -993,6 +994,7 @@ static int on_csi(const char *leader, co
 
     switch(intermed[0]) {
     case ' ':
+    case '!':
     case '"':
     case '$':
     case '\'':
@@ -1370,8 +1372,10 @@ static int on_csi(const char *leader, co
     break;
 
   case LEADER('?', 0x68): // DEC private mode set
-    if(!CSI_ARG_IS_MISSING(args[0]))
-      set_dec_mode(state, CSI_ARG(args[0]), 1);
+    for(int i = 0; i < argcount; i++) {
+      if(!CSI_ARG_IS_MISSING(args[i]))
+        set_dec_mode(state, CSI_ARG(args[i]), 1);
+    }
     break;
 
   case 0x6a: // HPB - ECMA-48 8.3.58
@@ -1392,8 +1396,10 @@ static int on_csi(const char *leader, co
     break;
 
   case LEADER('?', 0x6c): // DEC private mode reset
-    if(!CSI_ARG_IS_MISSING(args[0]))
-      set_dec_mode(state, CSI_ARG(args[0]), 0);
+    for(int i = 0; i < argcount; i++) {
+      if(!CSI_ARG_IS_MISSING(args[i]))
+        set_dec_mode(state, CSI_ARG(args[i]), 0);
+    }
     break;
 
   case 0x6d: // SGR - ECMA-48 8.3.117
@@ -1486,7 +1492,7 @@ static int on_csi(const char *leader, co
     break;
 
 
-  case LEADER('!', 0x70): // DECSTR - DEC soft terminal reset
+  case INTERMED('!', 0x70): // DECSTR - DEC soft terminal reset
     vterm_state_reset(state, 0);
     break;
 
@@ -1769,8 +1775,18 @@ static void osc_selection(VTermState *st
     frag.len--;
   }
 
-  if(!frag.len)
+  if(!frag.len) {
+    /* Clear selection if we're already finished but didn't do anything */
+    if(frag.final && state->selection.callbacks->set) {
+      (*state->selection.callbacks->set)(state->tmp.selection.mask, (VTermStringFragment){
+              .str     = NULL,
+              .len     = 0,
+              .initial = state->tmp.selection.state != SELECTION_SET,
+              .final   = TRUE,
+            }, state->selection.user);
+    }
     return;
+  }
 
   if(state->tmp.selection.state == SELECTION_SELECTED) {
     if(frag.str[0] == '?') {
@@ -1788,6 +1804,9 @@ static void osc_selection(VTermState *st
     return;
   }
 
+  if(state->tmp.selection.state == SELECTION_INVALID)
+    return;
+
   if(state->selection.callbacks->set) {
     size_t bufcur = 0;
     char *buffer = state->selection.buffer;
@@ -1823,11 +1842,21 @@ static void osc_selection(VTermState *st
         uint8_t b = unbase64one(frag.str[0]);
         if(b == 0xFF) {
           DEBUG_LOG1("base64decode bad input %02X\n", (uint8_t)frag.str[0]);
+
+          state->tmp.selection.state = SELECTION_INVALID;
+          if(state->selection.callbacks->set) {
+            (*state->selection.callbacks->set)(state->tmp.selection.mask, (VTermStringFragment){
+                .str     = NULL,
+                .len     = 0,
+                .initial = TRUE,
+                .final   = TRUE,
+                }, state->selection.user);
+          }
+          break;
         }
-        else {
-          x = (x << 6) | b;
-          n++;
-        }
+
+        x = (x << 6) | b;
+        n++;
         frag.str++, frag.len--;
 
         if(n == 4) {
@@ -1847,7 +1876,7 @@ static void osc_selection(VTermState *st
 	    state->selection.buffer, // str
 	    bufcur, // len
 	    state->tmp.selection.state == SELECTION_SET_INITIAL, // initial
-	    frag.final // final
+	    frag.final && !frag.len // final
 	  };
           (*state->selection.callbacks->set)(state->tmp.selection.mask,
 	      setfrag, state->selection.user);
@@ -2004,7 +2033,7 @@ static void request_status_string(VTermS
       return;
   }
 
-  vterm_push_output_sprintf_str(state->vt, C1_DCS, TRUE, "0$r%s", tmp);
+  vterm_push_output_sprintf_str(state->vt, C1_DCS, TRUE, "0$r");
 }
 
 static int on_dcs(const char *command, size_t commandlen, VTermStringFragment frag, void *user)
@@ -2354,6 +2383,9 @@ int vterm_state_set_termprop(VTermState 
     if(val->number == VTERM_PROP_MOUSE_MOVE)
       state->mouse_flags |= MOUSE_WANT_MOVE;
     return 1;
+  case VTERM_PROP_FOCUSREPORT:
+    state->mode.report_focus = val->boolean;
+    return 1;
 
   case VTERM_N_PROPS:
     return 0;
--- a/src/libvterm/src/vterm.c
+++ b/src/libvterm/src/vterm.c
@@ -73,6 +73,8 @@ VTerm *vterm_build(const struct VTermBui
   vt->parser.callbacks = NULL;
   vt->parser.cbdata    = NULL;
 
+  vt->parser.emit_nul  = FALSE;
+
   vt->outfunc = NULL;
   vt->outdata = NULL;
 
@@ -314,6 +316,7 @@ VTermValueType vterm_get_prop_type(VTerm
     case VTERM_PROP_REVERSE:       return VTERM_VALUETYPE_BOOL;
     case VTERM_PROP_CURSORSHAPE:   return VTERM_VALUETYPE_INT;
     case VTERM_PROP_MOUSE:         return VTERM_VALUETYPE_INT;
+    case VTERM_PROP_FOCUSREPORT:   return VTERM_VALUETYPE_BOOL;
     case VTERM_PROP_CURSORCOLOR:   return VTERM_VALUETYPE_STRING;
 
     case VTERM_N_PROPS: return 0;
--- a/src/libvterm/src/vterm_internal.h
+++ b/src/libvterm/src/vterm_internal.h
@@ -169,6 +169,7 @@ struct VTermState
         SELECTION_QUERY,
         SELECTION_SET_INITIAL,
         SELECTION_SET,
+        SELECTION_INVALID,
       } state : 8;
       uint32_t recvpartial;
       uint32_t sendpartial;
@@ -238,6 +239,8 @@ struct VTerm
     void *cbdata;
 
     int string_initial;
+
+    int emit_nul;
   } parser;
 
   /* len == malloc()ed size; cur == number of valid bytes */
--- a/src/libvterm/t/17state_mouse.test
+++ b/src/libvterm/t/17state_mouse.test
@@ -55,6 +55,10 @@ MOUSEBTN d 4 0
   output "\e[M\x60\x36\x2b"
 MOUSEBTN d 5 0
   output "\e[M\x61\x36\x2b"
+MOUSEBTN d 6 0
+  output "\e[M\x62\x36\x2b"
+MOUSEBTN d 7 0
+  output "\e[M\x63\x36\x2b"
 
 !DECRQM on mouse button mode
 PUSH "\e[?1000\$p"
@@ -179,3 +183,9 @@ RESET
 MOUSEMOVE 0,0 0
 MOUSEBTN d 1 0
 MOUSEBTN u 1 0
+
+!DECSM can set multiple modes at once
+PUSH "\e[?1002;1006h"
+  settermprop 8 2
+MOUSEBTN d 1 0
+  output "\e[<0;1;1M"
--- a/src/libvterm/t/25state_input.test
+++ b/src/libvterm/t/25state_input.test
@@ -148,7 +148,9 @@ FOCUS IN
 FOCUS OUT
 
 !Focus reporting enabled
+WANTSTATE +p
 PUSH "\e[?1004h"
+  settermprop 9 true
 FOCUS IN
   output "\e[I"
 FOCUS OUT
--- a/src/libvterm/t/30state_pen.test
+++ b/src/libvterm/t/30state_pen.test
@@ -123,3 +123,11 @@ PUSH "\e[74m"
 PUSH "\e[75m"
   ?pen small = off
   ?pen baseline = normal
+
+!DECSTR resets pen attributes
+PUSH "\e[1;4m"
+  ?pen bold = on
+  ?pen underline = 1
+PUSH "\e[!p"
+  ?pen bold = off
+  ?pen underline = 0
--- a/src/libvterm/t/40state_selection.test
+++ b/src/libvterm/t/40state_selection.test
@@ -26,6 +26,30 @@ PUSH "\e]52;c;SGVsbG"
 PUSH "8s\e\\"
   selection-set mask=0001 "lo,"]
 
+!Set clipboard; empty first chunk
+PUSH "\e]52;c;"
+PUSH "SGVsbG8s\e\\"
+  selection-set mask=0001 ["Hello,"]
+
+!Set clipboard; empty final chunk
+PUSH "\e]52;c;SGVsbG8s"
+  selection-set mask=0001 ["Hello,"
+PUSH "\e\\"
+  selection-set mask=0001 ]
+
+!Set clipboard; longer than buffer
+PUSH "\e]52;c;" . "LS0t"x10 . "\e\\"
+  selection-set mask=0001 ["-"x15
+  selection-set mask=0001 "-"x15]
+
+!Clear clipboard
+PUSH "\e]52;c;\e\\"
+  selection-set mask=0001 []
+
+!Set invalid data clears and ignores
+PUSH "\e]52;c;SGVs*SGVsbG8s\e\\"
+  selection-set mask=0001 []
+
 !Query clipboard
 PUSH "\e]52;c;?\e\\"
   selection-query mask=0001
--- a/src/libvterm/t/64screen_pen.test
+++ b/src/libvterm/t/64screen_pen.test
@@ -41,21 +41,31 @@ PUSH "x\e[74m0\e[73m2\e[m"
   ?screen_cell 0,9  = {0x30} width=1 attrs={S_} fg=rgb(240,240,240) bg=rgb(0,0,0)
   ?screen_cell 0,10 = {0x32} width=1 attrs={S^} fg=rgb(240,240,240) bg=rgb(0,0,0)
 
-!EL sets reverse and colours to end of line
+!EL sets only colours to end of line, not other attrs
 PUSH "\e[H\e[7;33;44m\e[K"
-  ?screen_cell 0,0  = {} width=1 attrs={R} fg=idx(3) bg=idx(4)
-  ?screen_cell 0,79 = {} width=1 attrs={R} fg=idx(3) bg=idx(4)
+  ?screen_cell 0,0  = {} width=1 attrs={} fg=idx(3) bg=idx(4)
+  ?screen_cell 0,79 = {} width=1 attrs={} fg=idx(3) bg=idx(4)
 
 !DECSCNM xors reverse for entire screen
-PUSH "\e[?5h"
-  ?screen_cell 0,0  = {} width=1 attrs={} fg=idx(3) bg=idx(4)
-  ?screen_cell 0,79 = {} width=1 attrs={} fg=idx(3) bg=idx(4)
+PUSH "R\e[?5h"
+  ?screen_cell 0,0  = {0x52} width=1 attrs={} fg=idx(3) bg=idx(4)
   ?screen_cell 1,0  = {} width=1 attrs={R} fg=rgb(240,240,240) bg=rgb(0,0,0)
 PUSH "\e[?5\$p"
   output "\e[?5;1\$y"
 PUSH "\e[?5l"
-  ?screen_cell 0,0  = {} width=1 attrs={R} fg=idx(3) bg=idx(4)
-  ?screen_cell 0,79 = {} width=1 attrs={R} fg=idx(3) bg=idx(4)
+  ?screen_cell 0,0  = {0x52} width=1 attrs={R} fg=idx(3) bg=idx(4)
   ?screen_cell 1,0  = {} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)
 PUSH "\e[?5\$p"
   output "\e[?5;2\$y"
+
+!Set default colours
+RESET
+PUSH "ABC\e[31mDEF\e[m"
+  ?screen_cell 0,0  = {0x41} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)
+  ?screen_cell 0,3  = {0x44} width=1 attrs={} fg=idx(1) bg=rgb(0,0,0)
+SETDEFAULTCOL rgb(252,253,254)
+  ?screen_cell 0,0  = {0x41} width=1 attrs={} fg=rgb(252,253,254) bg=rgb(0,0,0)
+  ?screen_cell 0,3  = {0x44} width=1 attrs={} fg=idx(1) bg=rgb(0,0,0)
+SETDEFAULTCOL rgb(250,250,250) rgb(10,20,30)
+  ?screen_cell 0,0  = {0x41} width=1 attrs={} fg=rgb(250,250,250) bg=rgb(10,20,30)
+  ?screen_cell 0,3  = {0x44} width=1 attrs={} fg=idx(1) bg=rgb(10,20,30)
--- a/src/libvterm/t/69screen_reflow.test
+++ b/src/libvterm/t/69screen_reflow.test
@@ -77,3 +77,12 @@ RESIZE 5,16
   ?lineinfo 3 =
   ?screen_row 3 = "> "
   ?cursor = 3,2
+
+!Cursor goes missing
+# For more context: https://github.com/neovim/neovim/pull/21124
+RESET
+RESIZE 5,5
+RESIZE 3,1
+PUSH "\x1b[2;1Habc\r\n\x1b[H"
+RESIZE 1,1
+  ?cursor = 0,0
--- a/src/libvterm/t/harness.c
+++ b/src/libvterm/t/harness.c
@@ -82,6 +82,26 @@ static void print_color(const VTermColor
   printf(")");
 }
 
+static VTermColor strpe_color(char **strp)
+{
+  uint8_t r, g, b, idx;
+  int len = 0;
+  VTermColor col;
+
+  if(sscanf(*strp, "rgb(%hhu,%hhu,%hhu)%n", &r, &g, &b, &len) == 3 && len > 0) {
+    *strp += len;
+    vterm_color_rgb(&col, r, g, b);
+  }
+  else if(sscanf(*strp, "idx(%hhu)%n", &idx, &len) == 1 && len > 0) {
+    *strp += len;
+    vterm_color_indexed(&col, idx);
+  }
+  else
+    vterm_color_rgb(&col, 127, 127, 127);
+
+  return col;
+}
+
 static VTerm *vt;
 static VTermState *state;
 static VTermScreen *screen;
@@ -669,7 +689,10 @@ int main(int argc UNUSED, char **argv UN
       if(!state) {
         state = vterm_obtain_state(vt);
         vterm_state_set_callbacks(state, &state_cbs, NULL);
-        vterm_state_set_selection_callbacks(state, &selection_cbs, NULL, NULL, 1024);
+        /* In some tests we want to check the behaviour of overflowing the
+         * buffer, so make it nicely small
+         */
+        vterm_state_set_selection_callbacks(state, &selection_cbs, NULL, NULL, 16);
         vterm_state_set_bold_highbright(state, 1);
         vterm_state_reset(state, 1);
       }
@@ -942,6 +965,23 @@ int main(int argc UNUSED, char **argv UN
       vterm_screen_flush_damage(screen);
     }
 
+    else if(strstartswith(line, "SETDEFAULTCOL ")) {
+      assert(screen);
+      char *linep = line + 14;
+      while(linep[0] == ' ')
+        linep++;
+      VTermColor fg = strpe_color(&linep);
+      if(linep[0]) {
+        while(linep[0] == ' ')
+          linep++;
+        VTermColor bg = strpe_color(&linep);
+
+        vterm_screen_set_default_colors(screen, &fg, &bg);
+      }
+      else
+        vterm_screen_set_default_colors(screen, &fg, NULL);
+    }
+
     else if(line[0] == '?') {
       if(streq(line, "?cursor")) {
         assert(state);
--- a/src/version.c
+++ b/src/version.c
@@ -696,6 +696,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    1684,
+/**/
     1683,
 /**/
     1682,