neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

vterm_test.c (17576B)


      1 #include <stdio.h>
      2 #include <string.h>
      3 
      4 #include "nvim/grid.h"
      5 #include "nvim/mbyte.h"
      6 #include "nvim/vterm/pen.h"
      7 #include "nvim/vterm/screen.h"
      8 #include "nvim/vterm/vterm_internal_defs.h"
      9 #include "vterm_test.h"
     10 
     11 int parser_text(const char bytes[], size_t len, void *user)
     12 {
     13  FILE *f = fopen(VTERM_TEST_FILE, "a");
     14  fprintf(f, "text ");
     15  size_t i;
     16  for (i = 0; i < len; i++) {
     17    unsigned char b = (unsigned char)bytes[i];
     18    if (b < 0x20 || b == 0x7f || (b >= 0x80 && b < 0xa0)) {
     19      break;
     20    }
     21    fprintf(f, i ? ",%x" : "%x", b);
     22  }
     23  fprintf(f, "\n");
     24  fclose(f);
     25 
     26  return (int)i;
     27 }
     28 
     29 static void printchars(const char *s, size_t len, FILE *f)
     30 {
     31  while (len--) {
     32    fprintf(f, "%c", (s++)[0]);
     33  }
     34 }
     35 
     36 int parser_csi(const char *leader, const long args[], int argcount, const char *intermed,
     37               char command, void *user)
     38 {
     39  FILE *f = fopen(VTERM_TEST_FILE, "a");
     40  fprintf(f, "csi %02x", command);
     41 
     42  if (leader && leader[0]) {
     43    fprintf(f, " L=");
     44    for (int i = 0; leader[i]; i++) {
     45      fprintf(f, "%02x", leader[i]);
     46    }
     47  }
     48 
     49  for (int i = 0; i < argcount; i++) {
     50    char sep = i ? ',' : ' ';
     51 
     52    if (args[i] == CSI_ARG_MISSING) {
     53      fprintf(f, "%c*", sep);
     54    } else {
     55      fprintf(f, "%c%ld%s", sep, CSI_ARG(args[i]), CSI_ARG_HAS_MORE(args[i]) ? "+" : "");
     56    }
     57  }
     58 
     59  if (intermed && intermed[0]) {
     60    fprintf(f, " I=");
     61    for (int i = 0; intermed[i]; i++) {
     62      fprintf(f, "%02x", intermed[i]);
     63    }
     64  }
     65 
     66  fprintf(f, "\n");
     67 
     68  fclose(f);
     69 
     70  return 1;
     71 }
     72 
     73 int parser_osc(int command, VTermStringFragment frag, void *user)
     74 {
     75  FILE *f = fopen(VTERM_TEST_FILE, "a");
     76  fprintf(f, "osc ");
     77 
     78  if (frag.initial) {
     79    if (command == -1) {
     80      fprintf(f, "[");
     81    } else {
     82      fprintf(f, "[%d;", command);
     83    }
     84  }
     85 
     86  printchars(frag.str, frag.len, f);
     87 
     88  if (frag.final) {
     89    fprintf(f, "]");
     90  }
     91 
     92  fprintf(f, "\n");
     93  fclose(f);
     94 
     95  return 1;
     96 }
     97 
     98 int parser_dcs(const char *command, size_t commandlen, VTermStringFragment frag, void *user)
     99 {
    100  FILE *f = fopen(VTERM_TEST_FILE, "a");
    101  fprintf(f, "dcs ");
    102 
    103  if (frag.initial) {
    104    fprintf(f, "[");
    105    for (size_t i = 0; i < commandlen; i++) {
    106      fprintf(f, "%c", command[i]);
    107    }
    108  }
    109 
    110  printchars(frag.str, frag.len, f);
    111 
    112  if (frag.final) {
    113    fprintf(f, "]");
    114  }
    115 
    116  fprintf(f, "\n");
    117  fclose(f);
    118 
    119  return 1;
    120 }
    121 
    122 int parser_apc(VTermStringFragment frag, void *user)
    123 {
    124  FILE *f = fopen(VTERM_TEST_FILE, "a");
    125  fprintf(f, "apc ");
    126 
    127  if (frag.initial) {
    128    fprintf(f, "[");
    129  }
    130 
    131  printchars(frag.str, frag.len, f);
    132 
    133  if (frag.final) {
    134    fprintf(f, "]");
    135  }
    136 
    137  fprintf(f, "\n");
    138  fclose(f);
    139 
    140  return 1;
    141 }
    142 
    143 int parser_pm(VTermStringFragment frag, void *user)
    144 {
    145  FILE *f = fopen(VTERM_TEST_FILE, "a");
    146  fprintf(f, "pm ");
    147 
    148  if (frag.initial) {
    149    fprintf(f, "[");
    150  }
    151 
    152  printchars(frag.str, frag.len, f);
    153 
    154  if (frag.final) {
    155    fprintf(f, "]");
    156  }
    157 
    158  fprintf(f, "\n");
    159  fclose(f);
    160 
    161  return 1;
    162 }
    163 
    164 int parser_sos(VTermStringFragment frag, void *user)
    165 {
    166  FILE *f = fopen(VTERM_TEST_FILE, "a");
    167  fprintf(f, "sos ");
    168 
    169  if (frag.initial) {
    170    fprintf(f, "[");
    171  }
    172 
    173  printchars(frag.str, frag.len, f);
    174 
    175  if (frag.final) {
    176    fprintf(f, "]");
    177  }
    178 
    179  fprintf(f, "\n");
    180  fclose(f);
    181 
    182  return 1;
    183 }
    184 
    185 int selection_set(VTermSelectionMask mask, VTermStringFragment frag, void *user)
    186 {
    187  FILE *f = fopen(VTERM_TEST_FILE, "a");
    188  fprintf(f, "selection-set mask=%04X ", mask);
    189  if (frag.initial) {
    190    fprintf(f, "[");
    191  }
    192  printchars(frag.str, frag.len, f);
    193  if (frag.final) {
    194    fprintf(f, "]");
    195  }
    196  fprintf(f, "\n");
    197 
    198  fclose(f);
    199  return 1;
    200 }
    201 
    202 int selection_query(VTermSelectionMask mask, void *user)
    203 {
    204  FILE *f = fopen(VTERM_TEST_FILE, "a");
    205  fprintf(f, "selection-query mask=%04X\n", mask);
    206 
    207  fclose(f);
    208  return 1;
    209 }
    210 
    211 static void print_schar(FILE *f, schar_T schar)
    212 {
    213  char buf[MAX_SCHAR_SIZE];
    214  schar_get(buf, schar);
    215  StrCharInfo ci = utf_ptr2StrCharInfo(buf);
    216  bool did = false;
    217  while (*ci.ptr != 0) {
    218    if (did) {
    219      fprintf(f, ",");
    220    }
    221 
    222    if (ci.chr.len == 1 && ci.chr.value >= 0x80) {
    223      fprintf(f, "??%x", ci.chr.value);
    224    } else {
    225      fprintf(f, "%x", ci.chr.value);
    226    }
    227    did = true;
    228    ci = utf_ptr2StrCharInfo(ci.ptr + ci.chr.len);
    229  }
    230 }
    231 
    232 bool want_state_putglyph;
    233 int state_putglyph(VTermGlyphInfo *info, VTermPos pos, void *user)
    234 {
    235  if (!want_state_putglyph) {
    236    return 1;
    237  }
    238 
    239  FILE *f = fopen(VTERM_TEST_FILE, "a");
    240  fprintf(f, "putglyph ");
    241  print_schar(f, info->schar);
    242  fprintf(f, " %d %d,%d", info->width, pos.row, pos.col);
    243  if (info->protected_cell) {
    244    fprintf(f, " prot");
    245  }
    246  if (info->dwl) {
    247    fprintf(f, " dwl");
    248  }
    249  if (info->dhl) {
    250    fprintf(f, " dhl-%s", info->dhl == 1 ? "top" : info->dhl == 2 ? "bottom" : "?");
    251  }
    252  fprintf(f, "\n");
    253 
    254  fclose(f);
    255 
    256  return 1;
    257 }
    258 
    259 bool want_state_movecursor;
    260 VTermPos state_pos;
    261 int state_movecursor(VTermPos pos, VTermPos oldpos, int visible, void *user)
    262 {
    263  FILE *f = fopen(VTERM_TEST_FILE, "a");
    264  state_pos = pos;
    265 
    266  if (want_state_movecursor) {
    267    fprintf(f, "movecursor %d,%d\n", pos.row, pos.col);
    268  }
    269 
    270  fclose(f);
    271  return 1;
    272 }
    273 
    274 bool want_state_scrollrect;
    275 int state_scrollrect(VTermRect rect, int downward, int rightward, void *user)
    276 {
    277  if (!want_state_scrollrect) {
    278    return 0;
    279  }
    280 
    281  FILE *f = fopen(VTERM_TEST_FILE, "a");
    282 
    283  fprintf(f, "scrollrect %d..%d,%d..%d => %+d,%+d\n",
    284          rect.start_row, rect.end_row, rect.start_col, rect.end_col,
    285          downward, rightward);
    286 
    287  fclose(f);
    288  return 1;
    289 }
    290 
    291 bool want_state_moverect;
    292 int state_moverect(VTermRect dest, VTermRect src, void *user)
    293 {
    294  if (!want_state_moverect) {
    295    return 0;
    296  }
    297 
    298  FILE *f = fopen(VTERM_TEST_FILE, "a");
    299  fprintf(f, "moverect %d..%d,%d..%d -> %d..%d,%d..%d\n",
    300          src.start_row,  src.end_row,  src.start_col,  src.end_col,
    301          dest.start_row, dest.end_row, dest.start_col, dest.end_col);
    302 
    303  fclose(f);
    304  return 1;
    305 }
    306 
    307 void print_color(const VTermColor *col)
    308 {
    309  FILE *f = fopen(VTERM_TEST_FILE, "a");
    310  if (VTERM_COLOR_IS_RGB(col)) {
    311    fprintf(f, "rgb(%d,%d,%d", col->rgb.red, col->rgb.green, col->rgb.blue);
    312  } else if (VTERM_COLOR_IS_INDEXED(col)) {
    313    fprintf(f, "idx(%d", col->indexed.idx);
    314  } else {
    315    fprintf(f, "invalid(%d", col->type);
    316  }
    317  if (VTERM_COLOR_IS_DEFAULT_FG(col)) {
    318    fprintf(f, ",is_default_fg");
    319  }
    320  if (VTERM_COLOR_IS_DEFAULT_BG(col)) {
    321    fprintf(f, ",is_default_bg");
    322  }
    323  fprintf(f, ")");
    324  fclose(f);
    325 }
    326 
    327 static VTermValueType vterm_get_prop_type(VTermProp prop)
    328 {
    329  switch (prop) {
    330  case VTERM_PROP_CURSORVISIBLE:
    331    return VTERM_VALUETYPE_BOOL;
    332  case VTERM_PROP_CURSORBLINK:
    333    return VTERM_VALUETYPE_BOOL;
    334  case VTERM_PROP_ALTSCREEN:
    335    return VTERM_VALUETYPE_BOOL;
    336  case VTERM_PROP_TITLE:
    337    return VTERM_VALUETYPE_STRING;
    338  case VTERM_PROP_ICONNAME:
    339    return VTERM_VALUETYPE_STRING;
    340  case VTERM_PROP_REVERSE:
    341    return VTERM_VALUETYPE_BOOL;
    342  case VTERM_PROP_CURSORSHAPE:
    343    return VTERM_VALUETYPE_INT;
    344  case VTERM_PROP_MOUSE:
    345    return VTERM_VALUETYPE_INT;
    346  case VTERM_PROP_FOCUSREPORT:
    347    return VTERM_VALUETYPE_BOOL;
    348  case VTERM_PROP_THEMEUPDATES:
    349    return VTERM_VALUETYPE_BOOL;
    350 
    351  case VTERM_N_PROPS:
    352    return 0;
    353  }
    354  return 0;  // UNREACHABLE
    355 }
    356 
    357 bool want_state_settermprop;
    358 int state_settermprop(VTermProp prop, VTermValue *val, void *user)
    359 {
    360  if (!want_state_settermprop) {
    361    return 1;
    362  }
    363 
    364  int errcode = 0;
    365  FILE *f = fopen(VTERM_TEST_FILE, "a");
    366 
    367  VTermValueType type = vterm_get_prop_type(prop);
    368  switch (type) {
    369  case VTERM_VALUETYPE_BOOL:
    370    fprintf(f, "settermprop %d %s\n", prop, val->boolean ? "true" : "false");
    371    errcode = 1;
    372    goto end;
    373  case VTERM_VALUETYPE_INT:
    374    fprintf(f, "settermprop %d %d\n", prop, val->number);
    375    errcode = 1;
    376    goto end;
    377  case VTERM_VALUETYPE_STRING:
    378    fprintf(f, "settermprop %d %s\"%.*s\"%s\n", prop,
    379            val->string.initial ? "[" : "", (int)val->string.len, val->string.str,
    380            val->string.final ? "]" : "");
    381    errcode = 0;
    382    goto end;
    383  case VTERM_VALUETYPE_COLOR:
    384    fprintf(f, "settermprop %d ", prop);
    385    print_color(&val->color);
    386    fprintf(f, "\n");
    387    errcode = 1;
    388    goto end;
    389  case VTERM_N_VALUETYPES:
    390    goto end;
    391  }
    392 
    393 end:
    394  fclose(f);
    395  return errcode;
    396 }
    397 
    398 bool want_state_erase;
    399 int state_erase(VTermRect rect, int selective, void *user)
    400 {
    401  if (!want_state_erase) {
    402    return 1;
    403  }
    404 
    405  FILE *f = fopen(VTERM_TEST_FILE, "a");
    406 
    407  fprintf(f, "erase %d..%d,%d..%d%s\n",
    408          rect.start_row, rect.end_row, rect.start_col, rect.end_col,
    409          selective ? " selective" : "");
    410 
    411  fclose(f);
    412  return 1;
    413 }
    414 
    415 struct {
    416  int bold;
    417  int underline;
    418  int italic;
    419  int blink;
    420  int reverse;
    421  int conceal;
    422  int strike;
    423  int font;
    424  int small;
    425  int baseline;
    426  int dim;
    427  int overline;
    428  VTermColor foreground;
    429  VTermColor background;
    430 } state_pen;
    431 
    432 int state_setpenattr(VTermAttr attr, VTermValue *val, void *user)
    433 {
    434  switch (attr) {
    435  case VTERM_ATTR_BOLD:
    436    state_pen.bold = val->boolean;
    437    break;
    438  case VTERM_ATTR_UNDERLINE:
    439    state_pen.underline = val->number;
    440    break;
    441  case VTERM_ATTR_ITALIC:
    442    state_pen.italic = val->boolean;
    443    break;
    444  case VTERM_ATTR_BLINK:
    445    state_pen.blink = val->boolean;
    446    break;
    447  case VTERM_ATTR_REVERSE:
    448    state_pen.reverse = val->boolean;
    449    break;
    450  case VTERM_ATTR_CONCEAL:
    451    state_pen.conceal = val->boolean;
    452    break;
    453  case VTERM_ATTR_STRIKE:
    454    state_pen.strike = val->boolean;
    455    break;
    456  case VTERM_ATTR_FONT:
    457    state_pen.font = val->number;
    458    break;
    459  case VTERM_ATTR_SMALL:
    460    state_pen.small = val->boolean;
    461    break;
    462  case VTERM_ATTR_BASELINE:
    463    state_pen.baseline = val->number;
    464    break;
    465  case VTERM_ATTR_DIM:
    466    state_pen.dim = val->boolean;
    467    break;
    468  case VTERM_ATTR_OVERLINE:
    469    state_pen.overline = val->boolean;
    470    break;
    471  case VTERM_ATTR_FOREGROUND:
    472    state_pen.foreground = val->color;
    473    break;
    474  case VTERM_ATTR_BACKGROUND:
    475    state_pen.background = val->color;
    476    break;
    477 
    478  case VTERM_N_ATTRS:
    479    return 0;
    480  default:
    481    break;
    482  }
    483 
    484  return 1;
    485 }
    486 
    487 bool want_state_scrollback;
    488 int state_sb_clear(void *user)
    489 {
    490  if (!want_state_scrollback) {
    491    return 1;
    492  }
    493 
    494  FILE *f = fopen(VTERM_TEST_FILE, "a");
    495  fprintf(f, "sb_clear\n");
    496  fclose(f);
    497 
    498  return 0;
    499 }
    500 
    501 bool want_screen_scrollback;
    502 int screen_sb_pushline(int cols, const VTermScreenCell *cells, void *user)
    503 {
    504  if (!want_screen_scrollback) {
    505    return 1;
    506  }
    507 
    508  int eol = cols;
    509  while (eol && !cells[eol - 1].schar) {
    510    eol--;
    511  }
    512 
    513  FILE *f = fopen(VTERM_TEST_FILE, "a");
    514  fprintf(f, "sb_pushline %d =", cols);
    515  for (int c = 0; c < eol; c++) {
    516    fprintf(f, " ");
    517    print_schar(f, cells[c].schar);
    518  }
    519  fprintf(f, "\n");
    520 
    521  fclose(f);
    522 
    523  return 1;
    524 }
    525 
    526 int screen_sb_popline(int cols, VTermScreenCell *cells, void *user)
    527 {
    528  if (!want_screen_scrollback) {
    529    return 0;
    530  }
    531 
    532  // All lines of scrollback contain "ABCDE"
    533  for (int col = 0; col < cols; col++) {
    534    if (col < 5) {
    535      cells[col].schar = schar_from_ascii((uint32_t)('A' + col));
    536    } else {
    537      cells[col].schar = 0;
    538    }
    539 
    540    cells[col].width = 1;
    541  }
    542 
    543  FILE *f = fopen(VTERM_TEST_FILE, "a");
    544  fprintf(f, "sb_popline %d\n", cols);
    545  fclose(f);
    546  return 1;
    547 }
    548 
    549 int screen_sb_clear(void *user)
    550 {
    551  if (!want_screen_scrollback) {
    552    return 1;
    553  }
    554 
    555  FILE *f = fopen(VTERM_TEST_FILE, "a");
    556  fprintf(f, "sb_clear\n");
    557  fclose(f);
    558  return 0;
    559 }
    560 
    561 void term_output(const char *s, size_t len, void *user)
    562 {
    563  FILE *f = fopen(VTERM_TEST_FILE, "a");
    564  fprintf(f, "output ");
    565  for (size_t i = 0; i < len; i++) {
    566    fprintf(f, "%x%s", (unsigned char)s[i], i < len - 1 ? "," : "\n");
    567  }
    568  fclose(f);
    569 }
    570 
    571 int vterm_state_get_penattr(const VTermState *state, VTermAttr attr, VTermValue *val)
    572 {
    573  switch (attr) {
    574  case VTERM_ATTR_BOLD:
    575    val->boolean = state->pen.bold;
    576    return 1;
    577 
    578  case VTERM_ATTR_UNDERLINE:
    579    val->number = state->pen.underline;
    580    return 1;
    581 
    582  case VTERM_ATTR_ITALIC:
    583    val->boolean = state->pen.italic;
    584    return 1;
    585 
    586  case VTERM_ATTR_BLINK:
    587    val->boolean = state->pen.blink;
    588    return 1;
    589 
    590  case VTERM_ATTR_REVERSE:
    591    val->boolean = state->pen.reverse;
    592    return 1;
    593 
    594  case VTERM_ATTR_CONCEAL:
    595    val->boolean = state->pen.conceal;
    596    return 1;
    597 
    598  case VTERM_ATTR_STRIKE:
    599    val->boolean = state->pen.strike;
    600    return 1;
    601 
    602  case VTERM_ATTR_FONT:
    603    val->number = state->pen.font;
    604    return 1;
    605 
    606  case VTERM_ATTR_FOREGROUND:
    607    val->color = state->pen.fg;
    608    return 1;
    609 
    610  case VTERM_ATTR_BACKGROUND:
    611    val->color = state->pen.bg;
    612    return 1;
    613 
    614  case VTERM_ATTR_SMALL:
    615    val->boolean = state->pen.small;
    616    return 1;
    617 
    618  case VTERM_ATTR_BASELINE:
    619    val->number = state->pen.baseline;
    620    return 1;
    621 
    622  case VTERM_ATTR_URI:
    623    val->number = state->pen.uri;
    624    return 1;
    625 
    626  case VTERM_ATTR_DIM:
    627    val->boolean = state->pen.dim;
    628    return 1;
    629 
    630  case VTERM_ATTR_OVERLINE:
    631    val->boolean = state->pen.overline;
    632    return 1;
    633 
    634  case VTERM_N_ATTRS:
    635    return 0;
    636  }
    637 
    638  return 0;
    639 }
    640 
    641 static int attrs_differ(VTermAttrMask attrs, ScreenCell *a, ScreenCell *b)
    642 {
    643  if ((attrs & VTERM_ATTR_BOLD_MASK) && (a->pen.bold != b->pen.bold)) {
    644    return 1;
    645  }
    646  if ((attrs & VTERM_ATTR_UNDERLINE_MASK) && (a->pen.underline != b->pen.underline)) {
    647    return 1;
    648  }
    649  if ((attrs & VTERM_ATTR_ITALIC_MASK) && (a->pen.italic != b->pen.italic)) {
    650    return 1;
    651  }
    652  if ((attrs & VTERM_ATTR_BLINK_MASK) && (a->pen.blink != b->pen.blink)) {
    653    return 1;
    654  }
    655  if ((attrs & VTERM_ATTR_REVERSE_MASK) && (a->pen.reverse != b->pen.reverse)) {
    656    return 1;
    657  }
    658  if ((attrs & VTERM_ATTR_CONCEAL_MASK) && (a->pen.conceal != b->pen.conceal)) {
    659    return 1;
    660  }
    661  if ((attrs & VTERM_ATTR_STRIKE_MASK) && (a->pen.strike != b->pen.strike)) {
    662    return 1;
    663  }
    664  if ((attrs & VTERM_ATTR_FONT_MASK) && (a->pen.font != b->pen.font)) {
    665    return 1;
    666  }
    667  if ((attrs & VTERM_ATTR_FOREGROUND_MASK) && !vterm_color_is_equal(&a->pen.fg, &b->pen.fg)) {
    668    return 1;
    669  }
    670  if ((attrs & VTERM_ATTR_BACKGROUND_MASK) && !vterm_color_is_equal(&a->pen.bg, &b->pen.bg)) {
    671    return 1;
    672  }
    673  if ((attrs & VTERM_ATTR_SMALL_MASK) && (a->pen.small != b->pen.small)) {
    674    return 1;
    675  }
    676  if ((attrs & VTERM_ATTR_BASELINE_MASK) && (a->pen.baseline != b->pen.baseline)) {
    677    return 1;
    678  }
    679  if ((attrs & VTERM_ATTR_URI_MASK) && (a->pen.uri != b->pen.uri)) {
    680    return 1;
    681  }
    682  if ((attrs & VTERM_ATTR_DIM_MASK) && (a->pen.dim != b->pen.dim)) {
    683    return 1;
    684  }
    685  if ((attrs & VTERM_ATTR_OVERLINE_MASK) && (a->pen.overline != b->pen.overline)) {
    686    return 1;
    687  }
    688 
    689  return 0;
    690 }
    691 
    692 int vterm_screen_get_attrs_extent(const VTermScreen *screen, VTermRect *extent, VTermPos pos,
    693                                  VTermAttrMask attrs)
    694 {
    695  ScreenCell *target = getcell(screen, pos.row, pos.col);
    696 
    697  // TODO(vterm): bounds check
    698  extent->start_row = pos.row;
    699  extent->end_row = pos.row + 1;
    700 
    701  if (extent->start_col < 0) {
    702    extent->start_col = 0;
    703  }
    704  if (extent->end_col < 0) {
    705    extent->end_col = screen->cols;
    706  }
    707 
    708  int col;
    709 
    710  for (col = pos.col - 1; col >= extent->start_col; col--) {
    711    if (attrs_differ(attrs, target, getcell(screen, pos.row, col))) {
    712      break;
    713    }
    714  }
    715  extent->start_col = col + 1;
    716 
    717  for (col = pos.col + 1; col < extent->end_col; col++) {
    718    if (attrs_differ(attrs, target, getcell(screen, pos.row, col))) {
    719      break;
    720    }
    721  }
    722  extent->end_col = col - 1;
    723 
    724  return 1;
    725 }
    726 
    727 /// Does not NUL-terminate the buffer
    728 size_t vterm_screen_get_text(const VTermScreen *screen, char *buffer, size_t len,
    729                             const VTermRect rect)
    730 {
    731  size_t outpos = 0;
    732  int padding = 0;
    733 
    734 #define PUT(bytes, thislen) \
    735  if (true) { \
    736    if (buffer && outpos + thislen <= len) \
    737    memcpy((char *)buffer + outpos, bytes, thislen); \
    738    outpos += thislen; \
    739  } \
    740 
    741  for (int row = rect.start_row; row < rect.end_row; row++) {
    742    for (int col = rect.start_col; col < rect.end_col; col++) {
    743      ScreenCell *cell = getcell(screen, row, col);
    744 
    745      if (cell->schar == 0) {
    746        // Erased cell, might need a space
    747        padding++;
    748      } else if (cell->schar == (uint32_t)-1) {
    749        // Gap behind a double-width char, do nothing
    750      } else {
    751        while (padding) {
    752          PUT(" ", 1);
    753          padding--;
    754        }
    755        char buf[MAX_SCHAR_SIZE + 1];
    756        size_t thislen = schar_get(buf, cell->schar);
    757        PUT(buf, thislen);
    758      }
    759    }
    760 
    761    if (row < rect.end_row - 1) {
    762      PUT("\n", 1);
    763      padding = 0;
    764    }
    765  }
    766 
    767  return outpos;
    768 }
    769 
    770 int vterm_screen_is_eol(const VTermScreen *screen, VTermPos pos)
    771 {
    772  // This cell is EOL if this and every cell to the right is black
    773  for (; pos.col < screen->cols; pos.col++) {
    774    ScreenCell *cell = getcell(screen, pos.row, pos.col);
    775    if (cell->schar != 0) {
    776      return 0;
    777    }
    778  }
    779 
    780  return 1;
    781 }
    782 
    783 void vterm_state_get_cursorpos(const VTermState *state, VTermPos *cursorpos)
    784 {
    785  *cursorpos = state->pos;
    786 }
    787 
    788 void vterm_state_set_bold_highbright(VTermState *state, int bold_is_highbright)
    789 {
    790  state->bold_is_highbright = bold_is_highbright;
    791 }
    792 
    793 /// Compares two colours. Returns true if the colors are equal, false otherwise.
    794 int vterm_color_is_equal(const VTermColor *a, const VTermColor *b)
    795 {
    796  // First make sure that the two colours are of the same type (RGB/Indexed)
    797  if (a->type != b->type) {
    798    return false;
    799  }
    800 
    801  // Depending on the type inspect the corresponding members
    802  if (VTERM_COLOR_IS_INDEXED(a)) {
    803    return a->indexed.idx == b->indexed.idx;
    804  } else if (VTERM_COLOR_IS_RGB(a)) {
    805    return (a->rgb.red == b->rgb.red)
    806           && (a->rgb.green == b->rgb.green)
    807           && (a->rgb.blue == b->rgb.blue);
    808  }
    809 
    810  return 0;
    811 }