1 #ifdef __GLIBC__
2 #  define _XOPEN_SOURCE 700
3 #endif
4 
5 #include "tickit.h"
6 
7 #include "tickit-mockterm.h"
8 #include "tickit-termdrv.h"
9 
10 #include <stdlib.h>
11 #include <string.h>
12 
13 #define BOUND(var,min,max) \
14   if(var < (min)) var = (min); \
15   if(var > (max)) var = (max)
16 
17 typedef struct
18 {
19   char      *str;
20   TickitPen *pen;
21 } MockTermCell;
22 
23 typedef struct
24 {
25   TickitTermDriver super;
26 
27   int lines;
28   int cols;
29   MockTermCell ***cells;
30 
31   TickitMockTermLogEntry *log;
32   size_t            logsize;
33   size_t            logi;
34 
35   TickitPen *pen;
36   int line;
37   int col;
38 
39   int cursorvis;
40   int cursorblink;
41   int cursorshape;
42 } MockTermDriver;
43 
mtd_free_cell(MockTermDriver * mtd,int line,int col)44 static void mtd_free_cell(MockTermDriver *mtd, int line, int col)
45 {
46   MockTermCell *cell = mtd->cells[line][col];
47 
48   if(cell->str)
49     free(cell->str);
50   if(cell->pen)
51     tickit_pen_unref(cell->pen);
52 
53   free(cell);
54 }
55 
mtd_free_line(MockTermDriver * mtd,int line)56 static void mtd_free_line(MockTermDriver *mtd, int line)
57 {
58   for(int col = 0; col < mtd->cols; col++)
59     mtd_free_cell(mtd, line, col);
60 
61   free(mtd->cells[line]);
62 }
63 
mtd_clear_cells(MockTermDriver * mtd,int line,int startcol,int stopcol)64 static void mtd_clear_cells(MockTermDriver *mtd, int line, int startcol, int stopcol)
65 {
66   /* This code is also used to initialise brand new cells in the structure, so
67    * it should be careful to vivify them correctly
68    */
69 
70   MockTermCell **linecells = mtd->cells[line];
71   if(!linecells) {
72     linecells = malloc(mtd->cols * sizeof(MockTermCell *));
73     mtd->cells[line] = linecells;
74 
75     for(int col = 0; col < mtd->cols; col++)
76       linecells[col] = NULL;
77   }
78 
79   for(int col = startcol; col < stopcol; col++) {
80     MockTermCell *cell = linecells[col];
81 
82     if(!cell) {
83       cell = malloc(sizeof(MockTermCell));
84       linecells[col] = cell;
85 
86       cell->str = NULL;
87       cell->pen = NULL;
88     }
89 
90     if(cell->str)
91       free(cell->str);
92     if(cell->pen)
93       tickit_pen_unref(cell->pen);
94 
95     cell->str = strdup(" ");
96     cell->pen = tickit_pen_clone(mtd->pen);
97   }
98 }
99 
mtd_nextlog(MockTermDriver * mtd)100 static TickitMockTermLogEntry *mtd_nextlog(MockTermDriver *mtd)
101 {
102   if(mtd->logi == mtd->logsize) {
103     mtd->logsize *= 2;
104     mtd->log = realloc(mtd->log, mtd->logsize * sizeof(TickitMockTermLogEntry));
105   }
106 
107   TickitMockTermLogEntry *entry = mtd->log + mtd->logi++;
108 
109   entry->str = NULL;
110   entry->pen = NULL;
111   return entry;
112 }
113 
mtd_free_logentry(TickitMockTermLogEntry * entry)114 static void mtd_free_logentry(TickitMockTermLogEntry *entry)
115 {
116   if(entry->str)
117     free((void *)entry->str);
118   entry->str = NULL;
119 
120   if(entry->pen)
121     tickit_pen_unref(entry->pen);
122   entry->pen = NULL;
123 }
124 
mtd_destroy(TickitTermDriver * ttd)125 static void mtd_destroy(TickitTermDriver *ttd)
126 {
127   MockTermDriver *mtd = (MockTermDriver *)ttd;
128 
129   for(int i = 0; i < mtd->logi; i++)
130     mtd_free_logentry(mtd->log + i);
131   free(mtd->log);
132 
133   for(int line = 0; line < mtd->lines; line++)
134     mtd_free_line(mtd, line);
135   free(mtd->cells);
136 
137   tickit_pen_unref(mtd->pen);
138 
139   free(mtd);
140 }
141 
mtd_print(TickitTermDriver * ttd,const char * str,size_t len)142 static bool mtd_print(TickitTermDriver *ttd, const char *str, size_t len)
143 {
144   MockTermDriver *mtd = (MockTermDriver *)ttd;
145 
146   TickitMockTermLogEntry *entry = mtd_nextlog(mtd);
147   entry->type = LOG_PRINT;
148   entry->str  = strndup(str, len);
149   entry->val1 = len;
150 
151   TickitStringPos pos;
152   tickit_stringpos_zero(&pos);
153   pos.columns = mtd->col;
154 
155   MockTermCell **linecells = mtd->cells[mtd->line];
156 
157   TickitStringPos limit;
158   tickit_stringpos_limit_columns(&limit, pos.columns);
159   limit.bytes = len;
160 
161   while(pos.bytes < len) {
162     TickitStringPos start = pos;
163 
164     limit.columns++;
165     tickit_utf8_ncountmore(str, len, &pos, &limit);
166 
167     if(pos.columns == start.columns)
168       continue;
169 
170     // Wrap but don't scroll - for now. This shouldn't cause scrolling anyway
171     if(start.columns >= mtd->cols) {
172       start.columns = 0;
173       if(mtd->line < mtd->lines-1) {
174         mtd->line++;
175         linecells = mtd->cells[mtd->line];
176       }
177     }
178 
179     MockTermCell *cell = linecells[start.columns];
180 
181     if(cell->str)
182       free(cell->str);
183     if(cell->pen)
184       tickit_pen_unref(cell->pen);
185 
186     cell->str = strndup(str + start.bytes, pos.bytes - start.bytes);
187     cell->pen = tickit_pen_clone(mtd->pen);
188 
189     // Empty out the other cells for doublewidth
190     for(start.columns++; start.columns < pos.columns; start.columns++) {
191       cell = linecells[start.columns];
192 
193       if(cell->str)
194         free(cell->str);
195       if(cell->pen)
196         tickit_pen_unref(cell->pen);
197 
198       cell->str = NULL; /* empty */
199       cell->pen = tickit_pen_clone(mtd->pen);
200     }
201   }
202 
203   mtd->col = pos.columns;
204 
205   return true;
206 }
207 
mtd_goto_abs(TickitTermDriver * ttd,int line,int col)208 static bool mtd_goto_abs(TickitTermDriver *ttd, int line, int col)
209 {
210   MockTermDriver *mtd = (MockTermDriver *)ttd;
211 
212   BOUND(line, 0, mtd->lines-1);
213   BOUND(col,  0, mtd->cols-1);
214 
215   TickitMockTermLogEntry *entry = mtd_nextlog(mtd);
216   entry->type = LOG_GOTO;
217   entry->val1 = line;
218   entry->val2 = col;
219 
220   mtd->line = line;
221   mtd->col  = col;
222 
223   return true;
224 }
225 
mtd_move_rel(TickitTermDriver * ttd,int downward,int rightward)226 static bool mtd_move_rel(TickitTermDriver *ttd, int downward, int rightward)
227 {
228   MockTermDriver *mtd = (MockTermDriver *)ttd;
229 
230   mtd_goto_abs(ttd, mtd->line + downward, mtd->col + rightward);
231 
232   return true;
233 }
234 
mtd_scrollrect(TickitTermDriver * ttd,const TickitRect * rect,int downward,int rightward)235 static bool mtd_scrollrect(TickitTermDriver *ttd, const TickitRect *rect, int downward, int rightward)
236 {
237   MockTermDriver *mtd = (MockTermDriver *)ttd;
238 
239   if(!downward && !rightward)
240     return true;
241 
242   int top    = rect->top;
243   int left   = rect->left;
244   int bottom = tickit_rect_bottom(rect);
245   int right  = tickit_rect_right(rect);
246 
247   BOUND(top,    0,   mtd->lines-1);
248   BOUND(bottom, top, mtd->lines);
249   BOUND(left,  0,    mtd->cols-1);
250   BOUND(right, left, mtd->cols);
251 
252   if((abs(downward) >= (bottom - top)) || (abs(rightward) >= (right - left)))
253     return false;
254 
255   if(left == 0 && right == mtd->cols && rightward == 0) {
256     MockTermCell ***cells = mtd->cells;
257 
258     TickitMockTermLogEntry *entry = mtd_nextlog(mtd);
259     entry->type = LOG_SCROLLRECT;
260     entry->val1 = downward;
261     entry->val2 = rightward;
262     entry->rect = *rect;
263 
264     if(downward > 0) {
265       int line;
266       for(line = top; line < top + downward; line++)
267         mtd_free_line(mtd, line);
268 
269       for(line = top; line < bottom - downward; line++)
270         cells[line] = cells[line + downward];
271 
272       for(/* line */; line < bottom; line++) {
273         cells[line] = NULL;
274         mtd_clear_cells(mtd, line, 0, mtd->cols);
275       }
276     }
277     else {
278       int upward = -downward;
279 
280       int line;
281       for(line = bottom-1; line >= bottom - upward; line--)
282         mtd_free_line(mtd, line);
283 
284       for(line = bottom-1; line >= top + upward; line--)
285         cells[line] = cells[line - upward];
286 
287       for(/* line */;    line >= top; line--) {
288         cells[line] = NULL;
289         mtd_clear_cells(mtd, line, 0, mtd->cols);
290       }
291     }
292 
293     return true;
294   }
295 
296   if(right == mtd->cols && downward == 0) {
297     TickitMockTermLogEntry *entry = mtd_nextlog(mtd);
298     entry->type = LOG_SCROLLRECT;
299     entry->val1 = downward;
300     entry->val2 = rightward;
301     entry->rect = *rect;
302 
303     for(int line = top; line < bottom; line++) {
304       MockTermCell **linecells = mtd->cells[line];
305 
306       if(rightward > 0) {
307         int col;
308         for(col = left; col < left + rightward; col++)
309           mtd_free_cell(mtd, line, col);
310 
311         for(col = left; col < right - rightward; col++)
312           linecells[col] = linecells[col + rightward];
313 
314         for(/* col */; col < right; col++)
315           linecells[col] = NULL;
316         mtd_clear_cells(mtd, line, right - rightward, right);
317       }
318       else {
319         int leftward = -rightward;
320 
321         int col;
322         for(col = right-1; col >= right - leftward; col--)
323           mtd_free_cell(mtd, line, col);
324 
325         for(col = right-1; col >= left + leftward; col--)
326           linecells[col] = linecells[col - leftward];
327 
328         for(/* col */;    col >= left; col--)
329           linecells[col] = NULL;
330         mtd_clear_cells(mtd, line, left, left + leftward);
331       }
332     }
333 
334     return true;
335   }
336 
337   return false;
338 }
339 
mtd_erasech(TickitTermDriver * ttd,int count,TickitMaybeBool moveend)340 static bool mtd_erasech(TickitTermDriver *ttd, int count, TickitMaybeBool moveend)
341 {
342   MockTermDriver *mtd = (MockTermDriver *)ttd;
343 
344   TickitMockTermLogEntry *entry = mtd_nextlog(mtd);
345   entry->type = LOG_ERASECH;
346   entry->val1 = count;
347   entry->val2 = moveend;
348 
349   int right = mtd->col + count;
350   BOUND(right, 0, mtd->cols);
351 
352   mtd_clear_cells(mtd, mtd->line, mtd->col, right);
353 
354   if(moveend != TICKIT_NO)
355     mtd->col = right;
356 
357   return true;
358 }
359 
mtd_clear(TickitTermDriver * ttd)360 static bool mtd_clear(TickitTermDriver *ttd)
361 {
362   MockTermDriver *mtd = (MockTermDriver *)ttd;
363 
364   TickitMockTermLogEntry *entry = mtd_nextlog(mtd);
365   entry->type = LOG_CLEAR;
366 
367   for(int line = 0; line < mtd->lines; line++)
368     mtd_clear_cells(mtd, line, 0, mtd->cols);
369 
370   return true;
371 }
372 
mtd_chpen(TickitTermDriver * ttd,const TickitPen * delta,const TickitPen * final)373 static bool mtd_chpen(TickitTermDriver *ttd, const TickitPen *delta, const TickitPen *final)
374 {
375   MockTermDriver *mtd = (MockTermDriver *)ttd;
376 
377   TickitMockTermLogEntry *entry = mtd_nextlog(mtd);
378   entry->type = LOG_SETPEN;
379   entry->pen  = tickit_pen_clone(final);
380 
381   tickit_pen_clear(mtd->pen);
382   tickit_pen_copy(mtd->pen, final, 1);
383 
384   return true;
385 }
386 
mtd_getctl_int(TickitTermDriver * ttd,TickitTermCtl ctl,int * value)387 static bool mtd_getctl_int(TickitTermDriver *ttd, TickitTermCtl ctl, int *value)
388 {
389   MockTermDriver *mtd = (MockTermDriver *)ttd;
390 
391   switch(ctl) {
392     case TICKIT_TERMCTL_CURSORVIS:
393       *value = mtd->cursorvis;
394       return true;
395     case TICKIT_TERMCTL_CURSORBLINK:
396       *value = mtd->cursorblink;
397       return true;
398     case TICKIT_TERMCTL_CURSORSHAPE:
399       *value = mtd->cursorshape;
400       return true;
401     case TICKIT_TERMCTL_COLORS:
402       *value = 256;
403       return true;
404 
405     default:
406       return false;
407   }
408 }
409 
mtd_setctl_int(TickitTermDriver * ttd,TickitTermCtl ctl,int value)410 static bool mtd_setctl_int(TickitTermDriver *ttd, TickitTermCtl ctl, int value)
411 {
412   MockTermDriver *mtd = (MockTermDriver *)ttd;
413 
414   switch(ctl) {
415     case TICKIT_TERMCTL_CURSORVIS:
416       mtd->cursorvis = !!value; break;
417     case TICKIT_TERMCTL_CURSORBLINK:
418       mtd->cursorblink = !!value; break;
419     case TICKIT_TERMCTL_CURSORSHAPE:
420       mtd->cursorshape = value; break;
421     case TICKIT_TERMCTL_ALTSCREEN:
422     case TICKIT_TERMCTL_MOUSE:
423       break;
424     default:
425       return false;
426   }
427 
428   return true;
429 }
430 
431 static TickitTermDriverVTable mtd_vtable = {
432   .destroy    = mtd_destroy,
433   .print      = mtd_print,
434   .goto_abs   = mtd_goto_abs,
435   .move_rel   = mtd_move_rel,
436   .scrollrect = mtd_scrollrect,
437   .erasech    = mtd_erasech,
438   .clear      = mtd_clear,
439   .chpen      = mtd_chpen,
440   .getctl_int = mtd_getctl_int,
441   .setctl_int = mtd_setctl_int,
442 };
443 
tickit_mockterm_new(int lines,int cols)444 TickitMockTerm *tickit_mockterm_new(int lines, int cols)
445 {
446   MockTermDriver *mtd = malloc(sizeof(MockTermDriver));
447   mtd->super.vtable = &mtd_vtable;
448 
449   mtd->logsize = 16; // should be sufficient; or it will grow
450   mtd->log = malloc(mtd->logsize * sizeof(TickitMockTermLogEntry));
451   mtd->logi = 0;
452 
453   mtd->pen       = tickit_pen_new();
454 
455   mtd->lines       = lines;
456   mtd->cols        = cols;
457   mtd->line        = -1;
458   mtd->col         = -1;
459   mtd->cursorvis   = 0;
460   mtd->cursorblink = 0;
461   mtd->cursorshape = 0;
462 
463   mtd->cells = malloc(lines * sizeof(MockTermCell **));
464   for(int line = 0; line < lines; line++) {
465     mtd->cells[line] = NULL;
466     mtd_clear_cells(mtd, line, 0, cols);
467   }
468 
469   TickitMockTerm *mt = (TickitMockTerm *)tickit_term_build(&(struct TickitTermBuilder){
470     .driver = &mtd->super,
471   });
472   if(!mt) {
473     mtd_destroy((TickitTermDriver *)mtd);
474     return NULL;
475   }
476 
477   tickit_term_set_size((TickitTerm *)mt, lines, cols);
478 
479   return mt;
480 }
481 
tickit_mockterm_destroy(TickitMockTerm * mt)482 void tickit_mockterm_destroy(TickitMockTerm *mt)
483 {
484   tickit_term_destroy((TickitTerm *)mt);
485 }
486 
tickit_mockterm_get_display_text(TickitMockTerm * mt,char * buffer,size_t len,int line,int col,int width)487 size_t tickit_mockterm_get_display_text(TickitMockTerm *mt, char *buffer, size_t len, int line, int col, int width)
488 {
489   MockTermDriver *mtd = (MockTermDriver *)tickit_term_get_driver((TickitTerm *)mt);
490 
491   MockTermCell **linecells = mtd->cells[line];
492 
493   size_t ret = 0;
494   for(/* col */; width; col++, width--) {
495     MockTermCell *cell = linecells[col];
496     size_t celllen = cell->str ? strlen(cell->str) : 0;
497 
498     if(buffer && celllen && len >= celllen) {
499       strcpy(buffer, cell->str);
500       buffer += celllen;
501       len    -= celllen;
502       if(len <= 0)
503         buffer = NULL;
504     }
505 
506     ret += celllen;
507   }
508 
509   return ret;
510 }
511 
tickit_mockterm_get_display_pen(TickitMockTerm * mt,int line,int col)512 TickitPen *tickit_mockterm_get_display_pen(TickitMockTerm *mt, int line, int col)
513 {
514   MockTermDriver *mtd = (MockTermDriver *)tickit_term_get_driver((TickitTerm *)mt);
515 
516   return mtd->cells[line][col]->pen;
517 }
518 
tickit_mockterm_resize(TickitMockTerm * mt,int newlines,int newcols)519 void tickit_mockterm_resize(TickitMockTerm *mt, int newlines, int newcols)
520 {
521   MockTermDriver *mtd = (MockTermDriver *)tickit_term_get_driver((TickitTerm *)mt);
522 
523   MockTermCell ***newcells = malloc(newlines * sizeof(MockTermCell **));
524 
525   int oldlines = mtd->lines;
526   int oldcols =  mtd->cols;
527 
528   int line;
529   for(line = newlines; line < oldlines; line++)
530     mtd_free_line(mtd, line);
531 
532   for(line = 0; line < newlines && line < oldlines; line++) {
533     MockTermCell **newlinecells;
534 
535     if(newcols == oldcols)
536       newlinecells = mtd->cells[line];
537     else {
538       newlinecells = malloc(newcols * sizeof(MockTermCell *));
539 
540       int col;
541       for(col = newcols; col < oldcols; col++)
542         mtd_free_cell(mtd, line, col);
543 
544       for(col = 0; col < newcols && col < oldcols; col++)
545         newlinecells[col] = mtd->cells[line][col];
546       for(/* col */; col < newcols; col++)
547         newlinecells[col] = NULL;
548 
549       free(mtd->cells[line]);
550     }
551 
552     newcells[line] = newlinecells;
553   }
554   for(/* line */; line < newlines; line++)
555     newcells[line] = NULL;
556 
557   free(mtd->cells);
558   mtd->cells = newcells;
559 
560   mtd->lines = newlines;
561   mtd->cols  = newcols;
562 
563   if(newcols > oldcols)
564     for(line = 0; line < newlines && line < oldlines; line++)
565       mtd_clear_cells(mtd, line, oldcols, newcols);
566 
567   for(line = oldlines; line < newlines; line++)
568     mtd_clear_cells(mtd, line, 0, newcols);
569 
570   tickit_term_set_size((TickitTerm *)mt, newlines, newcols);
571 
572   BOUND(mtd->line, 0, mtd->lines-1);
573   BOUND(mtd->col,  0, mtd->cols-1);
574 }
575 
tickit_mockterm_loglen(TickitMockTerm * mt)576 int tickit_mockterm_loglen(TickitMockTerm *mt)
577 {
578   MockTermDriver *mtd = (MockTermDriver *)tickit_term_get_driver((TickitTerm *)mt);
579 
580   return mtd->logi;
581 }
582 
tickit_mockterm_peeklog(TickitMockTerm * mt,int i)583 TickitMockTermLogEntry *tickit_mockterm_peeklog(TickitMockTerm *mt, int i)
584 {
585   MockTermDriver *mtd = (MockTermDriver *)tickit_term_get_driver((TickitTerm *)mt);
586 
587   if(i >= 0 && i < mtd->logi)
588     return mtd->log + i;
589 
590   return NULL;
591 }
592 
tickit_mockterm_clearlog(TickitMockTerm * mt)593 void tickit_mockterm_clearlog(TickitMockTerm *mt)
594 {
595   MockTermDriver *mtd = (MockTermDriver *)tickit_term_get_driver((TickitTerm *)mt);
596 
597   for(int i = 0; i < mtd->logi; i++)
598     mtd_free_logentry(mtd->log + i);
599 
600   mtd->logi = 0;
601 }
602 
tickit_mockterm_get_position(TickitMockTerm * mt,int * line,int * col)603 void tickit_mockterm_get_position(TickitMockTerm *mt, int *line, int *col)
604 {
605   MockTermDriver *mtd = (MockTermDriver *)tickit_term_get_driver((TickitTerm *)mt);
606 
607   if(line)
608     *line = mtd->line;
609   if(col)
610     *col = mtd->col;
611 }
612