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