1#include "vterm_internal.h"
2
3#include <stdio.h>
4#include <string.h>
5
6#include "rect.h"
7#include "utf8.h"
8
9#define UNICODE_SPACE 0x20
10#define UNICODE_LINEFEED 0x0a
11
12/* State of the pen at some moment in time, also used in a cell */
13typedef struct
14{
15  /* After the bitfield */
16  VTermColor   fg, bg;
17
18  unsigned int bold      : 1;
19  unsigned int underline : 2;
20  unsigned int italic    : 1;
21  unsigned int blink     : 1;
22  unsigned int reverse   : 1;
23  unsigned int strike    : 1;
24  unsigned int font      : 4; /* 0 to 9 */
25
26  /* Extra state storage that isn't strictly pen-related */
27  unsigned int protected_cell : 1;
28} ScreenPen;
29
30/* Internal representation of a screen cell */
31typedef struct
32{
33  uint32_t chars[VTERM_MAX_CHARS_PER_CELL];
34  ScreenPen pen;
35} ScreenCell;
36
37static int vterm_screen_set_cell(VTermScreen *screen, VTermPos pos, const VTermScreenCell *cell);
38
39struct VTermScreen
40{
41  VTerm *vt;
42  VTermState *state;
43
44  const VTermScreenCallbacks *callbacks;
45  void *cbdata;
46
47  VTermDamageSize damage_merge;
48  /* start_row == -1 => no damage */
49  VTermRect damaged;
50  VTermRect pending_scrollrect;
51  int pending_scroll_downward, pending_scroll_rightward;
52
53  int rows;
54  int cols;
55  int global_reverse;
56
57  /* Primary and Altscreen. buffers[1] is lazily allocated as needed */
58  ScreenCell *buffers[2];
59
60  /* buffer will == buffers[0] or buffers[1], depending on altscreen */
61  ScreenCell *buffer;
62
63  /* buffer for a single screen row used in scrollback storage callbacks */
64  VTermScreenCell *sb_buffer;
65
66  ScreenPen pen;
67};
68
69static inline ScreenCell *getcell(const VTermScreen *screen, int row, int col)
70{
71  if(row < 0 || row >= screen->rows)
72    return NULL;
73  if(col < 0 || col >= screen->cols)
74    return NULL;
75  return screen->buffer + (screen->cols * row) + col;
76}
77
78static ScreenCell *realloc_buffer(VTermScreen *screen, ScreenCell *buffer, int new_rows, int new_cols)
79{
80  ScreenCell *new_buffer = vterm_allocator_malloc(screen->vt, sizeof(ScreenCell) * new_rows * new_cols);
81
82  for(int row = 0; row < new_rows; row++) {
83    for(int col = 0; col < new_cols; col++) {
84      ScreenCell *new_cell = new_buffer + row*new_cols + col;
85
86      if(buffer && row < screen->rows && col < screen->cols)
87        *new_cell = buffer[row * screen->cols + col];
88      else {
89        new_cell->chars[0] = 0;
90        new_cell->pen = screen->pen;
91      }
92    }
93  }
94
95  if(buffer)
96    vterm_allocator_free(screen->vt, buffer);
97
98  return new_buffer;
99}
100
101static void damagerect(VTermScreen *screen, VTermRect rect)
102{
103  VTermRect emit;
104
105  switch(screen->damage_merge) {
106  case VTERM_DAMAGE_CELL:
107    /* Always emit damage event */
108    emit = rect;
109    break;
110
111  case VTERM_DAMAGE_ROW:
112    /* Emit damage longer than one row. Try to merge with existing damage in
113     * the same row */
114    if(rect.end_row > rect.start_row + 1) {
115      // Bigger than 1 line - flush existing, emit this
116      vterm_screen_flush_damage(screen);
117      emit = rect;
118    }
119    else if(screen->damaged.start_row == -1) {
120      // None stored yet
121      screen->damaged = rect;
122      return;
123    }
124    else if(rect.start_row == screen->damaged.start_row) {
125      // Merge with the stored line
126      if(screen->damaged.start_col > rect.start_col)
127        screen->damaged.start_col = rect.start_col;
128      if(screen->damaged.end_col < rect.end_col)
129        screen->damaged.end_col = rect.end_col;
130      return;
131    }
132    else {
133      // Emit the currently stored line, store a new one
134      emit = screen->damaged;
135      screen->damaged = rect;
136    }
137    break;
138
139  case VTERM_DAMAGE_SCREEN:
140  case VTERM_DAMAGE_SCROLL:
141    /* Never emit damage event */
142    if(screen->damaged.start_row == -1)
143      screen->damaged = rect;
144    else {
145      rect_expand(&screen->damaged, &rect);
146    }
147    return;
148
149  default:
150    fprintf(stderr, "TODO: Maybe merge damage for level %d\n", screen->damage_merge);
151    return;
152  }
153
154  if(screen->callbacks && screen->callbacks->damage)
155    (*screen->callbacks->damage)(emit, screen->cbdata);
156}
157
158static void damagescreen(VTermScreen *screen)
159{
160  VTermRect rect = {
161    .start_row = 0,
162    .end_row   = screen->rows,
163    .start_col = 0,
164    .end_col   = screen->cols,
165  };
166
167  damagerect(screen, rect);
168}
169
170static int putglyph(VTermGlyphInfo *info, VTermPos pos, void *user)
171{
172  VTermScreen *screen = user;
173  ScreenCell *cell = getcell(screen, pos.row, pos.col);
174
175  if(!cell)
176    return 0;
177
178  int i;
179  for(i = 0; i < VTERM_MAX_CHARS_PER_CELL && info->chars[i]; i++) {
180    cell->chars[i] = info->chars[i];
181    cell->pen = screen->pen;
182  }
183  if(i < VTERM_MAX_CHARS_PER_CELL)
184    cell->chars[i] = 0;
185
186  for(int col = 1; col < info->width; col++)
187    getcell(screen, pos.row, pos.col + col)->chars[0] = (uint32_t)-1;
188
189  VTermRect rect = {
190    .start_row = pos.row,
191    .end_row   = pos.row+1,
192    .start_col = pos.col,
193    .end_col   = pos.col+info->width,
194  };
195
196  cell->pen.protected_cell = info->protected_cell;
197
198  damagerect(screen, rect);
199
200  return 1;
201}
202
203static int moverect_internal(VTermRect dest, VTermRect src, void *user)
204{
205  VTermScreen *screen = user;
206
207  if(screen->callbacks && screen->callbacks->sb_pushline &&
208     dest.start_row == 0 && dest.start_col == 0 &&  // starts top-left corner
209     dest.end_col == screen->cols &&                // full width
210     screen->buffer == screen->buffers[0]) {        // not altscreen
211    VTermPos pos;
212    for(pos.row = 0; pos.row < src.start_row; pos.row++) {
213      for(pos.col = 0; pos.col < screen->cols; pos.col++)
214        vterm_screen_get_cell(screen, pos, screen->sb_buffer + pos.col);
215
216      (screen->callbacks->sb_pushline)(screen->cols, screen->sb_buffer, screen->cbdata);
217    }
218  }
219
220  int cols = src.end_col - src.start_col;
221  int downward = src.start_row - dest.start_row;
222
223  int init_row, test_row, inc_row;
224  if(downward < 0) {
225    init_row = dest.end_row - 1;
226    test_row = dest.start_row - 1;
227    inc_row  = -1;
228  }
229  else {
230    init_row = dest.start_row;
231    test_row = dest.end_row;
232    inc_row  = +1;
233  }
234
235  for(int row = init_row; row != test_row; row += inc_row)
236    memmove(getcell(screen, row, dest.start_col),
237            getcell(screen, row + downward, src.start_col),
238            cols * sizeof(ScreenCell));
239
240  return 1;
241}
242
243static int moverect_user(VTermRect dest, VTermRect src, void *user)
244{
245  VTermScreen *screen = user;
246
247  if(screen->callbacks && screen->callbacks->moverect) {
248    if(screen->damage_merge != VTERM_DAMAGE_SCROLL)
249      // Avoid an infinite loop
250      vterm_screen_flush_damage(screen);
251
252    if((*screen->callbacks->moverect)(dest, src, screen->cbdata))
253      return 1;
254  }
255
256  damagerect(screen, dest);
257
258  return 1;
259}
260
261static int erase_internal(VTermRect rect, int selective, void *user)
262{
263  VTermScreen *screen = user;
264
265  for(int row = rect.start_row; row < rect.end_row; row++)
266    for(int col = rect.start_col; col < rect.end_col; col++) {
267      ScreenCell *cell = getcell(screen, row, col);
268
269      if(selective && cell->pen.protected_cell)
270        continue;
271
272      cell->chars[0] = 0;
273      cell->pen = screen->pen;
274    }
275
276  return 1;
277}
278
279static int erase_user(VTermRect rect, int selective, void *user)
280{
281  VTermScreen *screen = user;
282
283  damagerect(screen, rect);
284
285  return 1;
286}
287
288static int erase(VTermRect rect, int selective, void *user)
289{
290  erase_internal(rect, selective, user);
291  return erase_user(rect, 0, user);
292}
293
294static int scrollrect(VTermRect rect, int downward, int rightward, void *user)
295{
296  VTermScreen *screen = user;
297
298  vterm_scroll_rect(rect, downward, rightward,
299      moverect_internal, erase_internal, screen);
300
301  if(screen->damage_merge != VTERM_DAMAGE_SCROLL) {
302    vterm_screen_flush_damage(screen);
303
304    vterm_scroll_rect(rect, downward, rightward,
305        moverect_user, erase_user, screen);
306
307    return 1;
308  }
309
310  if(screen->damaged.start_row != -1 &&
311     !rect_intersects(&rect, &screen->damaged)) {
312    vterm_screen_flush_damage(screen);
313  }
314
315  if(screen->pending_scrollrect.start_row == -1) {
316    screen->pending_scrollrect = rect;
317    screen->pending_scroll_downward  = downward;
318    screen->pending_scroll_rightward = rightward;
319  }
320  else if(rect_equal(&screen->pending_scrollrect, &rect) &&
321     ((screen->pending_scroll_downward  == 0 && downward  == 0) ||
322      (screen->pending_scroll_rightward == 0 && rightward == 0))) {
323    screen->pending_scroll_downward  += downward;
324    screen->pending_scroll_rightward += rightward;
325  }
326  else {
327    vterm_screen_flush_damage(screen);
328
329    screen->pending_scrollrect = rect;
330    screen->pending_scroll_downward  = downward;
331    screen->pending_scroll_rightward = rightward;
332  }
333
334  if(screen->damaged.start_row == -1)
335    return 1;
336
337  if(rect_contains(&rect, &screen->damaged)) {
338    vterm_rect_move(&screen->damaged, -downward, -rightward);
339    rect_clip(&screen->damaged, &rect);
340  }
341  /* There are a number of possible cases here, but lets restrict this to only
342   * the common case where we might actually gain some performance by
343   * optimising it. Namely, a vertical scroll that neatly cuts the damage
344   * region in half.
345   */
346  else if(rect.start_col <= screen->damaged.start_col &&
347          rect.end_col   >= screen->damaged.end_col &&
348          rightward == 0) {
349    if(screen->damaged.start_row >= rect.start_row &&
350       screen->damaged.start_row  < rect.end_row) {
351      screen->damaged.start_row -= downward;
352      if(screen->damaged.start_row < rect.start_row)
353        screen->damaged.start_row = rect.start_row;
354      if(screen->damaged.start_row > rect.end_row)
355        screen->damaged.start_row = rect.end_row;
356    }
357    if(screen->damaged.end_row >= rect.start_row &&
358       screen->damaged.end_row  < rect.end_row) {
359      screen->damaged.end_row -= downward;
360      if(screen->damaged.end_row < rect.start_row)
361        screen->damaged.end_row = rect.start_row;
362      if(screen->damaged.end_row > rect.end_row)
363        screen->damaged.end_row = rect.end_row;
364    }
365  }
366  else {
367    fprintf(stderr, "TODO: Just flush and redo damaged=" STRFrect " rect=" STRFrect "\n",
368        ARGSrect(screen->damaged), ARGSrect(rect));
369  }
370
371  return 1;
372}
373
374static int movecursor(VTermPos pos, VTermPos oldpos, int visible, void *user)
375{
376  VTermScreen *screen = user;
377
378  if(screen->callbacks && screen->callbacks->movecursor)
379    return (*screen->callbacks->movecursor)(pos, oldpos, visible, screen->cbdata);
380
381  return 0;
382}
383
384static int setpenattr(VTermAttr attr, VTermValue *val, void *user)
385{
386  VTermScreen *screen = user;
387
388  switch(attr) {
389  case VTERM_ATTR_BOLD:
390    screen->pen.bold = val->boolean;
391    return 1;
392  case VTERM_ATTR_UNDERLINE:
393    screen->pen.underline = val->number;
394    return 1;
395  case VTERM_ATTR_ITALIC:
396    screen->pen.italic = val->boolean;
397    return 1;
398  case VTERM_ATTR_BLINK:
399    screen->pen.blink = val->boolean;
400    return 1;
401  case VTERM_ATTR_REVERSE:
402    screen->pen.reverse = val->boolean;
403    return 1;
404  case VTERM_ATTR_STRIKE:
405    screen->pen.strike = val->boolean;
406    return 1;
407  case VTERM_ATTR_FONT:
408    screen->pen.font = val->number;
409    return 1;
410  case VTERM_ATTR_FOREGROUND:
411    screen->pen.fg = val->color;
412    return 1;
413  case VTERM_ATTR_BACKGROUND:
414    screen->pen.bg = val->color;
415    return 1;
416  }
417
418  return 0;
419}
420
421static int settermprop(VTermProp prop, VTermValue *val, void *user)
422{
423  VTermScreen *screen = user;
424
425  switch(prop) {
426  case VTERM_PROP_ALTSCREEN:
427    if(val->boolean && !screen->buffers[1])
428      return 0;
429
430    screen->buffer = val->boolean ? screen->buffers[1] : screen->buffers[0];
431    /* only send a damage event on disable; because during enable there's an
432     * erase that sends a damage anyway
433     */
434    if(!val->boolean)
435      damagescreen(screen);
436    break;
437  case VTERM_PROP_REVERSE:
438    screen->global_reverse = val->boolean;
439    damagescreen(screen);
440    break;
441  default:
442    ; /* ignore */
443  }
444
445  if(screen->callbacks && screen->callbacks->settermprop)
446    return (*screen->callbacks->settermprop)(prop, val, screen->cbdata);
447
448  return 1;
449}
450
451static int setmousefunc(VTermMouseFunc func, void *data, void *user)
452{
453  VTermScreen *screen = user;
454
455  if(screen->callbacks && screen->callbacks->setmousefunc)
456    return (*screen->callbacks->setmousefunc)(func, data, screen->cbdata);
457
458  return 0;
459}
460
461static int bell(void *user)
462{
463  VTermScreen *screen = user;
464
465  if(screen->callbacks && screen->callbacks->bell)
466    return (*screen->callbacks->bell)(screen->cbdata);
467
468  return 0;
469}
470
471static int resize(int new_rows, int new_cols, VTermPos *delta, void *user)
472{
473  VTermScreen *screen = user;
474
475  int is_altscreen = (screen->buffers[1] && screen->buffer == screen->buffers[1]);
476
477  int old_rows = screen->rows;
478  int old_cols = screen->cols;
479
480  if(!is_altscreen && new_rows < old_rows) {
481    // Fewer rows - determine if we're going to scroll at all, and if so, push
482    // those lines to scrollback
483    VTermPos pos = { 0, 0 };
484    for(pos.row = old_rows - 1; pos.row >= new_rows; pos.row--)
485      if(!vterm_screen_is_eol(screen, pos))
486        break;
487
488    int first_blank_row = pos.row + 1;
489    if(first_blank_row > new_rows) {
490      VTermRect rect = {
491        .start_row = 0,
492        .end_row   = old_rows,
493        .start_col = 0,
494        .end_col   = old_cols,
495      };
496      scrollrect(rect, first_blank_row - new_rows, 0, user);
497      vterm_screen_flush_damage(screen);
498
499      delta->row -= first_blank_row - new_rows;
500    }
501  }
502
503  screen->buffers[0] = realloc_buffer(screen, screen->buffers[0], new_rows, new_cols);
504  if(screen->buffers[1])
505    screen->buffers[1] = realloc_buffer(screen, screen->buffers[1], new_rows, new_cols);
506
507  screen->buffer = is_altscreen ? screen->buffers[1] : screen->buffers[0];
508
509  screen->rows = new_rows;
510  screen->cols = new_cols;
511
512  if(screen->sb_buffer)
513    vterm_allocator_free(screen->vt, screen->sb_buffer);
514
515  screen->sb_buffer = vterm_allocator_malloc(screen->vt, sizeof(VTermScreenCell) * new_cols);
516
517  if(new_cols > old_cols) {
518    VTermRect rect = {
519      .start_row = 0,
520      .end_row   = old_rows,
521      .start_col = old_cols,
522      .end_col   = new_cols,
523    };
524    damagerect(screen, rect);
525  }
526
527  if(new_rows > old_rows) {
528    if(!is_altscreen && screen->callbacks && screen->callbacks->sb_popline) {
529      int rows = new_rows - old_rows;
530      while(rows) {
531        if(!(screen->callbacks->sb_popline(screen->cols, screen->sb_buffer, screen->cbdata)))
532          break;
533
534        VTermRect rect = {
535          .start_row = 0,
536          .end_row   = screen->rows,
537          .start_col = 0,
538          .end_col   = screen->cols,
539        };
540        scrollrect(rect, -1, 0, user);
541
542        VTermPos pos = { 0, 0 };
543        for(pos.col = 0; pos.col < screen->cols; pos.col += screen->sb_buffer[pos.col].width)
544          vterm_screen_set_cell(screen, pos, screen->sb_buffer + pos.col);
545
546        rect.end_row = 1;
547        damagerect(screen, rect);
548
549        vterm_screen_flush_damage(screen);
550
551        rows--;
552        delta->row++;
553      }
554    }
555
556    VTermRect rect = {
557      .start_row = old_rows,
558      .end_row   = new_rows,
559      .start_col = 0,
560      .end_col   = new_cols,
561    };
562    damagerect(screen, rect);
563  }
564
565  if(screen->callbacks && screen->callbacks->resize)
566    return (*screen->callbacks->resize)(new_rows, new_cols, screen->cbdata);
567
568  return 1;
569}
570
571static VTermStateCallbacks state_cbs = {
572  .putglyph     = &putglyph,
573  .movecursor   = &movecursor,
574  .scrollrect   = &scrollrect,
575  .erase        = &erase,
576  .setpenattr   = &setpenattr,
577  .settermprop  = &settermprop,
578  .setmousefunc = &setmousefunc,
579  .bell         = &bell,
580  .resize       = &resize,
581};
582
583static VTermScreen *screen_new(VTerm *vt)
584{
585  VTermState *state = vterm_obtain_state(vt);
586  if(!state)
587    return NULL;
588
589  VTermScreen *screen = vterm_allocator_malloc(vt, sizeof(VTermScreen));
590  int rows, cols;
591
592  vterm_get_size(vt, &rows, &cols);
593
594  screen->vt = vt;
595  screen->state = state;
596
597  screen->damage_merge = VTERM_DAMAGE_CELL;
598  screen->damaged.start_row = -1;
599  screen->pending_scrollrect.start_row = -1;
600
601  screen->rows = rows;
602  screen->cols = cols;
603
604  screen->buffers[0] = realloc_buffer(screen, NULL, rows, cols);
605
606  screen->buffer = screen->buffers[0];
607
608  screen->sb_buffer = vterm_allocator_malloc(screen->vt, sizeof(VTermScreenCell) * cols);
609
610  vterm_state_set_callbacks(screen->state, &state_cbs, screen);
611
612  return screen;
613}
614
615void vterm_screen_free(VTermScreen *screen)
616{
617  vterm_allocator_free(screen->vt, screen->buffers[0]);
618  if(screen->buffers[1])
619    vterm_allocator_free(screen->vt, screen->buffers[1]);
620
621  vterm_allocator_free(screen->vt, screen->sb_buffer);
622
623  vterm_allocator_free(screen->vt, screen);
624}
625
626void vterm_screen_reset(VTermScreen *screen, int hard)
627{
628  screen->damaged.start_row = -1;
629  screen->pending_scrollrect.start_row = -1;
630  vterm_state_reset(screen->state, hard);
631  vterm_screen_flush_damage(screen);
632}
633
634static size_t _get_chars(const VTermScreen *screen, const int utf8, void *buffer, size_t len, const VTermRect rect)
635{
636  size_t outpos = 0;
637  int padding = 0;
638
639#define PUT(c)                                             \
640  if(utf8) {                                               \
641    size_t thislen = utf8_seqlen(c);                       \
642    if(buffer && outpos + thislen <= len)                  \
643      outpos += fill_utf8((c), (char *)buffer + outpos);   \
644    else                                                   \
645      outpos += thislen;                                   \
646  }                                                        \
647  else {                                                   \
648    if(buffer && outpos + 1 <= len)                        \
649      ((uint32_t*)buffer)[outpos++] = (c);                 \
650    else                                                   \
651      outpos++;                                            \
652  }
653
654  for(int row = rect.start_row; row < rect.end_row; row++) {
655    for(int col = rect.start_col; col < rect.end_col; col++) {
656      ScreenCell *cell = getcell(screen, row, col);
657
658      if(cell->chars[0] == 0)
659        // Erased cell, might need a space
660        padding++;
661      else if(cell->chars[0] == (uint32_t)-1)
662        // Gap behind a double-width char, do nothing
663        ;
664      else {
665        while(padding) {
666          PUT(UNICODE_SPACE);
667          padding--;
668        }
669        for(int i = 0; i < VTERM_MAX_CHARS_PER_CELL && cell->chars[i]; i++) {
670          PUT(cell->chars[i]);
671        }
672      }
673    }
674
675    if(row < rect.end_row - 1) {
676      PUT(UNICODE_LINEFEED);
677      padding = 0;
678    }
679  }
680
681  return outpos;
682}
683
684size_t vterm_screen_get_chars(const VTermScreen *screen, uint32_t *chars, size_t len, const VTermRect rect)
685{
686  return _get_chars(screen, 0, chars, len, rect);
687}
688
689size_t vterm_screen_get_text(const VTermScreen *screen, char *str, size_t len, const VTermRect rect)
690{
691  return _get_chars(screen, 1, str, len, rect);
692}
693
694/* Copy internal to external representation of a screen cell */
695int vterm_screen_get_cell(const VTermScreen *screen, VTermPos pos, VTermScreenCell *cell)
696{
697  ScreenCell *intcell = getcell(screen, pos.row, pos.col);
698  if(!intcell)
699    return 0;
700
701  for(int i = 0; ; i++) {
702    cell->chars[i] = intcell->chars[i];
703    if(!intcell->chars[i])
704      break;
705  }
706
707  cell->attrs.bold      = intcell->pen.bold;
708  cell->attrs.underline = intcell->pen.underline;
709  cell->attrs.italic    = intcell->pen.italic;
710  cell->attrs.blink     = intcell->pen.blink;
711  cell->attrs.reverse   = intcell->pen.reverse ^ screen->global_reverse;
712  cell->attrs.strike    = intcell->pen.strike;
713  cell->attrs.font      = intcell->pen.font;
714
715  cell->fg = intcell->pen.fg;
716  cell->bg = intcell->pen.bg;
717
718  if(pos.col < (screen->cols - 1) &&
719     getcell(screen, pos.row, pos.col + 1)->chars[0] == (uint32_t)-1)
720    cell->width = 2;
721  else
722    cell->width = 1;
723
724  return 1;
725}
726
727/* Copy external to internal representation of a screen cell */
728/* static because it's only used internally for sb_popline during resize */
729static int vterm_screen_set_cell(VTermScreen *screen, VTermPos pos, const VTermScreenCell *cell)
730{
731  ScreenCell *intcell = getcell(screen, pos.row, pos.col);
732  if(!intcell)
733    return 0;
734
735  for(int i = 0; ; i++) {
736    intcell->chars[i] = cell->chars[i];
737    if(!cell->chars[i])
738      break;
739  }
740
741  intcell->pen.bold      = cell->attrs.bold;
742  intcell->pen.underline = cell->attrs.underline;
743  intcell->pen.italic    = cell->attrs.italic;
744  intcell->pen.blink     = cell->attrs.blink;
745  intcell->pen.reverse   = cell->attrs.reverse ^ screen->global_reverse;
746  intcell->pen.strike    = cell->attrs.strike;
747  intcell->pen.font      = cell->attrs.font;
748
749  intcell->pen.fg = cell->fg;
750  intcell->pen.bg = cell->bg;
751
752  if(cell->width == 2)
753    getcell(screen, pos.row, pos.col + 1)->chars[0] = (uint32_t)-1;
754
755  return 1;
756}
757
758int vterm_screen_is_eol(const VTermScreen *screen, VTermPos pos)
759{
760  /* This cell is EOL if this and every cell to the right is black */
761  for(; pos.col < screen->cols; pos.col++) {
762    ScreenCell *cell = getcell(screen, pos.row, pos.col);
763    if(cell->chars[0] != 0)
764      return 0;
765  }
766
767  return 1;
768}
769
770VTermScreen *vterm_obtain_screen(VTerm *vt)
771{
772  if(vt->screen)
773    return vt->screen;
774
775  VTermScreen *screen = screen_new(vt);
776  vt->screen = screen;
777
778  return screen;
779}
780
781void vterm_screen_enable_altscreen(VTermScreen *screen, int altscreen)
782{
783
784  if(!screen->buffers[1] && altscreen) {
785    int rows, cols;
786    vterm_get_size(screen->vt, &rows, &cols);
787
788    screen->buffers[1] = realloc_buffer(screen, NULL, rows, cols);
789  }
790}
791
792void vterm_screen_set_callbacks(VTermScreen *screen, const VTermScreenCallbacks *callbacks, void *user)
793{
794  screen->callbacks = callbacks;
795  screen->cbdata = user;
796}
797
798void vterm_screen_flush_damage(VTermScreen *screen)
799{
800  if(screen->pending_scrollrect.start_row != -1) {
801    vterm_scroll_rect(screen->pending_scrollrect, screen->pending_scroll_downward, screen->pending_scroll_rightward,
802        moverect_user, erase_user, screen);
803
804    screen->pending_scrollrect.start_row = -1;
805  }
806
807  if(screen->damaged.start_row != -1) {
808    if(screen->callbacks && screen->callbacks->damage)
809      (*screen->callbacks->damage)(screen->damaged, screen->cbdata);
810
811    screen->damaged.start_row = -1;
812  }
813}
814
815void vterm_screen_set_damage_merge(VTermScreen *screen, VTermDamageSize size)
816{
817  vterm_screen_flush_damage(screen);
818  screen->damage_merge = size;
819}
820
821static int attrs_differ(VTermAttrMask attrs, ScreenCell *a, ScreenCell *b)
822{
823  if((attrs & VTERM_ATTR_BOLD_MASK)       && (a->pen.bold != b->pen.bold))
824    return 1;
825  if((attrs & VTERM_ATTR_UNDERLINE_MASK)  && (a->pen.underline != b->pen.underline))
826    return 1;
827  if((attrs & VTERM_ATTR_ITALIC_MASK)     && (a->pen.italic != b->pen.italic))
828    return 1;
829  if((attrs & VTERM_ATTR_BLINK_MASK)      && (a->pen.blink != b->pen.blink))
830    return 1;
831  if((attrs & VTERM_ATTR_REVERSE_MASK)    && (a->pen.reverse != b->pen.reverse))
832    return 1;
833  if((attrs & VTERM_ATTR_STRIKE_MASK)     && (a->pen.strike != b->pen.strike))
834    return 1;
835  if((attrs & VTERM_ATTR_FONT_MASK)       && (a->pen.font != b->pen.font))
836    return 1;
837  if((attrs & VTERM_ATTR_FOREGROUND_MASK) && !vterm_color_equal(a->pen.fg, b->pen.fg))
838    return 1;
839  if((attrs & VTERM_ATTR_BACKGROUND_MASK) && !vterm_color_equal(a->pen.bg, b->pen.bg))
840    return 1;
841
842  return 0;
843}
844
845int vterm_screen_get_attrs_extent(const VTermScreen *screen, VTermRect *extent, VTermPos pos, VTermAttrMask attrs)
846{
847  ScreenCell *target = getcell(screen, pos.row, pos.col);
848
849  // TODO: bounds check
850  extent->start_row = pos.row;
851  extent->end_row   = pos.row + 1;
852
853  if(extent->start_col < 0)
854    extent->start_col = 0;
855  if(extent->end_col < 0)
856    extent->end_col = screen->cols;
857
858  int col;
859
860  for(col = pos.col - 1; col >= extent->start_col; col--)
861    if(attrs_differ(attrs, target, getcell(screen, pos.row, col)))
862      break;
863  extent->start_col = col + 1;
864
865  for(col = pos.col + 1; col < extent->end_col; col++)
866    if(attrs_differ(attrs, target, getcell(screen, pos.row, col)))
867      break;
868  extent->end_col = col - 1;
869
870  return 1;
871}
872