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