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