neovim

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

ui_compositor.c (23006B)


      1 // Compositor: merge floating grids with the main grid for display in
      2 // TUI and non-multigrid UIs.
      3 //
      4 // Layer-based compositing: https://en.wikipedia.org/wiki/Digital_compositing
      5 
      6 #include <assert.h>
      7 #include <inttypes.h>
      8 #include <limits.h>
      9 #include <stdbool.h>
     10 #include <stdlib.h>
     11 #include <string.h>
     12 #include <uv.h>
     13 
     14 #include "klib/kvec.h"
     15 #include "nvim/api/private/defs.h"
     16 #include "nvim/ascii_defs.h"
     17 #include "nvim/buffer_defs.h"
     18 #include "nvim/globals.h"
     19 #include "nvim/grid.h"
     20 #include "nvim/highlight.h"
     21 #include "nvim/highlight_defs.h"
     22 #include "nvim/highlight_group.h"
     23 #include "nvim/log.h"
     24 #include "nvim/macros_defs.h"
     25 #include "nvim/memory.h"
     26 #include "nvim/message.h"
     27 #include "nvim/option_vars.h"
     28 #include "nvim/os/time.h"
     29 #include "nvim/types_defs.h"
     30 #include "nvim/ui.h"
     31 #include "nvim/ui_compositor.h"
     32 
     33 #include "ui_compositor.c.generated.h"
     34 
     35 static int composed_uis = 0;
     36 kvec_t(ScreenGrid *) layers = KV_INITIAL_VALUE;
     37 
     38 static size_t bufsize = 0;
     39 static schar_T *linebuf;
     40 static sattr_T *attrbuf;
     41 
     42 #ifndef NDEBUG
     43 static int chk_width = 0, chk_height = 0;
     44 #endif
     45 
     46 static ScreenGrid *curgrid;
     47 
     48 static bool valid_screen = true;
     49 static int msg_current_row = INT_MAX;
     50 static bool msg_was_scrolled = false;
     51 
     52 static int msg_sep_row = -1;
     53 static schar_T msg_sep_char = schar_from_ascii(' ');
     54 
     55 static int dbghl_normal, dbghl_clear, dbghl_composed, dbghl_recompose;
     56 
     57 void ui_comp_init(void)
     58 {
     59  kv_push(layers, &default_grid);
     60  curgrid = &default_grid;
     61 }
     62 
     63 #ifdef EXITFREE
     64 void ui_comp_free_all_mem(void)
     65 {
     66  kv_destroy(layers);
     67  xfree(linebuf);
     68  xfree(attrbuf);
     69 }
     70 #endif
     71 
     72 void ui_comp_syn_init(void)
     73 {
     74  dbghl_normal = syn_check_group(S_LEN("RedrawDebugNormal"));
     75  dbghl_clear = syn_check_group(S_LEN("RedrawDebugClear"));
     76  dbghl_composed = syn_check_group(S_LEN("RedrawDebugComposed"));
     77  dbghl_recompose = syn_check_group(S_LEN("RedrawDebugRecompose"));
     78 }
     79 
     80 void ui_comp_attach(RemoteUI *ui)
     81 {
     82  composed_uis++;
     83  ui->composed = true;
     84 }
     85 
     86 void ui_comp_detach(RemoteUI *ui)
     87 {
     88  composed_uis--;
     89  if (composed_uis == 0) {
     90    XFREE_CLEAR(linebuf);
     91    XFREE_CLEAR(attrbuf);
     92    bufsize = 0;
     93  }
     94  ui->composed = false;
     95 }
     96 
     97 bool ui_comp_should_draw(void)
     98 {
     99  return composed_uis != 0 && valid_screen;
    100 }
    101 
    102 /// Raises or lowers the layer, syncing comp_index with zindex.
    103 ///
    104 /// This function adjusts the position of a layer in the layers array
    105 /// based on its zindex, either raising or lowering it.
    106 ///
    107 /// @param[in]  layer_idx  Index of the layer to be raised or lowered.
    108 /// @param[in]  raise      Raise the layer if true, else lower it.
    109 void ui_comp_layers_adjust(size_t layer_idx, bool raise)
    110 {
    111  size_t size = layers.size;
    112  ScreenGrid *layer = layers.items[layer_idx];
    113 
    114  if (raise) {
    115    while (layer_idx < size - 1 && layer->zindex > layers.items[layer_idx + 1]->zindex) {
    116      layers.items[layer_idx] = layers.items[layer_idx + 1];
    117      layers.items[layer_idx]->comp_index = layer_idx;
    118      layers.items[layer_idx]->pending_comp_index_update = true;
    119      layer_idx++;
    120    }
    121  } else {
    122    while (layer_idx > 0 && layer->zindex < layers.items[layer_idx - 1]->zindex) {
    123      layers.items[layer_idx] = layers.items[layer_idx - 1];
    124      layers.items[layer_idx]->comp_index = layer_idx;
    125      layers.items[layer_idx]->pending_comp_index_update = true;
    126      layer_idx--;
    127    }
    128  }
    129  layers.items[layer_idx] = layer;
    130  layer->comp_index = layer_idx;
    131  layer->pending_comp_index_update = true;
    132 }
    133 
    134 /// Places `grid` at (col,row) position with (width * height) size.
    135 /// Adds `grid` as the top layer if it is a new layer.
    136 ///
    137 /// TODO(bfredl): later on the compositor should just use win_float_pos events,
    138 /// though that will require slight event order adjustment: emit the win_pos
    139 /// events in the beginning of update_screen(), rather than in ui_flush()
    140 bool ui_comp_put_grid(ScreenGrid *grid, int row, int col, int height, int width, bool valid,
    141                      bool on_top)
    142 {
    143  bool moved;
    144  grid->pending_comp_index_update = true;
    145 
    146  grid->comp_height = height;
    147  grid->comp_width = width;
    148  if (grid->comp_index != 0) {
    149    moved = (row != grid->comp_row) || (col != grid->comp_col);
    150    if (ui_comp_should_draw()) {
    151      // Redraw the area covered by the old position, and is not covered
    152      // by the new position. Disable the grid so that compose_area() will not
    153      // use it.
    154      grid->comp_disabled = true;
    155      compose_area(grid->comp_row, row,
    156                   grid->comp_col, grid->comp_col + grid->cols);
    157      if (grid->comp_col < col) {
    158        compose_area(MAX(row, grid->comp_row),
    159                     MIN(row + height, grid->comp_row + grid->rows),
    160                     grid->comp_col, col);
    161      }
    162      if (col + width < grid->comp_col + grid->cols) {
    163        compose_area(MAX(row, grid->comp_row),
    164                     MIN(row + height, grid->comp_row + grid->rows),
    165                     col + width, grid->comp_col + grid->cols);
    166      }
    167      compose_area(row + height, grid->comp_row + grid->rows,
    168                   grid->comp_col, grid->comp_col + grid->cols);
    169      grid->comp_disabled = false;
    170    }
    171    grid->comp_row = row;
    172    grid->comp_col = col;
    173  } else {
    174    moved = true;
    175 #ifndef NDEBUG
    176    for (size_t i = 0; i < kv_size(layers); i++) {
    177      if (kv_A(layers, i) == grid) {
    178        abort();
    179      }
    180    }
    181 #endif
    182 
    183    size_t insert_at = kv_size(layers);
    184    while (insert_at > 0 && kv_A(layers, insert_at - 1)->zindex > grid->zindex) {
    185      insert_at--;
    186    }
    187 
    188    if (curwin && kv_A(layers, insert_at - 1) == &curwin->w_grid_alloc
    189        && kv_A(layers, insert_at - 1)->zindex == grid->zindex
    190        && !on_top) {
    191      insert_at--;
    192    }
    193    // not found: new grid
    194    kv_pushp(layers);
    195    for (size_t i = kv_size(layers) - 1; i > insert_at; i--) {
    196      kv_A(layers, i) = kv_A(layers, i - 1);
    197      kv_A(layers, i)->comp_index = i;
    198      kv_A(layers, i)->pending_comp_index_update = true;
    199    }
    200    kv_A(layers, insert_at) = grid;
    201 
    202    grid->comp_row = row;
    203    grid->comp_col = col;
    204    grid->comp_index = insert_at;
    205    grid->pending_comp_index_update = true;
    206  }
    207  if (moved && valid && ui_comp_should_draw()) {
    208    compose_area(grid->comp_row, grid->comp_row + grid->rows,
    209                 grid->comp_col, grid->comp_col + grid->cols);
    210  }
    211  return moved;
    212 }
    213 
    214 void ui_comp_remove_grid(ScreenGrid *grid)
    215 {
    216  assert(grid != &default_grid);
    217  if (grid->comp_index == 0) {
    218    // grid wasn't present
    219    return;
    220  }
    221 
    222  if (curgrid == grid) {
    223    curgrid = &default_grid;
    224  }
    225 
    226  for (size_t i = grid->comp_index; i < kv_size(layers) - 1; i++) {
    227    kv_A(layers, i) = kv_A(layers, i + 1);
    228    kv_A(layers, i)->comp_index = i;
    229    kv_A(layers, i)->pending_comp_index_update = true;
    230  }
    231  (void)kv_pop(layers);
    232  grid->comp_index = 0;
    233  grid->pending_comp_index_update = true;
    234 
    235  // recompose the area under the grid
    236  // inefficient when being overlapped: only draw up to grid->comp_index
    237  ui_comp_compose_grid(grid);
    238 }
    239 
    240 bool ui_comp_set_grid(handle_T handle)
    241 {
    242  if (curgrid->handle == handle) {
    243    return true;
    244  }
    245  ScreenGrid *grid = NULL;
    246  for (size_t i = 0; i < kv_size(layers); i++) {
    247    if (kv_A(layers, i)->handle == handle) {
    248      grid = kv_A(layers, i);
    249      break;
    250    }
    251  }
    252  if (grid != NULL) {
    253    curgrid = grid;
    254    return true;
    255  }
    256  return false;
    257 }
    258 
    259 void ui_comp_raise_grid(ScreenGrid *grid, size_t new_index)
    260 {
    261  size_t old_index = grid->comp_index;
    262  for (size_t i = old_index; i < new_index; i++) {
    263    kv_A(layers, i) = kv_A(layers, i + 1);
    264    kv_A(layers, i)->comp_index = i;
    265    kv_A(layers, i)->pending_comp_index_update = true;
    266  }
    267  kv_A(layers, new_index) = grid;
    268  grid->comp_index = new_index;
    269  grid->pending_comp_index_update = true;
    270  for (size_t i = old_index; i < new_index; i++) {
    271    ScreenGrid *grid2 = kv_A(layers, i);
    272    int startcol = MAX(grid->comp_col, grid2->comp_col);
    273    int endcol = MIN(grid->comp_col + grid->cols,
    274                     grid2->comp_col + grid2->cols);
    275    compose_area(MAX(grid->comp_row, grid2->comp_row),
    276                 MIN(grid->comp_row + grid->rows, grid2->comp_row + grid2->rows),
    277                 startcol, endcol);
    278  }
    279 }
    280 
    281 void ui_comp_grid_cursor_goto(Integer grid_handle, Integer r, Integer c)
    282 {
    283  if (!ui_comp_set_grid((int)grid_handle)) {
    284    return;
    285  }
    286  int cursor_row = curgrid->comp_row + (int)r;
    287  int cursor_col = curgrid->comp_col + (int)c;
    288 
    289  // TODO(bfredl): maybe not the best time to do this, for efficiency we
    290  // should configure all grids before entering win_update()
    291  if (curgrid != &default_grid) {
    292    size_t new_index = kv_size(layers) - 1;
    293 
    294    while (new_index > 1 && kv_A(layers, new_index)->zindex > curgrid->zindex) {
    295      new_index--;
    296    }
    297 
    298    if (curgrid->comp_index < new_index) {
    299      ui_comp_raise_grid(curgrid, new_index);
    300    }
    301  }
    302 
    303  if (cursor_col >= default_grid.cols || cursor_row >= default_grid.rows) {
    304    // TODO(bfredl): this happens with 'writedelay', refactor?
    305    // abort();
    306    return;
    307  }
    308  ui_composed_call_grid_cursor_goto(1, cursor_row, cursor_col);
    309 }
    310 
    311 ScreenGrid *ui_comp_mouse_focus(int row, int col)
    312 {
    313  for (ssize_t i = (ssize_t)kv_size(layers) - 1; i > 0; i--) {
    314    ScreenGrid *grid = kv_A(layers, i);
    315    if (grid->mouse_enabled
    316        && row >= grid->comp_row && row < grid->comp_row + grid->rows
    317        && col >= grid->comp_col && col < grid->comp_col + grid->cols) {
    318      return grid;
    319    }
    320  }
    321  if (ui_has(kUIMultigrid)) {
    322    FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
    323      ScreenGrid *grid = &wp->w_grid_alloc;
    324      if (grid->mouse_enabled && row >= wp->w_winrow && row < wp->w_winrow + grid->rows
    325          && col >= wp->w_wincol && col < wp->w_wincol + grid->cols) {
    326        return grid;
    327      }
    328    }
    329  }
    330  return NULL;
    331 }
    332 
    333 /// Compute which grid is on top at supplied screen coordinates
    334 ScreenGrid *ui_comp_get_grid_at_coord(int row, int col)
    335 {
    336  for (ssize_t i = (ssize_t)kv_size(layers) - 1; i > 0; i--) {
    337    ScreenGrid *grid = kv_A(layers, i);
    338    if (row >= grid->comp_row && row < grid->comp_row + grid->rows
    339        && col >= grid->comp_col && col < grid->comp_col + grid->cols) {
    340      return grid;
    341    }
    342  }
    343 
    344  FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
    345    ScreenGrid *grid = &wp->w_grid_alloc;
    346    if (row >= grid->comp_row && row < grid->comp_row + grid->rows
    347        && col >= grid->comp_col && col < grid->comp_col + grid->cols
    348        && !wp->w_config.hide) {
    349      return grid;
    350    }
    351  }
    352  return &default_grid;
    353 }
    354 
    355 /// Baseline implementation. This is always correct, but we can sometimes
    356 /// do something more efficient (where efficiency means smaller deltas to
    357 /// the downstream UI.)
    358 static void compose_line(Integer row, Integer startcol, Integer endcol, LineFlags flags)
    359 {
    360  // If rightleft is set, startcol may be -1. In such cases, the assertions
    361  // will fail because no overlap is found. Adjust startcol to prevent it.
    362  startcol = MAX(startcol, 0);
    363  // in case we start on the right half of a double-width char, we need to
    364  // check the left half. But skip it in output if it wasn't doublewidth.
    365  int skipstart = 0;
    366  int skipend = 0;
    367  if (startcol > 0 && (flags & kLineFlagInvalid)) {
    368    startcol--;
    369    skipstart = 1;
    370  }
    371  if (endcol < default_grid.cols && (flags & kLineFlagInvalid)) {
    372    endcol++;
    373    skipend = 1;
    374  }
    375 
    376  int col = (int)startcol;
    377  ScreenGrid *grid = NULL;
    378  schar_T *bg_line = &default_grid.chars[default_grid.line_offset[row]
    379                                         + (size_t)startcol];
    380  sattr_T *bg_attrs = &default_grid.attrs[default_grid.line_offset[row]
    381                                          + (size_t)startcol];
    382 
    383  while (col < endcol) {
    384    int until = 0;
    385    for (size_t i = 0; i < kv_size(layers); i++) {
    386      ScreenGrid *g = kv_A(layers, i);
    387      // compose_line may have been called after a shrinking operation but
    388      // before the resize has actually been applied. Therefore, we need to
    389      // first check to see if any grids have pending updates to width/height,
    390      // to ensure that we don't accidentally put any characters into `linebuf`
    391      // that have been invalidated.
    392      int grid_width = MIN(g->cols, g->comp_width);
    393      int grid_height = MIN(g->rows, g->comp_height);
    394      if (g->comp_row > row || row >= g->comp_row + grid_height
    395          || g->comp_disabled) {
    396        continue;
    397      }
    398      if (g->comp_col <= col && col < g->comp_col + grid_width) {
    399        grid = g;
    400        until = g->comp_col + grid_width;
    401      } else if (g->comp_col > col) {
    402        until = MIN(until, g->comp_col);
    403      }
    404    }
    405    until = MIN(until, (int)endcol);
    406 
    407    assert(grid != NULL);
    408    assert(until > col);
    409    assert(until <= default_grid.cols);
    410    size_t n = (size_t)(until - col);
    411 
    412    if (row == msg_sep_row && grid->comp_index <= msg_grid.comp_index) {
    413      // TODO(bfredl): when we implement borders around floating windows, then
    414      // msgsep can just be a border "around" the message grid.
    415      grid = &msg_grid;
    416      sattr_T msg_sep_attr = (sattr_T)HL_ATTR(HLF_MSGSEP);
    417      for (int i = col; i < until; i++) {
    418        linebuf[i - startcol] = msg_sep_char;
    419        attrbuf[i - startcol] = msg_sep_attr;
    420      }
    421    } else {
    422      size_t off = grid->line_offset[row - grid->comp_row]
    423                   + (size_t)(col - grid->comp_col);
    424      memcpy(linebuf + (col - startcol), grid->chars + off, n * sizeof(*linebuf));
    425      memcpy(attrbuf + (col - startcol), grid->attrs + off, n * sizeof(*attrbuf));
    426      if (grid->comp_col + grid->cols > until
    427          && grid->chars[off + n] == NUL) {
    428        linebuf[until - 1 - startcol] = schar_from_ascii(' ');
    429        if (col == startcol && n == 1) {
    430          skipstart = 0;
    431        }
    432      }
    433    }
    434 
    435    // 'pumblend' and 'winblend'
    436    if (grid->blending) {
    437      int width;
    438      for (int i = col - (int)startcol; i < until - startcol; i += width) {
    439        width = 1;
    440        // negative space
    441        bool thru = (linebuf[i] == schar_from_ascii(' ')
    442                     || linebuf[i] == schar_from_char(L'\u2800')) && bg_line[i] != NUL;
    443        if (i + 1 < endcol - startcol && bg_line[i + 1] == NUL) {
    444          width = 2;
    445          thru &= (linebuf[i + 1] == schar_from_ascii(' ')
    446                   || linebuf[i + 1] == schar_from_char(L'\u2800'));
    447        }
    448        attrbuf[i] = (sattr_T)hl_blend_attrs(bg_attrs[i], attrbuf[i], &thru);
    449        if (width == 2) {
    450          attrbuf[i + 1] = (sattr_T)hl_blend_attrs(bg_attrs[i + 1],
    451                                                   attrbuf[i + 1], &thru);
    452        }
    453        if (thru) {
    454          memcpy(linebuf + i, bg_line + i, (size_t)width * sizeof(linebuf[i]));
    455        }
    456      }
    457    }
    458 
    459    // Tricky: if overlap caused a doublewidth char to get cut-off, must
    460    // replace the visible half with a space.
    461    if (linebuf[col - startcol] == NUL) {
    462      linebuf[col - startcol] = schar_from_ascii(' ');
    463      if (col == endcol - 1) {
    464        skipend = 0;
    465      }
    466    } else if (col == startcol && n > 1 && linebuf[1] == NUL) {
    467      skipstart = 0;
    468    }
    469 
    470    col = until;
    471  }
    472  if (linebuf[endcol - startcol - 1] == NUL) {
    473    skipend = 0;
    474  }
    475 
    476  assert(endcol <= chk_width);
    477  assert(row < chk_height);
    478 
    479  if (!(grid && (grid == &default_grid || (grid->comp_col == 0 && grid->cols == Columns)))) {
    480    flags = flags & ~kLineFlagWrap;
    481  }
    482 
    483  for (int i = skipstart; i < (endcol - skipend) - startcol; i++) {
    484    if (attrbuf[i] < 0) {
    485      if (rdb_flags & kOptRdbFlagInvalid) {
    486        abort();
    487      } else {
    488        attrbuf[i] = 0;
    489      }
    490    }
    491  }
    492  ui_composed_call_raw_line(1, row, startcol + skipstart,
    493                            endcol - skipend, endcol - skipend, 0, flags,
    494                            (const schar_T *)linebuf + skipstart,
    495                            (const sattr_T *)attrbuf + skipstart);
    496 }
    497 
    498 static void compose_debug(Integer startrow, Integer endrow, Integer startcol, Integer endcol,
    499                          int syn_id, bool delay)
    500 {
    501  if (!(rdb_flags & kOptRdbFlagCompositor) || startcol >= endcol) {
    502    return;
    503  }
    504 
    505  endrow = MIN(endrow, default_grid.rows);
    506  endcol = MIN(endcol, default_grid.cols);
    507  int attr = syn_id2attr(syn_id);
    508 
    509  if (delay) {
    510    debug_delay(endrow - startrow);
    511  }
    512 
    513  for (int row = (int)startrow; row < endrow; row++) {
    514    ui_composed_call_raw_line(1, row, startcol, startcol, endcol, attr, false,
    515                              (const schar_T *)linebuf,
    516                              (const sattr_T *)attrbuf);
    517  }
    518 
    519  if (delay) {
    520    debug_delay(endrow - startrow);
    521  }
    522 }
    523 
    524 static void debug_delay(Integer lines)
    525 {
    526  ui_call_flush();
    527  uint64_t wd = (uint64_t)llabs(p_wd);
    528  uint64_t factor = (uint64_t)MAX(MIN(lines, 5), 1);
    529  os_sleep(factor * wd);
    530 }
    531 
    532 static void compose_area(Integer startrow, Integer endrow, Integer startcol, Integer endcol)
    533 {
    534  compose_debug(startrow, endrow, startcol, endcol, dbghl_recompose, true);
    535  endrow = MIN(endrow, default_grid.rows);
    536  endcol = MIN(endcol, default_grid.cols);
    537  if (endcol <= startcol) {
    538    return;
    539  }
    540  for (int r = (int)startrow; r < endrow; r++) {
    541    compose_line(r, startcol, endcol, kLineFlagInvalid);
    542  }
    543 }
    544 
    545 /// compose the area under the grid.
    546 ///
    547 /// This is needed when some option affecting composition is changed,
    548 /// such as 'pumblend' for popupmenu grid.
    549 void ui_comp_compose_grid(ScreenGrid *grid)
    550 {
    551  if (ui_comp_should_draw()) {
    552    compose_area(grid->comp_row, grid->comp_row + grid->rows,
    553                 grid->comp_col, grid->comp_col + grid->cols);
    554  }
    555 }
    556 
    557 void ui_comp_raw_line(Integer grid, Integer row, Integer startcol, Integer endcol, Integer clearcol,
    558                      Integer clearattr, LineFlags flags, const schar_T *chunk,
    559                      const sattr_T *attrs)
    560 {
    561  if (!ui_comp_should_draw() || !ui_comp_set_grid((int)grid)) {
    562    return;
    563  }
    564 
    565  row += curgrid->comp_row;
    566  startcol += curgrid->comp_col;
    567  endcol += curgrid->comp_col;
    568  clearcol += curgrid->comp_col;
    569  if (curgrid != &default_grid) {
    570    flags = flags & ~kLineFlagWrap;
    571  }
    572 
    573  assert(endcol <= clearcol);
    574 
    575  // TODO(bfredl): this should not really be necessary. But on some condition
    576  // when resizing nvim, a window will be attempted to be drawn on the older
    577  // and possibly larger global screen size.
    578  if (row >= default_grid.rows) {
    579    DLOG("compositor: invalid row %" PRId64 " on grid %" PRId64, row, grid);
    580    return;
    581  }
    582  if (clearcol > default_grid.cols) {
    583    DLOG("compositor: invalid last column %" PRId64 " on grid %" PRId64,
    584         clearcol, grid);
    585    if (startcol >= default_grid.cols) {
    586      return;
    587    }
    588    clearcol = default_grid.cols;
    589    endcol = MIN(endcol, clearcol);
    590  }
    591 
    592  bool covered = curgrid_covered_above((int)row);
    593  // TODO(bfredl): eventually should just fix compose_line to respect clearing
    594  // and optimize it for uncovered lines.
    595  if (flags & kLineFlagInvalid || covered || curgrid->blending) {
    596    compose_debug(row, row + 1, startcol, clearcol, dbghl_composed, true);
    597    compose_line(row, startcol, clearcol, flags);
    598  } else {
    599    compose_debug(row, row + 1, startcol, endcol, dbghl_normal, endcol >= clearcol);
    600    compose_debug(row, row + 1, endcol, clearcol, dbghl_clear, true);
    601 #ifndef NDEBUG
    602    for (int i = 0; i < endcol - startcol; i++) {
    603      assert(attrs[i] >= 0);
    604    }
    605 #endif
    606    ui_composed_call_raw_line(1, row, startcol, endcol, clearcol, clearattr,
    607                              flags, chunk, attrs);
    608  }
    609 }
    610 
    611 /// The screen is invalid and will soon be cleared
    612 ///
    613 /// Don't redraw floats until screen is cleared
    614 bool ui_comp_set_screen_valid(bool valid)
    615 {
    616  bool old_val = valid_screen;
    617  valid_screen = valid;
    618  if (!valid) {
    619    msg_sep_row = -1;
    620  }
    621  return old_val;
    622 }
    623 
    624 void ui_comp_msg_set_pos(Integer grid, Integer row, Boolean scrolled, String sep_char,
    625                         Integer zindex, Integer compindex)
    626 {
    627  msg_grid.pending_comp_index_update = true;
    628  msg_grid.comp_row = (int)row;
    629  if (scrolled && row > 0) {
    630    msg_sep_row = (int)row - 1;
    631    if (sep_char.data) {
    632      msg_sep_char = schar_from_buf(sep_char.data, sep_char.size);
    633    }
    634  } else {
    635    msg_sep_row = -1;
    636  }
    637 
    638  if (row > msg_current_row && ui_comp_should_draw()) {
    639    compose_area(MAX(msg_current_row - 1, 0), row, 0, default_grid.cols);
    640  } else if (row < msg_current_row && ui_comp_should_draw()
    641             && (msg_current_row < Rows || (scrolled && !msg_was_scrolled))) {
    642    int delta = msg_current_row - (int)row;
    643    if (msg_grid.blending) {
    644      int first_row = MAX((int)row - (scrolled ? 1 : 0), 0);
    645      compose_area(first_row, Rows - delta, 0, Columns);
    646    } else {
    647      // scroll separator together with message text
    648      int first_row = MAX((int)row - (msg_was_scrolled ? 1 : 0), 0);
    649      ui_composed_call_grid_scroll(1, first_row, Rows, 0, Columns, delta, 0);
    650      if (scrolled && !msg_was_scrolled && row > 0) {
    651        compose_area(row - 1, row, 0, Columns);
    652      }
    653    }
    654  }
    655 
    656  msg_current_row = (int)row;
    657  msg_was_scrolled = scrolled;
    658 }
    659 
    660 /// check if curgrid is covered on row or above
    661 ///
    662 /// TODO(bfredl): currently this only handles message row
    663 static bool curgrid_covered_above(int row)
    664 {
    665  bool above_msg = (kv_A(layers, kv_size(layers) - 1) == &msg_grid
    666                    && row < msg_current_row - (msg_was_scrolled ? 1 : 0));
    667  return kv_size(layers) - (above_msg ? 1 : 0) > curgrid->comp_index + 1;
    668 }
    669 
    670 void ui_comp_grid_scroll(Integer grid, Integer top, Integer bot, Integer left, Integer right,
    671                         Integer rows, Integer cols)
    672 {
    673  if (!ui_comp_should_draw() || !ui_comp_set_grid((int)grid)) {
    674    return;
    675  }
    676  top += curgrid->comp_row;
    677  bot += curgrid->comp_row;
    678  left += curgrid->comp_col;
    679  right += curgrid->comp_col;
    680  bool covered = curgrid_covered_above((int)(bot - MAX(rows, 0)));
    681 
    682  if (covered || curgrid->blending) {
    683    // TODO(bfredl):
    684    // 1. check if rectangles actually overlap
    685    // 2. calculate subareas that can scroll.
    686    compose_debug(top, bot, left, right, dbghl_recompose, true);
    687    for (int r = (int)(top + MAX(-rows, 0)); r < bot - MAX(rows, 0); r++) {
    688      // TODO(bfredl): workaround for win_update() performing two scrolls in a
    689      // row, where the latter might scroll invalid space created by the first.
    690      // ideally win_update() should keep track of this itself and not scroll
    691      // the invalid space.
    692      if (curgrid->attrs[curgrid->line_offset[r - curgrid->comp_row]
    693                         + (size_t)left - (size_t)curgrid->comp_col] >= 0) {
    694        compose_line(r, left, right, 0);
    695      }
    696    }
    697  } else {
    698    ui_composed_call_grid_scroll(1, top, bot, left, right, rows, cols);
    699    if (rdb_flags & kOptRdbFlagCompositor) {
    700      debug_delay(2);
    701    }
    702  }
    703 }
    704 
    705 void ui_comp_grid_resize(Integer grid, Integer width, Integer height)
    706 {
    707  if (grid == 1) {
    708    ui_composed_call_grid_resize(1, width, height);
    709 #ifndef NDEBUG
    710    chk_width = (int)width;
    711    chk_height = (int)height;
    712 #endif
    713    size_t new_bufsize = (size_t)width;
    714    if (bufsize != new_bufsize) {
    715      xfree(linebuf);
    716      xfree(attrbuf);
    717      linebuf = xmalloc(new_bufsize * sizeof(*linebuf));
    718      attrbuf = xmalloc(new_bufsize * sizeof(*attrbuf));
    719      bufsize = new_bufsize;
    720    }
    721  }
    722 }