neovim

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

grid.c (38353B)


      1 // Low-level functions to manipulate individual character cells on the
      2 // screen grid.
      3 //
      4 // Most of the routines in this file perform screen (grid) manipulations. The
      5 // given operation is performed physically on the screen. The corresponding
      6 // change is also made to the internal screen image. In this way, the editor
      7 // anticipates the effect of editing changes on the appearance of the screen.
      8 // That way, when we call update_screen() a complete redraw isn't usually
      9 // necessary. Another advantage is that we can keep adding code to anticipate
     10 // screen changes, and in the meantime, everything still works.
     11 //
     12 // The grid_*() functions write to the screen and handle updating grid->lines[].
     13 
     14 #include <assert.h>
     15 #include <limits.h>
     16 #include <stdint.h>
     17 #include <stdlib.h>
     18 #include <string.h>
     19 
     20 #include "nvim/api/private/defs.h"
     21 #include "nvim/arabic.h"
     22 #include "nvim/ascii_defs.h"
     23 #include "nvim/buffer_defs.h"
     24 #include "nvim/decoration.h"
     25 #include "nvim/globals.h"
     26 #include "nvim/grid.h"
     27 #include "nvim/highlight.h"
     28 #include "nvim/log.h"
     29 #include "nvim/map_defs.h"
     30 #include "nvim/mbyte.h"
     31 #include "nvim/memory.h"
     32 #include "nvim/message.h"
     33 #include "nvim/option_vars.h"
     34 #include "nvim/optionstr.h"
     35 #include "nvim/types_defs.h"
     36 #include "nvim/ui.h"
     37 #include "nvim/ui_defs.h"
     38 
     39 #include "grid.c.generated.h"
     40 
     41 // temporary buffer for rendering a single screenline, so it can be
     42 // compared with previous contents to calculate smallest delta.
     43 // Per-cell attributes
     44 static size_t linebuf_size = 0;
     45 
     46 // Used to cache glyphs which doesn't fit an a sizeof(schar_T) length UTF-8 string.
     47 // Then it instead stores an index into glyph_cache.keys[] which is a flat char array.
     48 // The hash part is used by schar_from_buf() to quickly lookup glyphs which already
     49 // has been interned. schar_get() should used to convert a schar_T value
     50 // back to a string buffer.
     51 //
     52 // The maximum byte size of a glyph is MAX_SCHAR_SIZE (including the final NUL).
     53 static Set(glyph) glyph_cache = SET_INIT;
     54 
     55 /// Determine if dedicated window grid should be used or the default_grid
     56 ///
     57 /// If UI did not request multigrid support, draw all windows on the
     58 /// default_grid.
     59 ///
     60 /// NB: this function can only been used with window grids in a context where
     61 /// win_grid_alloc already has been called!
     62 ///
     63 /// If the default_grid is used, adjust window relative positions to global
     64 /// screen positions.
     65 ScreenGrid *grid_adjust(GridView *grid, int *row_off, int *col_off)
     66 {
     67  *row_off += grid->row_offset;
     68  *col_off += grid->col_offset;
     69  return grid->target;
     70 }
     71 
     72 schar_T schar_from_str(const char *str)
     73 {
     74  if (str == NULL) {
     75    return 0;
     76  }
     77  return schar_from_buf(str, strlen(str));
     78 }
     79 
     80 /// @param buf need not be NUL terminated, but may not contain embedded NULs.
     81 ///
     82 /// caller must ensure len < MAX_SCHAR_SIZE (not =, as NUL needs a byte)
     83 schar_T schar_from_buf(const char *buf, size_t len)
     84 {
     85  assert(len < MAX_SCHAR_SIZE);
     86  if (len <= 4) {
     87    schar_T sc = 0;
     88    memcpy((char *)&sc, buf, len);
     89    return sc;
     90  } else {
     91    String str = { .data = (char *)buf, .size = len };
     92 
     93    MHPutStatus status;
     94    uint32_t idx = set_put_idx(glyph, &glyph_cache, str, &status);
     95    assert(idx < 0xFFFFFF);
     96 #ifdef ORDER_BIG_ENDIAN
     97    return idx + ((uint32_t)0xFF << 24);
     98 #else
     99    return 0xFF + (idx << 8);
    100 #endif
    101  }
    102 }
    103 
    104 /// Check if cache is full, and if it is, clear it.
    105 ///
    106 /// This should normally only be called in update_screen()
    107 ///
    108 /// @return true if cache was clered, and all your screen buffers now are hosed
    109 /// and you need to use UPD_CLEAR
    110 bool schar_cache_clear_if_full(void)
    111 {
    112  // note: critical max is really (1<<24)-1. This gives us some marginal
    113  // until next time update_screen() is called
    114  if (glyph_cache.h.n_keys > (1<<21)) {
    115    schar_cache_clear();
    116    return true;
    117  }
    118  return false;
    119 }
    120 
    121 void schar_cache_clear(void)
    122 {
    123  decor_check_invalid_glyphs();
    124  set_clear(glyph, &glyph_cache);
    125 
    126  // for char options we have stored the original strings. Regenerate
    127  // the parsed schar_T values with the new clean cache.
    128  // This must not return an error as cell widths have not changed.
    129  if (check_chars_options()) {
    130    abort();
    131  }
    132 }
    133 
    134 bool schar_high(schar_T sc)
    135 {
    136 #ifdef ORDER_BIG_ENDIAN
    137  return ((sc & 0xFF000000) == 0xFF000000);
    138 #else
    139  return ((sc & 0xFF) == 0xFF);
    140 #endif
    141 }
    142 
    143 #ifdef ORDER_BIG_ENDIAN
    144 # define schar_idx(sc) (sc & (0x00FFFFFF))
    145 #else
    146 # define schar_idx(sc) (sc >> 8)
    147 #endif
    148 
    149 /// sets final NUL
    150 size_t schar_get(char *buf_out, schar_T sc)
    151 {
    152  size_t len = schar_get_adv(&buf_out, sc);
    153  *buf_out = NUL;
    154  return len;
    155 }
    156 
    157 /// advance buf_out. do NOT set final NUL
    158 size_t schar_get_adv(char **buf_out, schar_T sc)
    159 {
    160  size_t len;
    161  if (schar_high(sc)) {
    162    uint32_t idx = schar_idx(sc);
    163    assert(idx < glyph_cache.h.n_keys);
    164    len = strlen(&glyph_cache.keys[idx]);
    165    memcpy(*buf_out, &glyph_cache.keys[idx], len);
    166  } else {
    167    len = strnlen((char *)&sc, 4);
    168    memcpy(*buf_out, (char *)&sc, len);
    169  }
    170  *buf_out += len;
    171  return len;
    172 }
    173 
    174 size_t schar_len(schar_T sc)
    175 {
    176  if (schar_high(sc)) {
    177    uint32_t idx = schar_idx(sc);
    178    assert(idx < glyph_cache.h.n_keys);
    179    return strlen(&glyph_cache.keys[idx]);
    180  } else {
    181    return strnlen((char *)&sc, 4);
    182  }
    183 }
    184 
    185 int schar_cells(schar_T sc)
    186 {
    187  // hot path
    188 #ifdef ORDER_BIG_ENDIAN
    189  if (!(sc & 0x80FFFFFF)) {
    190    return 1;
    191  }
    192 #else
    193  if (sc < 0x80) {
    194    return 1;
    195  }
    196 #endif
    197 
    198  char sc_buf[MAX_SCHAR_SIZE];
    199  schar_get(sc_buf, sc);
    200  return utf_ptr2cells(sc_buf);
    201 }
    202 
    203 /// gets first raw UTF-8 byte of an schar
    204 static char schar_get_first_byte(schar_T sc)
    205 {
    206  assert(!(schar_high(sc) && schar_idx(sc) >= glyph_cache.h.n_keys));
    207  return schar_high(sc) ? glyph_cache.keys[schar_idx(sc)] : *(char *)&sc;
    208 }
    209 
    210 int schar_get_first_codepoint(schar_T sc)
    211 {
    212  char sc_buf[MAX_SCHAR_SIZE];
    213  schar_get(sc_buf, sc);
    214  return utf_ptr2char(sc_buf);
    215 }
    216 
    217 /// @return ascii char or NUL if not ascii
    218 char schar_get_ascii(schar_T sc)
    219 {
    220 #ifdef ORDER_BIG_ENDIAN
    221  return (!(sc & 0x80FFFFFF)) ? *(char *)&sc : NUL;
    222 #else
    223  return (sc < 0x80) ? (char)sc : NUL;
    224 #endif
    225 }
    226 
    227 static bool schar_in_arabic_block(schar_T sc)
    228 {
    229  char first_byte = schar_get_first_byte(sc);
    230  return ((uint8_t)first_byte & 0xFE) == 0xD8;
    231 }
    232 
    233 /// Get the first two codepoints of an schar, or NUL when not available
    234 static void schar_get_first_two_codepoints(schar_T sc, int *c0, int *c1)
    235 {
    236  char sc_buf[MAX_SCHAR_SIZE];
    237  schar_get(sc_buf, sc);
    238 
    239  *c0 = utf_ptr2char(sc_buf);
    240  int len = utf_ptr2len(sc_buf);
    241  if (*c0 == NUL) {
    242    *c1 = NUL;
    243  } else {
    244    *c1 = utf_ptr2char(sc_buf + len);
    245  }
    246 }
    247 
    248 void line_do_arabic_shape(schar_T *buf, int cols)
    249 {
    250  int i = 0;
    251 
    252  for (i = 0; i < cols; i++) {
    253    // quickly skip over non-arabic text
    254    if (schar_in_arabic_block(buf[i])) {
    255      break;
    256    }
    257  }
    258 
    259  if (i == cols) {
    260    return;
    261  }
    262 
    263  int c0prev = 0;
    264  int c0, c1;
    265  schar_get_first_two_codepoints(buf[i], &c0, &c1);
    266 
    267  for (; i < cols; i++) {
    268    int c0next, c1next;
    269    schar_get_first_two_codepoints(i + 1 < cols ? buf[i + 1] : 0, &c0next, &c1next);
    270 
    271    if (!ARABIC_CHAR(c0)) {
    272      goto next;
    273    }
    274 
    275    int c1new = c1;
    276    int c0new = arabic_shape(c0, &c1new, c0next, c1next, c0prev);
    277 
    278    if (c0new == c0 && c1new == c1) {
    279      goto next;  // unchanged
    280    }
    281 
    282    char scbuf[MAX_SCHAR_SIZE];
    283    schar_get(scbuf, buf[i]);
    284 
    285    char scbuf_new[MAX_SCHAR_SIZE];
    286    size_t len = (size_t)utf_char2bytes(c0new, scbuf_new);
    287    if (c1new) {
    288      len += (size_t)utf_char2bytes(c1new, scbuf_new + len);
    289    }
    290 
    291    int off = utf_char2len(c0) + (c1 ? utf_char2len(c1) : 0);
    292    size_t rest = strlen(scbuf + off);
    293    if (rest + len + 1 > MAX_SCHAR_SIZE) {
    294      // Too bigly, discard one code-point.
    295      // This should be enough as c0 cannot grow more than from 2 to 4 bytes
    296      // (base arabic to extended arabic)
    297      rest -= (size_t)utf_cp_bounds(scbuf + off, scbuf + off + rest - 1).begin_off + 1;
    298    }
    299    memcpy(scbuf_new + len, scbuf + off, rest);
    300    buf[i] = schar_from_buf(scbuf_new, len + rest);
    301 
    302 next:
    303    c0prev = c0;
    304    c0 = c0next;
    305    c1 = c1next;
    306  }
    307 }
    308 
    309 /// clear a line in the grid starting at "off" until "width" characters
    310 /// are cleared.
    311 void grid_clear_line(ScreenGrid *grid, size_t off, int width, bool valid)
    312 {
    313  for (int col = 0; col < width; col++) {
    314    grid->chars[off + (size_t)col] = schar_from_ascii(' ');
    315  }
    316  int fill = valid ? 0 : -1;
    317  memset(grid->attrs + off, fill, (size_t)width * sizeof(sattr_T));
    318  memset(grid->vcols + off, -1, (size_t)width * sizeof(colnr_T));
    319 }
    320 
    321 void grid_invalidate(ScreenGrid *grid)
    322 {
    323  memset(grid->attrs, -1, sizeof(sattr_T) * (size_t)grid->rows * (size_t)grid->cols);
    324 }
    325 
    326 static bool grid_invalid_row(ScreenGrid *grid, int row)
    327 {
    328  return grid->attrs[grid->line_offset[row]] < 0;
    329 }
    330 
    331 /// Get a single character directly from grid.chars
    332 ///
    333 /// @param[out] attrp  set to the character's attribute (optional)
    334 schar_T grid_getchar(ScreenGrid *grid, int row, int col, int *attrp)
    335 {
    336  // safety check
    337  if (grid->chars == NULL || row >= grid->rows || col >= grid->cols) {
    338    return NUL;
    339  }
    340 
    341  size_t off = grid->line_offset[row] + (size_t)col;
    342  if (attrp != NULL) {
    343    *attrp = grid->attrs[off];
    344  }
    345  return grid->chars[off];
    346 }
    347 
    348 static ScreenGrid *grid_line_grid = NULL;
    349 static int grid_line_row = -1;
    350 static int grid_line_coloff = 0;
    351 static int grid_line_maxcol = 0;
    352 static int grid_line_first = INT_MAX;
    353 static int grid_line_last = 0;
    354 static int grid_line_clear_to = 0;
    355 static int grid_line_bg_attr = 0;
    356 static int grid_line_clear_attr = 0;
    357 static int grid_line_flags = 0;
    358 
    359 /// Start a group of grid_line_puts calls that builds a single grid line.
    360 ///
    361 /// Must be matched with a grid_line_flush call before moving to
    362 /// another line.
    363 void grid_line_start(GridView *view, int row)
    364 {
    365  int col = 0;
    366  ScreenGrid *grid = grid_adjust(view, &row, &col);
    367  screengrid_line_start(grid, row, col);
    368 }
    369 
    370 void screengrid_line_start(ScreenGrid *grid, int row, int col)
    371 {
    372  grid_line_maxcol = grid->cols;
    373  assert(grid_line_grid == NULL);
    374  grid_line_row = row;
    375  grid_line_grid = grid;
    376  grid_line_coloff = col;
    377  grid_line_first = (int)linebuf_size;
    378  grid_line_maxcol = MIN(grid_line_maxcol, grid->cols - grid_line_coloff);
    379  grid_line_last = 0;
    380  grid_line_clear_to = 0;
    381  grid_line_bg_attr = 0;
    382  grid_line_clear_attr = 0;
    383  grid_line_flags = 0;
    384 
    385  assert((size_t)grid_line_maxcol <= linebuf_size);
    386 
    387  if (full_screen && (rdb_flags & kOptRdbFlagInvalid)) {
    388    assert(linebuf_char);
    389    // Current batch must not depend on previous contents of linebuf_char.
    390    // Set invalid values which will cause assertion failures later if they are used.
    391    memset(linebuf_char, 0xFF, sizeof(schar_T) * linebuf_size);
    392    memset(linebuf_attr, 0xFF, sizeof(sattr_T) * linebuf_size);
    393  }
    394 }
    395 
    396 /// Get present char from current rendered screen line
    397 ///
    398 /// This indicates what already is on screen, not the pending render buffer.
    399 ///
    400 /// @return char or space if out of bounds
    401 schar_T grid_line_getchar(int col, int *attr)
    402 {
    403  if (col < grid_line_maxcol) {
    404    col += grid_line_coloff;
    405    size_t off = grid_line_grid->line_offset[grid_line_row] + (size_t)col;
    406    if (attr != NULL) {
    407      *attr = grid_line_grid->attrs[off];
    408    }
    409    return grid_line_grid->chars[off];
    410  } else {
    411    // NUL is a very special value (right-half of double width), space is True Neutralâ„¢
    412    return schar_from_ascii(' ');
    413  }
    414 }
    415 
    416 void grid_line_put_schar(int col, schar_T schar, int attr)
    417 {
    418  assert(grid_line_grid);
    419  if (col >= grid_line_maxcol) {
    420    return;
    421  }
    422 
    423  linebuf_char[col] = schar;
    424  linebuf_attr[col] = attr;
    425 
    426  grid_line_first = MIN(grid_line_first, col);
    427  // TODO(bfredl): Y U NO DOUBLEWIDTH?
    428  grid_line_last = MAX(grid_line_last, col + 1);
    429  linebuf_vcol[col] = -1;
    430 }
    431 
    432 /// Put string "text" at "col" position relative to the grid line from the
    433 /// recent grid_line_start() call.
    434 ///
    435 /// @param textlen length of string or -1 to use strlen(text)
    436 /// Note: only outputs within one row!
    437 ///
    438 /// @return number of grid cells used
    439 int grid_line_puts(int col, const char *text, int textlen, int attr)
    440 {
    441  const char *ptr = text;
    442  int len = textlen;
    443 
    444  assert(grid_line_grid);
    445 
    446  int start_col = col;
    447 
    448  const int max_col = grid_line_maxcol;
    449  while (col < max_col && (len < 0 || (int)(ptr - text) < len) && *ptr != NUL) {
    450    // check if this is the first byte of a multibyte
    451    int mbyte_blen;
    452    if (len >= 0) {
    453      int maxlen = (int)((text + len) - ptr);
    454      mbyte_blen = utfc_ptr2len_len(ptr, maxlen);
    455      if (mbyte_blen > maxlen) {
    456        mbyte_blen = 1;
    457      }
    458    } else {
    459      mbyte_blen = utfc_ptr2len(ptr);
    460    }
    461    int firstc;
    462    schar_T schar = utfc_ptrlen2schar(ptr, mbyte_blen, &firstc);
    463    int mbyte_cells = utf_ptr2cells_len(ptr, mbyte_blen);
    464    if (mbyte_cells > 2 || schar == 0) {
    465      mbyte_cells = 1;
    466      schar = schar_from_char(0xFFFD);
    467    }
    468 
    469    if (col + mbyte_cells > max_col) {
    470      // Only 1 cell left, but character requires 2 cells:
    471      // display a '>' in the last column to avoid wrapping.
    472      schar = schar_from_ascii('>');
    473      mbyte_cells = 1;
    474    }
    475 
    476    // When at the start of the text and overwriting the right half of a
    477    // two-cell character in the same grid, truncate that into a '>'.
    478    if (ptr == text && col > grid_line_first && col < grid_line_last
    479        && linebuf_char[col] == 0) {
    480      linebuf_char[col - 1] = schar_from_ascii('>');
    481    }
    482 
    483    linebuf_char[col] = schar;
    484    linebuf_attr[col] = attr;
    485    linebuf_vcol[col] = -1;
    486    if (mbyte_cells == 2) {
    487      linebuf_char[col + 1] = 0;
    488      linebuf_attr[col + 1] = attr;
    489      linebuf_vcol[col + 1] = -1;
    490    }
    491 
    492    col += mbyte_cells;
    493    ptr += mbyte_blen;
    494  }
    495 
    496  if (col > start_col) {
    497    grid_line_first = MIN(grid_line_first, start_col);
    498    grid_line_last = MAX(grid_line_last, col);
    499  }
    500 
    501  return col - start_col;
    502 }
    503 
    504 int grid_line_fill(int start_col, int end_col, schar_T sc, int attr)
    505 {
    506  end_col = MIN(end_col, grid_line_maxcol);
    507  if (start_col >= end_col) {
    508    return end_col;
    509  }
    510 
    511  for (int col = start_col; col < end_col; col++) {
    512    linebuf_char[col] = sc;
    513    linebuf_attr[col] = attr;
    514    linebuf_vcol[col] = -1;
    515  }
    516 
    517  grid_line_first = MIN(grid_line_first, start_col);
    518  grid_line_last = MAX(grid_line_last, end_col);
    519  return end_col;
    520 }
    521 
    522 /// @param bg_attr     applies to both the buffered line and the columns to clear
    523 /// @param clear_attr  applies only to the columns to clear
    524 void grid_line_clear_end(int start_col, int end_col, int bg_attr, int clear_attr)
    525 {
    526  if (grid_line_first > start_col) {
    527    grid_line_first = start_col;
    528    grid_line_last = start_col;
    529  }
    530  grid_line_clear_to = end_col;
    531  grid_line_bg_attr = bg_attr;
    532  grid_line_clear_attr = clear_attr;
    533 }
    534 
    535 /// move the cursor to a position in a currently rendered line.
    536 void grid_line_cursor_goto(int col)
    537 {
    538  ui_grid_cursor_goto(grid_line_grid->handle, grid_line_row, col);
    539 }
    540 
    541 void grid_line_mirror(int width)
    542 {
    543  grid_line_clear_to = MAX(grid_line_last, grid_line_clear_to);
    544  if (grid_line_first >= grid_line_clear_to) {
    545    return;
    546  }
    547  linebuf_mirror(&grid_line_first, &grid_line_last, &grid_line_clear_to, width);
    548  grid_line_flags |= SLF_RIGHTLEFT;
    549 }
    550 
    551 void linebuf_mirror(int *firstp, int *lastp, int *clearp, int width)
    552 {
    553  int first = *firstp;
    554  int last = *lastp;
    555 
    556  size_t n = (size_t)(last - first);
    557  int mirror = width - 1;  // Mirrors are more fun than television.
    558  schar_T *scratch_char = (schar_T *)linebuf_scratch;
    559  memcpy(scratch_char + first, linebuf_char + first, n * sizeof(schar_T));
    560  for (int col = first; col < last; col++) {
    561    int rev = mirror - col;
    562    if (col + 1 < last && scratch_char[col + 1] == 0) {
    563      linebuf_char[rev - 1] = scratch_char[col];
    564      linebuf_char[rev] = 0;
    565      col++;
    566    } else {
    567      linebuf_char[rev] = scratch_char[col];
    568    }
    569  }
    570 
    571  // for attr and vcol: assumes doublewidth chars are self-consistent
    572  sattr_T *scratch_attr = (sattr_T *)linebuf_scratch;
    573  memcpy(scratch_attr + first, linebuf_attr + first, n * sizeof(sattr_T));
    574  for (int col = first; col < last; col++) {
    575    linebuf_attr[mirror - col] = scratch_attr[col];
    576  }
    577 
    578  colnr_T *scratch_vcol = (colnr_T *)linebuf_scratch;
    579  memcpy(scratch_vcol + first, linebuf_vcol + first, n * sizeof(colnr_T));
    580  for (int col = first; col < last; col++) {
    581    linebuf_vcol[mirror - col] = scratch_vcol[col];
    582  }
    583 
    584  *firstp = width - *clearp;
    585  *clearp = width - first;
    586  *lastp = width - last;
    587 }
    588 
    589 /// End a group of grid_line_puts calls and send the screen buffer to the UI layer.
    590 void grid_line_flush(void)
    591 {
    592  ScreenGrid *grid = grid_line_grid;
    593  grid_line_grid = NULL;
    594  grid_line_clear_to = MAX(grid_line_last, grid_line_clear_to);
    595  assert(grid_line_clear_to <= grid_line_maxcol);
    596  if (grid_line_first >= grid_line_clear_to) {
    597    return;
    598  }
    599 
    600  grid_put_linebuf(grid, grid_line_row, grid_line_coloff, grid_line_first, grid_line_last,
    601                   grid_line_clear_to, grid_line_bg_attr, grid_line_clear_attr, -1,
    602                   grid_line_flags);
    603 }
    604 
    605 /// flush grid line but only if on a valid row
    606 ///
    607 /// This is a stopgap until message.c has been refactored to behave
    608 void grid_line_flush_if_valid_row(void)
    609 {
    610  if (grid_line_row < 0 || grid_line_row >= grid_line_grid->rows) {
    611    if (rdb_flags & kOptRdbFlagInvalid) {
    612      abort();
    613    } else {
    614      grid_line_grid = NULL;
    615      return;
    616    }
    617  }
    618  grid_line_flush();
    619 }
    620 
    621 void grid_clear(GridView *grid, int start_row, int end_row, int start_col, int end_col, int attr)
    622 {
    623  for (int row = start_row; row < end_row; row++) {
    624    grid_line_start(grid, row);
    625    end_col = MIN(end_col, grid_line_maxcol);
    626    if (grid_line_row >= grid_line_grid->rows || start_col >= end_col) {
    627      grid_line_grid = NULL;  // TODO(bfredl): make callers behave instead
    628      return;
    629    }
    630    grid_line_clear_end(start_col, end_col, attr, 0);
    631    grid_line_flush();
    632  }
    633 }
    634 
    635 /// Check whether the given character needs redrawing:
    636 /// - the (first byte of the) character is different
    637 /// - the attributes are different
    638 /// - the character is multi-byte and the next byte is different
    639 /// - the character is two cells wide and the second cell differs.
    640 static int grid_char_needs_redraw(ScreenGrid *grid, int col, size_t off_to, int cols)
    641 {
    642  return (cols > 0
    643          && ((linebuf_char[col] != grid->chars[off_to]
    644               || linebuf_attr[col] != grid->attrs[off_to]
    645               || (cols > 1 && linebuf_char[col + 1] == 0
    646                   && linebuf_char[col + 1] != grid->chars[off_to + 1]))
    647              || exmode_active  // TODO(bfredl): what in the actual fuck
    648              || rdb_flags & kOptRdbFlagNodelta));
    649 }
    650 
    651 /// Move one buffered line to the window grid, but only the characters that
    652 /// have actually changed.  Handle insert/delete character.
    653 ///
    654 /// @param coloff  gives the first column on the grid for this line.
    655 /// @param endcol  gives the columns where valid characters are.
    656 /// @param clear_width  see SLF_RIGHTLEFT.
    657 /// @param clear_attr   combined with "bg_attr" for the columns to clear.
    658 /// @param flags  can have bits:
    659 /// - SLF_RIGHTLEFT  rightleft text, like a window with 'rightleft' option set:
    660 ///   - When false, clear columns "endcol" to "clear_width".
    661 ///   - When true, clear columns "col" to "endcol".
    662 /// - SLF_WRAP  hint to UI that "row" contains a line wrapped into the next row.
    663 /// - SLF_INC_VCOL:
    664 ///   - When false, use "last_vcol" for grid->vcols[] of the columns to clear.
    665 ///   - When true, use an increasing sequence starting from "last_vcol + 1" for
    666 ///     grid->vcols[] of the columns to clear.
    667 void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int col, int endcol, int clear_width,
    668                      int bg_attr, int clear_attr, colnr_T last_vcol, int flags)
    669 {
    670  bool redraw_next;                         // redraw_this for next character
    671  bool clear_next = false;
    672  assert(0 <= row && row < grid->rows);
    673  // TODO(bfredl): check all callsites and eliminate
    674  // Check for illegal col, just in case
    675  if (endcol > grid->cols) {
    676    endcol = grid->cols;
    677  }
    678 
    679  // Safety check. Avoids clang warnings down the call stack.
    680  if (grid->chars == NULL || row >= grid->rows || coloff >= grid->cols) {
    681    DLOG("invalid state, skipped");
    682    return;
    683  }
    684 
    685  bool invalid_row = grid != &default_grid && grid_invalid_row(grid, row) && col == 0;
    686  size_t off_to = grid->line_offset[row] + (size_t)coloff;
    687  const size_t max_off_to = grid->line_offset[row] + (size_t)grid->cols;
    688 
    689  // When at the start of the text and overwriting the right half of a
    690  // two-cell character in the same grid, truncate that into a '>'.
    691  if (col > 0 && grid->chars[off_to + (size_t)col] == 0) {
    692    linebuf_char[col - 1] = schar_from_ascii('>');
    693    linebuf_attr[col - 1] = grid->attrs[off_to + (size_t)col - 1];
    694    col--;
    695  }
    696 
    697  int clear_start = endcol;
    698  if (flags & SLF_RIGHTLEFT) {
    699    clear_start = col;
    700    col = endcol;
    701    endcol = clear_width;
    702    clear_width = col;
    703  }
    704 
    705  if (p_arshape && !p_tbidi && endcol > col) {
    706    line_do_arabic_shape(linebuf_char + col, endcol - col);
    707  }
    708 
    709  if (bg_attr) {
    710    for (int c = col; c < endcol; c++) {
    711      linebuf_attr[c] = hl_combine_attr(bg_attr, linebuf_attr[c]);
    712    }
    713  }
    714 
    715  redraw_next = grid_char_needs_redraw(grid, col, off_to + (size_t)col, endcol - col);
    716 
    717  int start_dirty = -1;
    718  int end_dirty = 0;
    719 
    720  while (col < endcol) {
    721    int char_cells = 1;  // 1: normal char
    722                         // 2: occupies two display cells
    723    if (col + 1 < endcol && linebuf_char[col + 1] == 0) {
    724      char_cells = 2;
    725    }
    726    bool redraw_this = redraw_next;  // Does character need redraw?
    727    size_t off = off_to + (size_t)col;
    728    redraw_next = grid_char_needs_redraw(grid, col + char_cells,
    729                                         off + (size_t)char_cells,
    730                                         endcol - col - char_cells);
    731 
    732    if (redraw_this) {
    733      if (start_dirty == -1) {
    734        start_dirty = col;
    735      }
    736      end_dirty = col + char_cells;
    737      // When writing a single-width character over a double-width
    738      // character and at the end of the redrawn text, need to clear out
    739      // the right half of the old character.
    740      // Also required when writing the right half of a double-width
    741      // char over the left half of an existing one
    742      if (col + char_cells == endcol && off + (size_t)char_cells < max_off_to
    743          && grid->chars[off + (size_t)char_cells] == NUL) {
    744        clear_next = true;
    745      }
    746 
    747      grid->chars[off] = linebuf_char[col];
    748      if (char_cells == 2) {
    749        grid->chars[off + 1] = linebuf_char[col + 1];
    750      }
    751 
    752      grid->attrs[off] = linebuf_attr[col];
    753      // For simplicity set the attributes of second half of a
    754      // double-wide character equal to the first half.
    755      if (char_cells == 2) {
    756        grid->attrs[off + 1] = linebuf_attr[col];
    757      }
    758    }
    759 
    760    grid->vcols[off] = linebuf_vcol[col];
    761    if (char_cells == 2) {
    762      grid->vcols[off + 1] = linebuf_vcol[col + 1];
    763    }
    764 
    765    col += char_cells;
    766  }
    767 
    768  if (clear_next) {
    769    // Clear the second half of a double-wide character of which the left
    770    // half was overwritten with a single-wide character.
    771    grid->chars[off_to + (size_t)col] = schar_from_ascii(' ');
    772    end_dirty++;
    773  }
    774 
    775  // When clearing the left half of a double-wide char also clear the right half.
    776  if (off_to + (size_t)clear_width < max_off_to
    777      && grid->chars[off_to + (size_t)clear_width] == 0) {
    778    clear_width++;
    779  }
    780 
    781  int clear_dirty_start = -1, clear_end = -1;
    782  if (flags & SLF_RIGHTLEFT) {
    783    for (col = clear_width - 1; col >= clear_start; col--) {
    784      size_t off = off_to + (size_t)col;
    785      grid->vcols[off] = (flags & SLF_INC_VCOL) ? ++last_vcol : last_vcol;
    786    }
    787  }
    788  clear_attr = hl_combine_attr(bg_attr, clear_attr);
    789  // blank out the rest of the line
    790  // TODO(bfredl): we could cache winline widths
    791  for (col = clear_start; col < clear_width; col++) {
    792    size_t off = off_to + (size_t)col;
    793    if (grid->chars[off] != schar_from_ascii(' ')
    794        || grid->attrs[off] != clear_attr
    795        || rdb_flags & kOptRdbFlagNodelta) {
    796      grid->chars[off] = schar_from_ascii(' ');
    797      grid->attrs[off] = clear_attr;
    798      if (clear_dirty_start == -1) {
    799        clear_dirty_start = col;
    800      }
    801      clear_end = col + 1;
    802    }
    803    if (!(flags & SLF_RIGHTLEFT)) {
    804      grid->vcols[off] = (flags & SLF_INC_VCOL) ? ++last_vcol : last_vcol;
    805    }
    806  }
    807 
    808  if ((flags & SLF_RIGHTLEFT) && start_dirty != -1 && clear_dirty_start != -1) {
    809    if (grid->throttled || clear_dirty_start >= start_dirty - 5) {
    810      // cannot draw now or too small to be worth a separate "clear" event
    811      start_dirty = clear_dirty_start;
    812    } else {
    813      ui_line(grid, row, invalid_row, coloff + clear_dirty_start, coloff + clear_dirty_start,
    814              coloff + clear_end, clear_attr, flags & SLF_WRAP);
    815    }
    816    clear_end = end_dirty;
    817  } else {
    818    if (start_dirty == -1) {  // clear only
    819      start_dirty = clear_dirty_start;
    820      end_dirty = clear_dirty_start;
    821    } else if (clear_end < end_dirty) {  // put only
    822      clear_end = end_dirty;
    823    } else {
    824      end_dirty = endcol;
    825    }
    826  }
    827 
    828  if (clear_end > start_dirty) {
    829    if (!grid->throttled) {
    830      ui_line(grid, row, invalid_row, coloff + start_dirty, coloff + end_dirty, coloff + clear_end,
    831              clear_attr, flags & SLF_WRAP);
    832    } else if (grid->dirty_col) {
    833      // TODO(bfredl): really get rid of the extra pseudo terminal in message.c
    834      // by using a linebuf_char copy for "throttled message line"
    835      if (clear_end > grid->dirty_col[row]) {
    836        grid->dirty_col[row] = clear_end;
    837      }
    838    }
    839  }
    840 }
    841 
    842 void grid_alloc(ScreenGrid *grid, int rows, int columns, bool copy, bool valid)
    843 {
    844  int new_row;
    845  ScreenGrid ngrid = *grid;
    846  assert(rows >= 0 && columns >= 0);
    847  size_t ncells = (size_t)rows * (size_t)columns;
    848  ngrid.chars = xmalloc(ncells * sizeof(schar_T));
    849  ngrid.attrs = xmalloc(ncells * sizeof(sattr_T));
    850  ngrid.vcols = xmalloc(ncells * sizeof(colnr_T));
    851  memset(ngrid.vcols, -1, ncells * sizeof(colnr_T));
    852  ngrid.line_offset = xmalloc((size_t)rows * sizeof(*ngrid.line_offset));
    853 
    854  ngrid.rows = rows;
    855  ngrid.cols = columns;
    856 
    857  for (new_row = 0; new_row < ngrid.rows; new_row++) {
    858    ngrid.line_offset[new_row] = (size_t)new_row * (size_t)ngrid.cols;
    859 
    860    grid_clear_line(&ngrid, ngrid.line_offset[new_row], columns, valid);
    861 
    862    if (copy) {
    863      // If the screen is not going to be cleared, copy as much as
    864      // possible from the old screen to the new one and clear the rest
    865      // (used when resizing the window at the "--more--" prompt or when
    866      // executing an external command, for the GUI).
    867      if (new_row < grid->rows && grid->chars != NULL) {
    868        int len = MIN(grid->cols, ngrid.cols);
    869        memmove(ngrid.chars + ngrid.line_offset[new_row],
    870                grid->chars + grid->line_offset[new_row],
    871                (size_t)len * sizeof(schar_T));
    872        memmove(ngrid.attrs + ngrid.line_offset[new_row],
    873                grid->attrs + grid->line_offset[new_row],
    874                (size_t)len * sizeof(sattr_T));
    875        memmove(ngrid.vcols + ngrid.line_offset[new_row],
    876                grid->vcols + grid->line_offset[new_row],
    877                (size_t)len * sizeof(colnr_T));
    878      }
    879    }
    880  }
    881  grid_free(grid);
    882  *grid = ngrid;
    883 
    884  // Share a single scratch buffer for all grids, by
    885  // ensuring it is as wide as the widest grid.
    886  if (linebuf_size < (size_t)columns) {
    887    xfree(linebuf_char);
    888    xfree(linebuf_attr);
    889    xfree(linebuf_vcol);
    890    xfree(linebuf_scratch);
    891    linebuf_char = xmalloc((size_t)columns * sizeof(schar_T));
    892    linebuf_attr = xmalloc((size_t)columns * sizeof(sattr_T));
    893    linebuf_vcol = xmalloc((size_t)columns * sizeof(colnr_T));
    894    linebuf_scratch = xmalloc((size_t)columns * sizeof(sscratch_T));
    895    linebuf_size = (size_t)columns;
    896  }
    897 }
    898 
    899 void grid_free(ScreenGrid *grid)
    900 {
    901  xfree(grid->chars);
    902  xfree(grid->attrs);
    903  xfree(grid->vcols);
    904  xfree(grid->line_offset);
    905 
    906  grid->chars = NULL;
    907  grid->attrs = NULL;
    908  grid->vcols = NULL;
    909  grid->line_offset = NULL;
    910 }
    911 
    912 #ifdef EXITFREE
    913 /// Doesn't allow reinit, so must only be called by free_all_mem!
    914 void grid_free_all_mem(void)
    915 {
    916  grid_free(&default_grid);
    917  grid_free(&msg_grid);
    918  XFREE_CLEAR(msg_grid.dirty_col);
    919  xfree(linebuf_char);
    920  xfree(linebuf_attr);
    921  xfree(linebuf_vcol);
    922  xfree(linebuf_scratch);
    923  set_destroy(glyph, &glyph_cache);
    924 }
    925 #endif
    926 
    927 /// (Re)allocates a window grid if size changed while in ext_multigrid mode.
    928 /// Updates size, offsets and handle for the grid regardless.
    929 ///
    930 /// If "doclear" is true, don't try to copy from the old grid rather clear the
    931 /// resized grid.
    932 void win_grid_alloc(win_T *wp)
    933 {
    934  GridView *grid = &wp->w_grid;
    935  ScreenGrid *grid_allocated = &wp->w_grid_alloc;
    936 
    937  int total_rows = wp->w_height_outer;
    938  int total_cols = wp->w_width_outer;
    939 
    940  bool want_allocation = ui_has(kUIMultigrid) || wp->w_floating;
    941  bool has_allocation = (grid_allocated->chars != NULL);
    942 
    943  if (wp->w_view_height > wp->w_lines_size) {
    944    wp->w_lines_valid = 0;
    945    xfree(wp->w_lines);
    946    wp->w_lines = xcalloc((size_t)wp->w_view_height + 1, sizeof(wline_T));
    947    wp->w_lines_size = wp->w_view_height;
    948  }
    949 
    950  bool was_resized = false;
    951  if (want_allocation && (!has_allocation
    952                          || grid_allocated->rows != total_rows
    953                          || grid_allocated->cols != total_cols)) {
    954    grid_alloc(grid_allocated, total_rows, total_cols,
    955               wp->w_grid_alloc.valid, false);
    956    grid_allocated->valid = true;
    957    if (wp->w_floating && wp->w_config.border) {
    958      wp->w_redr_border = true;
    959    }
    960    was_resized = true;
    961  } else if (!want_allocation && has_allocation) {
    962    // Single grid mode, all rendering will be redirected to default_grid.
    963    // Only keep track of the size and offset of the window.
    964    grid_free(grid_allocated);
    965    grid_allocated->valid = false;
    966    was_resized = true;
    967  } else if (want_allocation && has_allocation && !wp->w_grid_alloc.valid) {
    968    grid_invalidate(grid_allocated);
    969    grid_allocated->valid = true;
    970  }
    971 
    972  if (want_allocation) {
    973    grid->target = grid_allocated;
    974    grid->row_offset = wp->w_winrow_off;
    975    grid->col_offset = wp->w_wincol_off;
    976  } else {
    977    grid->target = &default_grid;
    978    grid->row_offset = wp->w_winrow + wp->w_winrow_off;
    979    grid->col_offset = wp->w_wincol + wp->w_wincol_off;
    980  }
    981 
    982  // send grid resize event if:
    983  // - a grid was just resized
    984  // - screen_resize was called and all grid sizes must be sent
    985  // - the UI wants multigrid event (necessary)
    986  if ((resizing_screen || was_resized) && want_allocation) {
    987    ui_call_grid_resize(grid_allocated->handle,
    988                        grid_allocated->cols, grid_allocated->rows);
    989    ui_check_cursor_grid(grid_allocated->handle);
    990  }
    991 }
    992 
    993 /// assign a handle to the grid. The grid need not be allocated.
    994 void grid_assign_handle(ScreenGrid *grid)
    995 {
    996  static int last_grid_handle = DEFAULT_GRID_HANDLE;
    997 
    998  // only assign a grid handle if not already
    999  if (grid->handle == 0) {
   1000    grid->handle = ++last_grid_handle;
   1001  }
   1002 }
   1003 
   1004 /// insert lines on the screen and move the existing lines down
   1005 /// 'line_count' is the number of lines to be inserted.
   1006 /// 'end' is the line after the scrolled part. Normally it is Rows.
   1007 /// 'col' is the column from with we start inserting.
   1008 //
   1009 /// 'row', 'col' and 'end' are relative to the start of the region.
   1010 void grid_ins_lines(ScreenGrid *grid, int row, int line_count, int end, int col, int width)
   1011 {
   1012  int j;
   1013  unsigned temp;
   1014 
   1015  if (line_count <= 0) {
   1016    return;
   1017  }
   1018 
   1019  // Shift line_offset[] line_count down to reflect the inserted lines.
   1020  // Clear the inserted lines.
   1021  for (int i = 0; i < line_count; i++) {
   1022    if (width != grid->cols) {
   1023      // need to copy part of a line
   1024      j = end - 1 - i;
   1025      while ((j -= line_count) >= row) {
   1026        linecopy(grid, j + line_count, j, col, width);
   1027      }
   1028      j += line_count;
   1029      grid_clear_line(grid, grid->line_offset[j] + (size_t)col, width, false);
   1030    } else {
   1031      j = end - 1 - i;
   1032      temp = (unsigned)grid->line_offset[j];
   1033      while ((j -= line_count) >= row) {
   1034        grid->line_offset[j + line_count] = grid->line_offset[j];
   1035      }
   1036      grid->line_offset[j + line_count] = temp;
   1037      grid_clear_line(grid, temp, grid->cols, false);
   1038    }
   1039  }
   1040 
   1041  if (!grid->throttled) {
   1042    ui_call_grid_scroll(grid->handle, row, end, col, col + width, -line_count, 0);
   1043  }
   1044 }
   1045 
   1046 /// delete lines on the screen and move lines up.
   1047 /// 'end' is the line after the scrolled part. Normally it is Rows.
   1048 /// When scrolling region used 'off' is the offset from the top for the region.
   1049 /// 'row' and 'end' are relative to the start of the region.
   1050 void grid_del_lines(ScreenGrid *grid, int row, int line_count, int end, int col, int width)
   1051 {
   1052  int j;
   1053  unsigned temp;
   1054 
   1055  if (line_count <= 0) {
   1056    return;
   1057  }
   1058 
   1059  // Now shift line_offset[] line_count up to reflect the deleted lines.
   1060  // Clear the inserted lines.
   1061  for (int i = 0; i < line_count; i++) {
   1062    if (width != grid->cols) {
   1063      // need to copy part of a line
   1064      j = row + i;
   1065      while ((j += line_count) <= end - 1) {
   1066        linecopy(grid, j - line_count, j, col, width);
   1067      }
   1068      j -= line_count;
   1069      grid_clear_line(grid, grid->line_offset[j] + (size_t)col, width, false);
   1070    } else {
   1071      // whole width, moving the line pointers is faster
   1072      j = row + i;
   1073      temp = (unsigned)grid->line_offset[j];
   1074      while ((j += line_count) <= end - 1) {
   1075        grid->line_offset[j - line_count] = grid->line_offset[j];
   1076      }
   1077      grid->line_offset[j - line_count] = temp;
   1078      grid_clear_line(grid, temp, grid->cols, false);
   1079    }
   1080  }
   1081 
   1082  if (!grid->throttled) {
   1083    ui_call_grid_scroll(grid->handle, row, end, col, col + width, line_count, 0);
   1084  }
   1085 }
   1086 
   1087 /// @param overflow Number of cells to skip.
   1088 static void grid_draw_bordertext(VirtText vt, int col, int winbl, const int *hl_attr,
   1089                                 BorderTextType bt, int overflow)
   1090 {
   1091  int default_attr = hl_attr[bt == kBorderTextTitle ? HLF_BTITLE : HLF_BFOOTER];
   1092  if (overflow > 0) {
   1093    grid_line_puts(1, "<", -1,  hl_apply_winblend(winbl, default_attr));
   1094    col += 1;
   1095    overflow += 1;
   1096  }
   1097 
   1098  for (size_t i = 0; i < kv_size(vt);) {
   1099    int attr = -1;
   1100    char *text = next_virt_text_chunk(vt, &i, &attr);
   1101    if (text == NULL) {
   1102      break;
   1103    }
   1104    if (attr == -1) {  // No highlight specified.
   1105      attr = default_attr;
   1106    }
   1107    // Skip characters from the beginning when title overflows available width.
   1108    if (overflow > 0) {
   1109      int cells = (int)mb_string2cells(text);
   1110      // Skip entire chunk if overflow is larger than chunk width.
   1111      if (overflow >= cells) {
   1112        overflow -= cells;
   1113        continue;
   1114      }
   1115      // Skip partial characters within the chunk.
   1116      char *p = text;
   1117      while (*p && overflow > 0) {
   1118        overflow -= utf_ptr2cells(p);
   1119        p += utfc_ptr2len(p);
   1120      }
   1121      text = p;
   1122    }
   1123    attr = hl_apply_winblend(winbl, attr);
   1124    col += grid_line_puts(col, text, -1, attr);
   1125  }
   1126 }
   1127 
   1128 static int get_bordertext_col(int total_col, int text_width, AlignTextPos align)
   1129 {
   1130  switch (align) {
   1131  case kAlignLeft:
   1132    return 1;
   1133  case kAlignCenter:
   1134    return MAX((total_col - text_width) / 2 + 1, 1);
   1135  case kAlignRight:
   1136    return MAX(total_col - text_width + 1, 1);
   1137  }
   1138  UNREACHABLE;
   1139 }
   1140 
   1141 /// draw border on floating window grid
   1142 void grid_draw_border(ScreenGrid *grid, WinConfig *config, int *adj, int winbl, int *hl_attr)
   1143 {
   1144  int *attrs = config->border_attr;
   1145  int default_adj[4] = { 1, 1, 1, 1 };
   1146  if (adj == NULL) {
   1147    adj = default_adj;
   1148  }
   1149  schar_T chars[8];
   1150  if (!hl_attr) {
   1151    hl_attr = hl_attr_active;
   1152  }
   1153 
   1154  for (int i = 0; i < 8; i++) {
   1155    chars[i] = schar_from_str(config->border_chars[i]);
   1156  }
   1157 
   1158  int irow = grid->rows - adj[0] - adj[2];
   1159  int icol = grid->cols - adj[1] - adj[3];
   1160 
   1161  if (adj[0]) {
   1162    screengrid_line_start(grid, 0, 0);
   1163    if (adj[3]) {
   1164      grid_line_put_schar(0, chars[0], attrs[0]);
   1165    }
   1166 
   1167    for (int i = 0; i < icol; i++) {
   1168      grid_line_put_schar(i + adj[3], chars[1], attrs[1]);
   1169    }
   1170 
   1171    if (config->title) {
   1172      int title_col = get_bordertext_col(icol, config->title_width, config->title_pos);
   1173      grid_draw_bordertext(config->title_chunks, title_col, winbl, hl_attr, kBorderTextTitle,
   1174                           config->title_width - icol);
   1175    }
   1176    if (adj[1]) {
   1177      grid_line_put_schar(icol + adj[3], chars[2], attrs[2]);
   1178    }
   1179    grid_line_flush();
   1180  }
   1181 
   1182  for (int i = 0; i < irow; i++) {
   1183    if (adj[3]) {
   1184      screengrid_line_start(grid, i + adj[0], 0);
   1185      grid_line_put_schar(0, chars[7], attrs[7]);
   1186      grid_line_flush();
   1187    }
   1188    if (adj[1]) {
   1189      int ic = (i == 0 && !adj[0] && chars[2]) ? 2 : 3;
   1190      screengrid_line_start(grid, i + adj[0], 0);
   1191      grid_line_put_schar(icol + adj[3], chars[ic], attrs[ic]);
   1192      grid_line_flush();
   1193    }
   1194  }
   1195 
   1196  if (adj[2]) {
   1197    screengrid_line_start(grid, irow + adj[0], 0);
   1198    if (adj[3]) {
   1199      grid_line_put_schar(0, chars[6], attrs[6]);
   1200    }
   1201 
   1202    for (int i = 0; i < icol; i++) {
   1203      int ic = (i == 0 && !adj[3] && chars[6]) ? 6 : 5;
   1204      grid_line_put_schar(i + adj[3], chars[ic], attrs[ic]);
   1205    }
   1206 
   1207    if (config->footer) {
   1208      int footer_col = get_bordertext_col(icol, config->footer_width, config->footer_pos);
   1209      grid_draw_bordertext(config->footer_chunks, footer_col, winbl, hl_attr, kBorderTextFooter,
   1210                           config->footer_width - icol);
   1211    }
   1212    if (adj[1]) {
   1213      grid_line_put_schar(icol + adj[3], chars[4], attrs[4]);
   1214    }
   1215    grid_line_flush();
   1216  }
   1217 }
   1218 
   1219 static void linecopy(ScreenGrid *grid, int to, int from, int col, int width)
   1220 {
   1221  unsigned off_to = (unsigned)(grid->line_offset[to] + (size_t)col);
   1222  unsigned off_from = (unsigned)(grid->line_offset[from] + (size_t)col);
   1223 
   1224  memmove(grid->chars + off_to, grid->chars + off_from, (size_t)width * sizeof(schar_T));
   1225  memmove(grid->attrs + off_to, grid->attrs + off_from, (size_t)width * sizeof(sattr_T));
   1226  memmove(grid->vcols + off_to, grid->vcols + off_from, (size_t)width * sizeof(colnr_T));
   1227 }
   1228 
   1229 win_T *get_win_by_grid_handle(handle_T handle)
   1230 {
   1231  FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
   1232    if (wp->w_grid_alloc.handle == handle) {
   1233      return wp;
   1234    }
   1235  }
   1236  return NULL;
   1237 }
   1238 
   1239 /// Put a unicode character in a screen cell.
   1240 schar_T schar_from_char(int c)
   1241 {
   1242  schar_T sc = 0;
   1243  if (c >= 0x200000) {
   1244    // TODO(bfredl): this must NEVER happen, even if the file contained overlong sequences
   1245    c = 0xFFFD;
   1246  }
   1247  utf_char2bytes(c, (char *)&sc);
   1248  return sc;
   1249 }