neovim

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

screen.c (31712B)


      1 #include <stdio.h>
      2 #include <string.h>
      3 
      4 #include "nvim/grid.h"
      5 #include "nvim/mbyte.h"
      6 #include "nvim/tui/termkey/termkey.h"
      7 #include "nvim/vterm/pen.h"
      8 #include "nvim/vterm/screen.h"
      9 #include "nvim/vterm/state.h"
     10 #include "nvim/vterm/vterm.h"
     11 #include "nvim/vterm/vterm_defs.h"
     12 #include "nvim/vterm/vterm_internal_defs.h"
     13 
     14 #include "vterm/screen.c.generated.h"
     15 
     16 #define UNICODE_SPACE 0x20
     17 #define UNICODE_LINEFEED 0x0a
     18 
     19 #undef DEBUG_REFLOW
     20 
     21 static inline void clearcell(const VTermScreen *screen, ScreenCell *cell)
     22 {
     23  cell->schar = 0;
     24  cell->pen = screen->pen;
     25 }
     26 
     27 ScreenCell *getcell(const VTermScreen *screen, int row, int col)
     28 {
     29  if (row < 0 || row >= screen->rows) {
     30    return NULL;
     31  }
     32  if (col < 0 || col >= screen->cols) {
     33    return NULL;
     34  }
     35  return screen->buffer + (screen->cols * row) + col;
     36 }
     37 
     38 static ScreenCell *alloc_buffer(VTermScreen *screen, int rows, int cols)
     39 {
     40  ScreenCell *new_buffer = vterm_allocator_malloc(screen->vt,
     41                                                  sizeof(ScreenCell) * (size_t)rows * (size_t)cols);
     42 
     43  for (int row = 0; row < rows; row++) {
     44    for (int col = 0; col < cols; col++) {
     45      clearcell(screen, &new_buffer[row * cols + col]);
     46    }
     47  }
     48 
     49  return new_buffer;
     50 }
     51 
     52 static void damagerect(VTermScreen *screen, VTermRect rect)
     53 {
     54  VTermRect emit;
     55 
     56  switch (screen->damage_merge) {
     57  case VTERM_DAMAGE_CELL:
     58    // Always emit damage event
     59    emit = rect;
     60    break;
     61 
     62  case VTERM_DAMAGE_ROW:
     63    // Emit damage longer than one row. Try to merge with existing damage in the same row
     64    if (rect.end_row > rect.start_row + 1) {
     65      // Bigger than 1 line - flush existing, emit this
     66      vterm_screen_flush_damage(screen);
     67      emit = rect;
     68    } else if (screen->damaged.start_row == -1) {
     69      // None stored yet
     70      screen->damaged = rect;
     71      return;
     72    } else if (rect.start_row == screen->damaged.start_row) {
     73      // Merge with the stored line
     74      if (screen->damaged.start_col > rect.start_col) {
     75        screen->damaged.start_col = rect.start_col;
     76      }
     77      if (screen->damaged.end_col < rect.end_col) {
     78        screen->damaged.end_col = rect.end_col;
     79      }
     80      return;
     81    } else {
     82      // Emit the currently stored line, store a new one
     83      emit = screen->damaged;
     84      screen->damaged = rect;
     85    }
     86    break;
     87 
     88  case VTERM_DAMAGE_SCREEN:
     89  case VTERM_DAMAGE_SCROLL:
     90    // Never emit damage event
     91    if (screen->damaged.start_row == -1) {
     92      screen->damaged = rect;
     93    } else {
     94      rect_expand(&screen->damaged, &rect);
     95    }
     96    return;
     97 
     98  default:
     99    DEBUG_LOG("TODO: Maybe merge damage for level %d\n", screen->damage_merge);
    100    return;
    101  }
    102 
    103  if (screen->callbacks && screen->callbacks->damage) {
    104    (*screen->callbacks->damage)(emit, screen->cbdata);
    105  }
    106 }
    107 
    108 static void damagescreen(VTermScreen *screen)
    109 {
    110  VTermRect rect = {
    111    .start_row = 0,
    112    .end_row = screen->rows,
    113    .start_col = 0,
    114    .end_col = screen->cols,
    115  };
    116 
    117  damagerect(screen, rect);
    118 }
    119 
    120 static int putglyph(VTermGlyphInfo *info, VTermPos pos, void *user)
    121 {
    122  VTermScreen *screen = user;
    123  ScreenCell *cell = getcell(screen, pos.row, pos.col);
    124 
    125  if (!cell) {
    126    return 0;
    127  }
    128 
    129  cell->schar = info->schar;
    130  if (info->schar != 0) {
    131    cell->pen = screen->pen;
    132  }
    133 
    134  for (int col = 1; col < info->width; col++) {
    135    getcell(screen, pos.row, pos.col + col)->schar = (uint32_t)-1;
    136  }
    137 
    138  VTermRect rect = {
    139    .start_row = pos.row,
    140    .end_row = pos.row + 1,
    141    .start_col = pos.col,
    142    .end_col = pos.col + info->width,
    143  };
    144 
    145  cell->pen.protected_cell = info->protected_cell;
    146  cell->pen.dwl = info->dwl;
    147  cell->pen.dhl = info->dhl;
    148 
    149  damagerect(screen, rect);
    150 
    151  return 1;
    152 }
    153 
    154 static void sb_pushline_from_row(VTermScreen *screen, int row)
    155 {
    156  VTermPos pos = { .row = row };
    157  for (pos.col = 0; pos.col < screen->cols; pos.col++) {
    158    vterm_screen_get_cell(screen, pos, screen->sb_buffer + pos.col);
    159  }
    160 
    161  (screen->callbacks->sb_pushline)(screen->cols, screen->sb_buffer, screen->cbdata);
    162 }
    163 
    164 static int moverect_internal(VTermRect dest, VTermRect src, void *user)
    165 {
    166  VTermScreen *screen = user;
    167 
    168  if (screen->callbacks && screen->callbacks->sb_pushline
    169      && dest.start_row == 0 && dest.start_col == 0           // starts top-left corner
    170      && dest.end_col == screen->cols                         // full width
    171      && screen->buffer == screen->buffers[BUFIDX_PRIMARY]) {  // not altscreen
    172    for (int row = 0; row < src.start_row; row++) {
    173      sb_pushline_from_row(screen, row);
    174    }
    175  }
    176 
    177  int cols = src.end_col - src.start_col;
    178  int downward = src.start_row - dest.start_row;
    179 
    180  int init_row, test_row, inc_row;
    181  if (downward < 0) {
    182    init_row = dest.end_row - 1;
    183    test_row = dest.start_row - 1;
    184    inc_row = -1;
    185  } else {
    186    init_row = dest.start_row;
    187    test_row = dest.end_row;
    188    inc_row = +1;
    189  }
    190 
    191  for (int row = init_row; row != test_row; row += inc_row) {
    192    memmove(getcell(screen, row, dest.start_col),
    193            getcell(screen, row + downward, src.start_col),
    194            (size_t)cols * sizeof(ScreenCell));
    195  }
    196 
    197  return 1;
    198 }
    199 
    200 static int moverect_user(VTermRect dest, VTermRect src, void *user)
    201 {
    202  VTermScreen *screen = user;
    203 
    204  if (screen->callbacks && screen->callbacks->moverect) {
    205    if (screen->damage_merge != VTERM_DAMAGE_SCROLL) {
    206      // Avoid an infinite loop
    207      vterm_screen_flush_damage(screen);
    208    }
    209 
    210    if ((*screen->callbacks->moverect)(dest, src, screen->cbdata)) {
    211      return 1;
    212    }
    213  }
    214 
    215  damagerect(screen, dest);
    216 
    217  return 1;
    218 }
    219 
    220 static int erase_internal(VTermRect rect, int selective, void *user)
    221 {
    222  VTermScreen *screen = user;
    223 
    224  for (int row = rect.start_row; row < screen->state->rows && row < rect.end_row; row++) {
    225    const VTermLineInfo *info = vterm_state_get_lineinfo(screen->state, row);
    226 
    227    for (int col = rect.start_col; col < rect.end_col; col++) {
    228      ScreenCell *cell = getcell(screen, row, col);
    229 
    230      if (selective && cell->pen.protected_cell) {
    231        continue;
    232      }
    233 
    234      cell->schar = 0;
    235      cell->pen = (ScreenPen){
    236        // Only copy .fg and .bg; leave things like rv in reset state
    237        .fg = screen->pen.fg,
    238        .bg = screen->pen.bg,
    239      };
    240      cell->pen.dwl = info->doublewidth;
    241      cell->pen.dhl = info->doubleheight;
    242    }
    243  }
    244 
    245  return 1;
    246 }
    247 
    248 static int erase_user(VTermRect rect, int selective, void *user)
    249 {
    250  VTermScreen *screen = user;
    251 
    252  damagerect(screen, rect);
    253 
    254  return 1;
    255 }
    256 
    257 static int erase(VTermRect rect, int selective, void *user)
    258 {
    259  erase_internal(rect, selective, user);
    260  return erase_user(rect, 0, user);
    261 }
    262 
    263 static int scrollrect(VTermRect rect, int downward, int rightward, void *user)
    264 {
    265  VTermScreen *screen = user;
    266 
    267  if (screen->damage_merge != VTERM_DAMAGE_SCROLL) {
    268    vterm_scroll_rect(rect, downward, rightward,
    269                      moverect_internal, erase_internal, screen);
    270 
    271    vterm_screen_flush_damage(screen);
    272 
    273    vterm_scroll_rect(rect, downward, rightward,
    274                      moverect_user, erase_user, screen);
    275 
    276    return 1;
    277  }
    278 
    279  if (screen->damaged.start_row != -1
    280      && !rect_intersects(&rect, &screen->damaged)) {
    281    vterm_screen_flush_damage(screen);
    282  }
    283 
    284  if (screen->pending_scrollrect.start_row == -1) {
    285    screen->pending_scrollrect = rect;
    286    screen->pending_scroll_downward = downward;
    287    screen->pending_scroll_rightward = rightward;
    288  } else if (rect_equal(&screen->pending_scrollrect, &rect)
    289             && ((screen->pending_scroll_downward == 0 && downward == 0)
    290                 || (screen->pending_scroll_rightward == 0 && rightward == 0))) {
    291    screen->pending_scroll_downward += downward;
    292    screen->pending_scroll_rightward += rightward;
    293  } else {
    294    vterm_screen_flush_damage(screen);
    295 
    296    screen->pending_scrollrect = rect;
    297    screen->pending_scroll_downward = downward;
    298    screen->pending_scroll_rightward = rightward;
    299  }
    300 
    301  vterm_scroll_rect(rect, downward, rightward,
    302                    moverect_internal, erase_internal, screen);
    303 
    304  if (screen->damaged.start_row == -1) {
    305    return 1;
    306  }
    307 
    308  if (rect_contains(&rect, &screen->damaged)) {
    309    // Scroll region entirely contains the damage; just move it
    310    vterm_rect_move(&screen->damaged, -downward, -rightward);
    311    rect_clip(&screen->damaged, &rect);
    312  }
    313  // There are a number of possible cases here, but lets restrict this to only the common case where
    314  // we might actually gain some performance by optimising it. Namely, a vertical scroll that neatly
    315  // cuts the damage region in half.
    316  else if (rect.start_col <= screen->damaged.start_col
    317           && rect.end_col >= screen->damaged.end_col
    318           && rightward == 0) {
    319    if (screen->damaged.start_row >= rect.start_row
    320        && screen->damaged.start_row < rect.end_row) {
    321      screen->damaged.start_row -= downward;
    322      if (screen->damaged.start_row < rect.start_row) {
    323        screen->damaged.start_row = rect.start_row;
    324      }
    325      if (screen->damaged.start_row > rect.end_row) {
    326        screen->damaged.start_row = rect.end_row;
    327      }
    328    }
    329    if (screen->damaged.end_row >= rect.start_row
    330        && screen->damaged.end_row < rect.end_row) {
    331      screen->damaged.end_row -= downward;
    332      if (screen->damaged.end_row < rect.start_row) {
    333        screen->damaged.end_row = rect.start_row;
    334      }
    335      if (screen->damaged.end_row > rect.end_row) {
    336        screen->damaged.end_row = rect.end_row;
    337      }
    338    }
    339  } else {
    340    DEBUG_LOG("TODO: Just flush and redo damaged=" STRFrect " rect=" STRFrect "\n",
    341              ARGSrect(screen->damaged), ARGSrect(rect));
    342  }
    343 
    344  return 1;
    345 }
    346 
    347 static int movecursor(VTermPos pos, VTermPos oldpos, int visible, void *user)
    348 {
    349  VTermScreen *screen = user;
    350 
    351  if (screen->callbacks && screen->callbacks->movecursor) {
    352    return (*screen->callbacks->movecursor)(pos, oldpos, visible, screen->cbdata);
    353  }
    354 
    355  return 0;
    356 }
    357 
    358 static int setpenattr(VTermAttr attr, VTermValue *val, void *user)
    359 {
    360  VTermScreen *screen = user;
    361 
    362  switch (attr) {
    363  case VTERM_ATTR_BOLD:
    364    screen->pen.bold = (unsigned)val->boolean;
    365    return 1;
    366  case VTERM_ATTR_UNDERLINE:
    367    screen->pen.underline = (unsigned)val->number;
    368    return 1;
    369  case VTERM_ATTR_ITALIC:
    370    screen->pen.italic = (unsigned)val->boolean;
    371    return 1;
    372  case VTERM_ATTR_BLINK:
    373    screen->pen.blink = (unsigned)val->boolean;
    374    return 1;
    375  case VTERM_ATTR_REVERSE:
    376    screen->pen.reverse = (unsigned)val->boolean;
    377    return 1;
    378  case VTERM_ATTR_CONCEAL:
    379    screen->pen.conceal = (unsigned)val->boolean;
    380    return 1;
    381  case VTERM_ATTR_STRIKE:
    382    screen->pen.strike = (unsigned)val->boolean;
    383    return 1;
    384  case VTERM_ATTR_FONT:
    385    screen->pen.font = (unsigned)val->number;
    386    return 1;
    387  case VTERM_ATTR_FOREGROUND:
    388    screen->pen.fg = val->color;
    389    return 1;
    390  case VTERM_ATTR_BACKGROUND:
    391    screen->pen.bg = val->color;
    392    return 1;
    393  case VTERM_ATTR_SMALL:
    394    screen->pen.small = (unsigned)val->boolean;
    395    return 1;
    396  case VTERM_ATTR_BASELINE:
    397    screen->pen.baseline = (unsigned)val->number;
    398    return 1;
    399  case VTERM_ATTR_URI:
    400    screen->pen.uri = val->number;
    401    return 1;
    402  case VTERM_ATTR_DIM:
    403    screen->pen.dim = (unsigned)val->boolean;
    404    return 1;
    405  case VTERM_ATTR_OVERLINE:
    406    screen->pen.overline = (unsigned)val->boolean;
    407    return 1;
    408 
    409  case VTERM_N_ATTRS:
    410    return 0;
    411  }
    412 
    413  return 0;
    414 }
    415 
    416 static int settermprop(VTermProp prop, VTermValue *val, void *user)
    417 {
    418  VTermScreen *screen = user;
    419 
    420  switch (prop) {
    421  case VTERM_PROP_ALTSCREEN:
    422    if (val->boolean && !screen->buffers[BUFIDX_ALTSCREEN]) {
    423      return 0;
    424    }
    425 
    426    screen->buffer =
    427      val->boolean ? screen->buffers[BUFIDX_ALTSCREEN] : screen->buffers[BUFIDX_PRIMARY];
    428    // only send a damage event on disable; because during enable there's an erase that sends a
    429    // damage anyway
    430    if (!val->boolean) {
    431      damagescreen(screen);
    432    }
    433    break;
    434  case VTERM_PROP_REVERSE:
    435    screen->global_reverse = (unsigned)val->boolean;
    436    damagescreen(screen);
    437    break;
    438  default:
    439    ;  // ignore
    440  }
    441 
    442  if (screen->callbacks && screen->callbacks->settermprop) {
    443    return (*screen->callbacks->settermprop)(prop, val, screen->cbdata);
    444  }
    445 
    446  return 1;
    447 }
    448 
    449 static int bell(void *user)
    450 {
    451  VTermScreen *screen = user;
    452 
    453  if (screen->callbacks && screen->callbacks->bell) {
    454    return (*screen->callbacks->bell)(screen->cbdata);
    455  }
    456 
    457  return 0;
    458 }
    459 
    460 /// How many cells are non-blank Returns the position of the first blank cell in the trailing blank
    461 /// end
    462 static int line_popcount(ScreenCell *buffer, int row, int rows, int cols)
    463 {
    464  int col = cols - 1;
    465  while (col >= 0 && buffer[row * cols + col].schar == 0) {
    466    col--;
    467  }
    468  return col + 1;
    469 }
    470 
    471 static void resize_buffer(VTermScreen *screen, int bufidx, int new_rows, int new_cols, bool active,
    472                          VTermStateFields *statefields)
    473 {
    474  int old_rows = screen->rows;
    475  int old_cols = screen->cols;
    476 
    477  ScreenCell *old_buffer = screen->buffers[bufidx];
    478  VTermLineInfo *old_lineinfo = statefields->lineinfos[bufidx];
    479 
    480  ScreenCell *new_buffer = vterm_allocator_malloc(screen->vt,
    481                                                  sizeof(ScreenCell) * (size_t)new_rows *
    482                                                  (size_t)new_cols);
    483  VTermLineInfo *new_lineinfo = vterm_allocator_malloc(screen->vt,
    484                                                       sizeof(new_lineinfo[0]) * (size_t)new_rows);
    485 
    486  int old_row = old_rows - 1;
    487  int new_row = new_rows - 1;
    488 
    489  VTermPos old_cursor = statefields->pos;
    490  VTermPos new_cursor = { -1, -1 };
    491 
    492 #ifdef DEBUG_REFLOW
    493  fprintf(stderr, "Resizing from %dx%d to %dx%d; cursor was at (%d,%d)\n",
    494          old_cols, old_rows, new_cols, new_rows, old_cursor.col, old_cursor.row);
    495 #endif
    496 
    497  // Keep track of the final row that is known to be blank, so we know what spare space we have for
    498  // scrolling into
    499  int final_blank_row = new_rows;
    500 
    501  while (old_row >= 0) {
    502    int old_row_end = old_row;
    503    // TODO(vterm): Stop if dwl or dhl
    504    while (screen->reflow && old_lineinfo && old_row > 0 && old_lineinfo[old_row].continuation) {
    505      old_row--;
    506    }
    507    int old_row_start = old_row;
    508 
    509    int width = 0;
    510    for (int row = old_row_start; row <= old_row_end; row++) {
    511      if (screen->reflow && row < (old_rows - 1) && old_lineinfo[row + 1].continuation) {
    512        width += old_cols;
    513      } else {
    514        width += line_popcount(old_buffer, row, old_rows, old_cols);
    515      }
    516    }
    517 
    518    if (final_blank_row == (new_row + 1) && width == 0) {
    519      final_blank_row = new_row;
    520    }
    521 
    522    int new_height = screen->reflow
    523                     ? width ? (width + new_cols - 1) / new_cols : 1
    524                     : 1;
    525 
    526    int new_row_end = new_row;
    527    int new_row_start = new_row - new_height + 1;
    528 
    529    old_row = old_row_start;
    530    int old_col = 0;
    531 
    532    int spare_rows = new_rows - final_blank_row;
    533 
    534    if (new_row_start < 0    // we'd fall off the top
    535        && spare_rows >= 0    // we actually have spare rows
    536        && (!active || new_cursor.row == -1 || (new_cursor.row - new_row_start) < new_rows)) {
    537      // Attempt to scroll content down into the blank rows at the bottom to make it fit
    538      int downwards = -new_row_start;
    539      if (downwards > spare_rows) {
    540        downwards = spare_rows;
    541      }
    542      int rowcount = new_rows - downwards;
    543 
    544 #ifdef DEBUG_REFLOW
    545      fprintf(stderr, "  scroll %d rows +%d downwards\n", rowcount, downwards);
    546 #endif
    547 
    548      memmove(&new_buffer[downwards * new_cols], &new_buffer[0],
    549              (size_t)rowcount * (size_t)new_cols * sizeof(ScreenCell));
    550      memmove(&new_lineinfo[downwards],          &new_lineinfo[0],
    551              (size_t)rowcount * sizeof(new_lineinfo[0]));
    552 
    553      new_row += downwards;
    554      new_row_start += downwards;
    555      new_row_end += downwards;
    556 
    557      if (new_cursor.row >= 0) {
    558        new_cursor.row += downwards;
    559      }
    560 
    561      final_blank_row += downwards;
    562    }
    563 
    564 #ifdef DEBUG_REFLOW
    565    fprintf(stderr, "  rows [%d..%d] <- [%d..%d] width=%d\n",
    566            new_row_start, new_row_end, old_row_start, old_row_end, width);
    567 #endif
    568 
    569    if (new_row_start < 0) {
    570      if (old_row_start <= old_cursor.row && old_cursor.row <= old_row_end) {
    571        new_cursor.row = 0;
    572        new_cursor.col = old_cursor.col;
    573        if (new_cursor.col >= new_cols) {
    574          new_cursor.col = new_cols - 1;
    575        }
    576      }
    577      break;
    578    }
    579 
    580    for (new_row = new_row_start, old_row = old_row_start; new_row <= new_row_end; new_row++) {
    581      int count = width >= new_cols ? new_cols : width;
    582      width -= count;
    583 
    584      int new_col = 0;
    585 
    586      while (count) {
    587        // TODO(vterm): This could surely be done a lot faster by memcpy()'ing the entire range
    588        new_buffer[new_row * new_cols + new_col] = old_buffer[old_row * old_cols + old_col];
    589 
    590        if (old_cursor.row == old_row && old_cursor.col == old_col) {
    591          new_cursor.row = new_row, new_cursor.col = new_col;
    592        }
    593 
    594        old_col++;
    595        if (old_col == old_cols) {
    596          old_row++;
    597 
    598          if (!screen->reflow) {
    599            new_col++;
    600            break;
    601          }
    602          old_col = 0;
    603        }
    604 
    605        new_col++;
    606        count--;
    607      }
    608 
    609      if (old_cursor.row == old_row && old_cursor.col >= old_col) {
    610        new_cursor.row = new_row, new_cursor.col = (old_cursor.col - old_col + new_col);
    611        if (new_cursor.col >= new_cols) {
    612          new_cursor.col = new_cols - 1;
    613        }
    614      }
    615 
    616      while (new_col < new_cols) {
    617        clearcell(screen, &new_buffer[new_row * new_cols + new_col]);
    618        new_col++;
    619      }
    620 
    621      new_lineinfo[new_row].continuation = (new_row > new_row_start);
    622    }
    623 
    624    old_row = old_row_start - 1;
    625    new_row = new_row_start - 1;
    626  }
    627 
    628  if (old_cursor.row <= old_row) {
    629    // cursor would have moved entirely off the top of the screen; lets just bring it within range
    630    new_cursor.row = 0, new_cursor.col = old_cursor.col;
    631    if (new_cursor.col >= new_cols) {
    632      new_cursor.col = new_cols - 1;
    633    }
    634  }
    635 
    636  // We really expect the cursor position to be set by now
    637  if (active && (new_cursor.row == -1 || new_cursor.col == -1)) {
    638    fprintf(stderr, "screen_resize failed to update cursor position\n");
    639    abort();
    640  }
    641 
    642  if (old_row >= 0 && bufidx == BUFIDX_PRIMARY) {
    643    // Push spare lines to scrollback buffer
    644    if (screen->callbacks && screen->callbacks->sb_pushline) {
    645      for (int row = 0; row <= old_row; row++) {
    646        sb_pushline_from_row(screen, row);
    647      }
    648    }
    649    if (active) {
    650      statefields->pos.row -= (old_row + 1);
    651    }
    652  }
    653  if (new_row >= 0 && bufidx == BUFIDX_PRIMARY
    654      && screen->callbacks && screen->callbacks->sb_popline) {
    655    // Try to backfill rows by popping scrollback buffer
    656    while (new_row >= 0) {
    657      if (!(screen->callbacks->sb_popline(old_cols, screen->sb_buffer, screen->cbdata))) {
    658        break;
    659      }
    660 
    661      VTermPos pos = { .row = new_row };
    662      for (pos.col = 0; pos.col < old_cols && pos.col < new_cols;
    663           pos.col += screen->sb_buffer[pos.col].width) {
    664        VTermScreenCell *src = &screen->sb_buffer[pos.col];
    665        ScreenCell *dst = &new_buffer[pos.row * new_cols + pos.col];
    666 
    667        dst->schar = src->schar;
    668 
    669        dst->pen.bold = src->attrs.bold;
    670        dst->pen.underline = src->attrs.underline;
    671        dst->pen.italic = src->attrs.italic;
    672        dst->pen.blink = src->attrs.blink;
    673        dst->pen.reverse = src->attrs.reverse ^ screen->global_reverse;
    674        dst->pen.conceal = src->attrs.conceal;
    675        dst->pen.strike = src->attrs.strike;
    676        dst->pen.font = src->attrs.font;
    677        dst->pen.small = src->attrs.small;
    678        dst->pen.baseline = src->attrs.baseline;
    679        dst->pen.dim = src->attrs.dim;
    680        dst->pen.overline = src->attrs.overline;
    681 
    682        dst->pen.fg = src->fg;
    683        dst->pen.bg = src->bg;
    684 
    685        dst->pen.uri = src->uri;
    686 
    687        if (src->width == 2 && pos.col < (new_cols - 1)) {
    688          (dst + 1)->schar = (uint32_t)-1;
    689        }
    690      }
    691      for (; pos.col < new_cols; pos.col++) {
    692        clearcell(screen, &new_buffer[pos.row * new_cols + pos.col]);
    693      }
    694      new_row--;
    695 
    696      if (active) {
    697        statefields->pos.row++;
    698      }
    699    }
    700  }
    701  if (new_row >= 0) {
    702    // Scroll new rows back up to the top and fill in blanks at the bottom
    703    int moverows = new_rows - new_row - 1;
    704    memmove(&new_buffer[0], &new_buffer[(new_row + 1) * new_cols],
    705            (size_t)moverows * (size_t)new_cols * sizeof(ScreenCell));
    706    memmove(&new_lineinfo[0], &new_lineinfo[new_row + 1],
    707            (size_t)moverows * sizeof(new_lineinfo[0]));
    708 
    709    new_cursor.row -= (new_row + 1);
    710 
    711    for (new_row = moverows; new_row < new_rows; new_row++) {
    712      for (int col = 0; col < new_cols; col++) {
    713        clearcell(screen, &new_buffer[new_row * new_cols + col]);
    714      }
    715      new_lineinfo[new_row] = (VTermLineInfo){ 0 };
    716    }
    717  }
    718 
    719  vterm_allocator_free(screen->vt, old_buffer);
    720  screen->buffers[bufidx] = new_buffer;
    721 
    722  vterm_allocator_free(screen->vt, old_lineinfo);
    723  statefields->lineinfos[bufidx] = new_lineinfo;
    724 
    725  if (active) {
    726    statefields->pos = new_cursor;
    727  }
    728 }
    729 
    730 static int resize(int new_rows, int new_cols, VTermStateFields *fields, void *user)
    731 {
    732  VTermScreen *screen = user;
    733 
    734  int altscreen_active = (screen->buffers[BUFIDX_ALTSCREEN]
    735                          && screen->buffer == screen->buffers[BUFIDX_ALTSCREEN]);
    736 
    737  int old_rows = screen->rows;
    738  int old_cols = screen->cols;
    739 
    740  if (new_cols > old_cols) {
    741    // Ensure that ->sb_buffer is large enough for a new or and old row
    742    if (screen->sb_buffer) {
    743      vterm_allocator_free(screen->vt, screen->sb_buffer);
    744    }
    745 
    746    screen->sb_buffer = vterm_allocator_malloc(screen->vt,
    747                                               sizeof(VTermScreenCell) * (size_t)new_cols);
    748  }
    749 
    750  resize_buffer(screen, 0, new_rows, new_cols, !altscreen_active, fields);
    751  if (screen->buffers[BUFIDX_ALTSCREEN]) {
    752    resize_buffer(screen, 1, new_rows, new_cols, altscreen_active, fields);
    753  } else if (new_rows != old_rows) {
    754    // We don't need a full resize of the altscreen because it isn't enabled but we should at least
    755    // keep the lineinfo the right size
    756    vterm_allocator_free(screen->vt, fields->lineinfos[BUFIDX_ALTSCREEN]);
    757 
    758    VTermLineInfo *new_lineinfo = vterm_allocator_malloc(screen->vt,
    759                                                         sizeof(new_lineinfo[0]) *
    760                                                         (size_t)new_rows);
    761    for (int row = 0; row < new_rows; row++) {
    762      new_lineinfo[row] = (VTermLineInfo){ 0 };
    763    }
    764 
    765    fields->lineinfos[BUFIDX_ALTSCREEN] = new_lineinfo;
    766  }
    767 
    768  screen->buffer =
    769    altscreen_active ? screen->buffers[BUFIDX_ALTSCREEN] : screen->buffers[BUFIDX_PRIMARY];
    770 
    771  screen->rows = new_rows;
    772  screen->cols = new_cols;
    773 
    774  if (new_cols <= old_cols) {
    775    if (screen->sb_buffer) {
    776      vterm_allocator_free(screen->vt, screen->sb_buffer);
    777    }
    778 
    779    screen->sb_buffer = vterm_allocator_malloc(screen->vt,
    780                                               sizeof(VTermScreenCell) * (size_t)new_cols);
    781  }
    782 
    783  // TODO(vterm): Maaaaybe we can optimise this if there's no reflow happening
    784  damagescreen(screen);
    785 
    786  if (screen->callbacks && screen->callbacks->resize) {
    787    return (*screen->callbacks->resize)(new_rows, new_cols, screen->cbdata);
    788  }
    789 
    790  return 1;
    791 }
    792 
    793 static int theme(bool *dark, void *user)
    794 {
    795  VTermScreen *screen = user;
    796 
    797  if (screen->callbacks && screen->callbacks->theme) {
    798    return (*screen->callbacks->theme)(dark, screen->cbdata);
    799  }
    800 
    801  return 1;
    802 }
    803 
    804 static int setlineinfo(int row, const VTermLineInfo *newinfo, const VTermLineInfo *oldinfo,
    805                       void *user)
    806 {
    807  VTermScreen *screen = user;
    808 
    809  if (newinfo->doublewidth != oldinfo->doublewidth
    810      || newinfo->doubleheight != oldinfo->doubleheight) {
    811    for (int col = 0; col < screen->cols; col++) {
    812      ScreenCell *cell = getcell(screen, row, col);
    813      cell->pen.dwl = newinfo->doublewidth;
    814      cell->pen.dhl = newinfo->doubleheight;
    815    }
    816 
    817    VTermRect rect = {
    818      .start_row = row,
    819      .end_row = row + 1,
    820      .start_col = 0,
    821      .end_col = newinfo->doublewidth ? screen->cols / 2 : screen->cols,
    822    };
    823    damagerect(screen, rect);
    824 
    825    if (newinfo->doublewidth) {
    826      rect.start_col = screen->cols / 2;
    827      rect.end_col = screen->cols;
    828 
    829      erase_internal(rect, 0, user);
    830    }
    831  }
    832 
    833  return 1;
    834 }
    835 
    836 static int sb_clear(void *user)
    837 {
    838  VTermScreen *screen = user;
    839 
    840  if (screen->callbacks && screen->callbacks->sb_clear) {
    841    if ((*screen->callbacks->sb_clear)(screen->cbdata)) {
    842      return 1;
    843    }
    844  }
    845 
    846  return 0;
    847 }
    848 
    849 static VTermStateCallbacks state_cbs = {
    850  .putglyph = &putglyph,
    851  .movecursor = &movecursor,
    852  .scrollrect = &scrollrect,
    853  .erase = &erase,
    854  .setpenattr = &setpenattr,
    855  .settermprop = &settermprop,
    856  .bell = &bell,
    857  .resize = &resize,
    858  .theme = &theme,
    859  .setlineinfo = &setlineinfo,
    860  .sb_clear = &sb_clear,
    861 };
    862 
    863 static VTermScreen *screen_new(VTerm *vt)
    864 {
    865  VTermState *state = vterm_obtain_state(vt);
    866  if (!state) {
    867    return NULL;
    868  }
    869 
    870  VTermScreen *screen = vterm_allocator_malloc(vt, sizeof(VTermScreen));
    871  int rows, cols;
    872 
    873  vterm_get_size(vt, &rows, &cols);
    874 
    875  screen->vt = vt;
    876  screen->state = state;
    877 
    878  screen->damage_merge = VTERM_DAMAGE_CELL;
    879  screen->damaged.start_row = -1;
    880  screen->pending_scrollrect.start_row = -1;
    881 
    882  screen->rows = rows;
    883  screen->cols = cols;
    884 
    885  screen->global_reverse = false;
    886  screen->reflow = false;
    887 
    888  screen->callbacks = NULL;
    889  screen->cbdata = NULL;
    890 
    891  screen->buffers[BUFIDX_PRIMARY] = alloc_buffer(screen, rows, cols);
    892 
    893  screen->buffer = screen->buffers[BUFIDX_PRIMARY];
    894 
    895  screen->sb_buffer = vterm_allocator_malloc(screen->vt, sizeof(VTermScreenCell) * (size_t)cols);
    896 
    897  vterm_state_set_callbacks(screen->state, &state_cbs, screen);
    898 
    899  return screen;
    900 }
    901 
    902 void vterm_screen_free(VTermScreen *screen)
    903 {
    904  vterm_allocator_free(screen->vt, screen->buffers[BUFIDX_PRIMARY]);
    905  if (screen->buffers[BUFIDX_ALTSCREEN]) {
    906    vterm_allocator_free(screen->vt, screen->buffers[BUFIDX_ALTSCREEN]);
    907  }
    908 
    909  vterm_allocator_free(screen->vt, screen->sb_buffer);
    910 
    911  vterm_allocator_free(screen->vt, screen);
    912 }
    913 
    914 void vterm_screen_reset(VTermScreen *screen, int hard)
    915 {
    916  screen->damaged.start_row = -1;
    917  screen->pending_scrollrect.start_row = -1;
    918  vterm_state_reset(screen->state, hard);
    919  vterm_screen_flush_damage(screen);
    920 }
    921 
    922 // Copy internal to external representation of a screen cell
    923 int vterm_screen_get_cell(const VTermScreen *screen, VTermPos pos, VTermScreenCell *cell)
    924 {
    925  ScreenCell *intcell = getcell(screen, pos.row, pos.col);
    926  if (!intcell) {
    927    return 0;
    928  }
    929 
    930  cell->schar = (intcell->schar == (uint32_t)-1) ? 0 : intcell->schar;
    931 
    932  cell->attrs.bold = intcell->pen.bold;
    933  cell->attrs.underline = intcell->pen.underline;
    934  cell->attrs.italic = intcell->pen.italic;
    935  cell->attrs.blink = intcell->pen.blink;
    936  cell->attrs.reverse = intcell->pen.reverse ^ screen->global_reverse;
    937  cell->attrs.conceal = intcell->pen.conceal;
    938  cell->attrs.strike = intcell->pen.strike;
    939  cell->attrs.font = intcell->pen.font;
    940  cell->attrs.small = intcell->pen.small;
    941  cell->attrs.baseline = intcell->pen.baseline;
    942  cell->attrs.dim = intcell->pen.dim;
    943  cell->attrs.overline = intcell->pen.overline;
    944 
    945  cell->attrs.dwl = intcell->pen.dwl;
    946  cell->attrs.dhl = intcell->pen.dhl;
    947 
    948  cell->fg = intcell->pen.fg;
    949  cell->bg = intcell->pen.bg;
    950 
    951  cell->uri = intcell->pen.uri;
    952 
    953  if (pos.col < (screen->cols - 1)
    954      && getcell(screen, pos.row, pos.col + 1)->schar == (uint32_t)-1) {
    955    cell->width = 2;
    956  } else {
    957    cell->width = 1;
    958  }
    959 
    960  return 1;
    961 }
    962 
    963 VTermScreen *vterm_obtain_screen(VTerm *vt)
    964 {
    965  if (vt->screen) {
    966    return vt->screen;
    967  }
    968 
    969  VTermScreen *screen = screen_new(vt);
    970  vt->screen = screen;
    971 
    972  return screen;
    973 }
    974 
    975 void vterm_screen_enable_reflow(VTermScreen *screen, bool reflow)
    976 {
    977  screen->reflow = reflow;
    978 }
    979 
    980 #undef vterm_screen_set_reflow
    981 void vterm_screen_set_reflow(VTermScreen *screen, bool reflow)
    982 {
    983  vterm_screen_enable_reflow(screen, reflow);
    984 }
    985 
    986 void vterm_screen_enable_altscreen(VTermScreen *screen, int altscreen)
    987 {
    988  if (!screen->buffers[BUFIDX_ALTSCREEN] && altscreen) {
    989    int rows, cols;
    990    vterm_get_size(screen->vt, &rows, &cols);
    991 
    992    screen->buffers[BUFIDX_ALTSCREEN] = alloc_buffer(screen, rows, cols);
    993  }
    994 }
    995 
    996 void vterm_screen_set_callbacks(VTermScreen *screen, const VTermScreenCallbacks *callbacks,
    997                                void *user)
    998 {
    999  screen->callbacks = callbacks;
   1000  screen->cbdata = user;
   1001 }
   1002 
   1003 void vterm_screen_set_unrecognised_fallbacks(VTermScreen *screen,
   1004                                             const VTermStateFallbacks *fallbacks, void *user)
   1005 {
   1006  vterm_state_set_unrecognised_fallbacks(screen->state, fallbacks, user);
   1007 }
   1008 
   1009 void vterm_screen_flush_damage(VTermScreen *screen)
   1010 {
   1011  if (screen->pending_scrollrect.start_row != -1) {
   1012    vterm_scroll_rect(screen->pending_scrollrect, screen->pending_scroll_downward,
   1013                      screen->pending_scroll_rightward,
   1014                      moverect_user, erase_user, screen);
   1015 
   1016    screen->pending_scrollrect.start_row = -1;
   1017  }
   1018 
   1019  if (screen->damaged.start_row != -1) {
   1020    if (screen->callbacks && screen->callbacks->damage) {
   1021      (*screen->callbacks->damage)(screen->damaged, screen->cbdata);
   1022    }
   1023 
   1024    screen->damaged.start_row = -1;
   1025  }
   1026 }
   1027 
   1028 void vterm_screen_set_damage_merge(VTermScreen *screen, VTermDamageSize size)
   1029 {
   1030  vterm_screen_flush_damage(screen);
   1031  screen->damage_merge = size;
   1032 }
   1033 
   1034 /// Same as vterm_state_convert_color_to_rgb(), but takes a `screen` instead of a `state` instance.
   1035 void vterm_screen_convert_color_to_rgb(const VTermScreen *screen, VTermColor *col)
   1036 {
   1037  vterm_state_convert_color_to_rgb(screen->state, col);
   1038 }
   1039 
   1040 // Some utility functions on VTermRect structures
   1041 
   1042 #define STRFrect "(%d,%d-%d,%d)"
   1043 #define ARGSrect(r) (r).start_row, (r).start_col, (r).end_row, (r).end_col
   1044 
   1045 // Expand dst to contain src as well
   1046 void rect_expand(VTermRect *dst, VTermRect *src)
   1047 {
   1048  if (dst->start_row > src->start_row) {
   1049    dst->start_row = src->start_row;
   1050  }
   1051  if (dst->start_col > src->start_col) {
   1052    dst->start_col = src->start_col;
   1053  }
   1054  if (dst->end_row < src->end_row) {
   1055    dst->end_row = src->end_row;
   1056  }
   1057  if (dst->end_col < src->end_col) {
   1058    dst->end_col = src->end_col;
   1059  }
   1060 }
   1061 
   1062 // Clip the dst to ensure it does not step outside of bounds
   1063 void rect_clip(VTermRect *dst, VTermRect *bounds)
   1064 {
   1065  if (dst->start_row < bounds->start_row) {
   1066    dst->start_row = bounds->start_row;
   1067  }
   1068  if (dst->start_col < bounds->start_col) {
   1069    dst->start_col = bounds->start_col;
   1070  }
   1071  if (dst->end_row > bounds->end_row) {
   1072    dst->end_row = bounds->end_row;
   1073  }
   1074  if (dst->end_col > bounds->end_col) {
   1075    dst->end_col = bounds->end_col;
   1076  }
   1077  // Ensure it doesn't end up negatively-sized
   1078  if (dst->end_row < dst->start_row) {
   1079    dst->end_row = dst->start_row;
   1080  }
   1081  if (dst->end_col < dst->start_col) {
   1082    dst->end_col = dst->start_col;
   1083  }
   1084 }
   1085 
   1086 // True if the two rectangles are equal
   1087 int rect_equal(VTermRect *a, VTermRect *b)
   1088 {
   1089  return (a->start_row == b->start_row)
   1090         && (a->start_col == b->start_col)
   1091         && (a->end_row == b->end_row)
   1092         && (a->end_col == b->end_col);
   1093 }
   1094 
   1095 // True if small is contained entirely within big
   1096 int rect_contains(VTermRect *big, VTermRect *small)
   1097 {
   1098  if (small->start_row < big->start_row) {
   1099    return 0;
   1100  }
   1101  if (small->start_col < big->start_col) {
   1102    return 0;
   1103  }
   1104  if (small->end_row > big->end_row) {
   1105    return 0;
   1106  }
   1107  if (small->end_col > big->end_col) {
   1108    return 0;
   1109  }
   1110  return 1;
   1111 }
   1112 
   1113 // True if the rectangles overlap at all
   1114 int rect_intersects(VTermRect *a, VTermRect *b)
   1115 {
   1116  if (a->start_row > b->end_row || b->start_row > a->end_row) {
   1117    return 0;
   1118  }
   1119  if (a->start_col > b->end_col || b->start_col > a->end_col) {
   1120    return 0;
   1121  }
   1122  return 1;
   1123 }