1#include <stdbool.h>
2#include <stdlib.h>
3#include <string.h>
4
5#include "../config.h"
6#include "term.h"
7#include "utils.h"
8
9static int csi_arg(Term* t, int i, int fallback);
10static void alt_screen(Term* t, Caret* c, bool on);
11static void chars_insert(Term* t, Caret* c, int n);
12static void chars_delete(Term* t, Caret* c, int n);
13static uint32_t acs_map(unsigned char ch);
14static void csi_dispatch(Term* t, Caret* c, unsigned char ch);
15static void csi_reset(Term* t);
16static void erase_display(Term* t, Caret* c);
17static void erase_line(Term* t, Caret* c);
18static void reset_rune(Term* t, int x, int y);
19static void rune_prepare(Term* t, int x, int y);
20static void scroll_down(Term* t, int top, int bot, int n);
21static void scroll_up(Term* t, int top, int bot, int n);
22static void sgr(Term* t);
23
24/* TODO */
25static uint32_t acs_map(unsigned char ch)
26{
27 switch (ch) {
28 case '`': return 0x25c6; /* ◆ */ case 'a': return 0x2592; /* ▒ */
29 case 'f': return 0x00b0; /* ° */ case 'g': return 0x00b1; /* ± */
30 case 'j': return 0x2518; /* ┘ */ case 'k': return 0x2510; /* ┐ */
31 case 'l': return 0x250c; /* ┌ */ case 'm': return 0x2514; /* └ */
32 case 'n': return 0x253c; /* ┼ */ case 'q': return 0x2500; /* ─ */
33 case 't': return 0x251c; /* ├ */ case 'u': return 0x2524; /* ┤ */
34 case 'v': return 0x2534; /* ┴ */ case 'w': return 0x252c; /* ┬ */
35 case 'x': return 0x2502; /* │ */ case 'y': return 0x2264; /* ≤ */
36 case 'z': return 0x2265; /* ≥ */ case '{': return 0x03c0; /* π */
37 case '|': return 0x2260; /* ≠ */ case '}': return 0x00a3; /* £ */
38 case '~': return 0x00b7; /* · */ default: return ch;
39 }
40}
41
42/* TODO */
43static int csi_arg(Term* t, int i, int fallback)
44{
45 if (i > t->csi_idx)
46 return fallback;
47 if (t->csi_params[i] == 0)
48 return fallback;
49
50 return t->csi_params[i];
51}
52
53/* TODO */
54static void alt_screen(Term* t, Caret* c, bool on)
55{
56 Rune* tmp;
57
58 if (on) {
59 if (t->alt)
60 return;
61 tmp = calloc(t->cols*t->rows, sizeof(*tmp));
62 if (!tmp)
63 return;
64 t->alt = t->runes;
65 t->runes = tmp;
66 term_clear(t, c);
67 return;
68 }
69
70 if (!t->alt)
71 return;
72 free(t->runes);
73 t->runes = t->alt;
74 t->alt = NULL;
75 term_damage_all(t);
76}
77
78/* TODO */
79static void chars_insert(Term* t, Caret* c, int n)
80{
81 int cols = t->cols - c->x;
82 Rune* line = &RUNE(t, 0, c->y);
83
84 if (n > cols)
85 n = cols;
86 memmove(&line[c->x + n], &line[c->x], sizeof(Rune) * (cols - n));
87 for (int x = c->x; x < c->x + n; x++)
88 reset_rune(t, x, c->y);
89 for (int x = c->x + n; x < t->cols; x++)
90 RUNE(t, x, c->y).dmg = true;
91}
92
93/* TODO */
94static void chars_delete(Term* t, Caret* c, int n)
95{
96 int cols = t->cols - c->x;
97 Rune* line = &RUNE(t, 0, c->y);
98
99 if (n > cols)
100 n = cols;
101 memmove(&line[c->x], &line[c->x + n], sizeof(Rune) * (cols - n));
102 for (int x = t->cols - n; x < t->cols; x++)
103 reset_rune(t, x, c->y);
104 for (int x = c->x; x < t->cols - n; x++)
105 RUNE(t, x, c->y).dmg = true;
106}
107
108/* TODO */
109static void csi_dispatch(Term* t, Caret* c, unsigned char ch)
110{
111 switch (ch) {
112 case '@': /* ICH - Insert Character */
113 t->wrapnext = false;
114 chars_insert(t, c, csi_arg(t, 0, 1));
115 break;
116
117 case 'H': /* CUP - Cursor Position - ESC[row;colH */
118 case 'f': { /* HVP - Horizontal and Vertical Postion - ESC[row;colf */
119 t->wrapnext = false;
120 int row = csi_arg(t, 0, 1) - 1;
121 int col = csi_arg(t, 1, 1) - 1;
122
123 if (row < 0)
124 row = 0;
125 if (col < 0)
126 col = 0;
127 if (row >= t->rows)
128 row = t->rows - 1;
129 if (col >= t->cols)
130 col = t->cols - 1;
131
132 c->y = row;
133 c->x = col;
134 break;
135 }
136
137 case 'J': /* ED - Erase in Display - ESC[nJ */
138 erase_display(t, c);
139 break;
140
141 case 'K': /* EL - Erase in Line - ESC[nK */
142 erase_line(t, c);
143 break;
144
145 case 'P': /* DCH - Delete Character */
146 t->wrapnext = false;
147 chars_delete(t, c, csi_arg(t, 0, 1));
148 break;
149
150 case 'X': { /* ECH - Erase Character */
151 int n = csi_arg(t, 0, 1);
152 for (int x = c->x; x < c->x + n && x < t->cols; x++)
153 reset_rune(t, x, c->y);
154 break;
155 }
156
157 case 'm': /* SGR - Select Graphics Rendition - ESC[nm, colours */
158 sgr(t);
159 break;
160
161 case 'A': { /* CUU - cursor up */
162 t->wrapnext = false;
163 int n = csi_arg(t, 0, 1);
164 c->y -= n;
165 if (c->y < 0)
166 c->y = 0;
167 break;
168 }
169
170 case 'B': { /* CUD - cursor down */
171 t->wrapnext = false;
172 int n = csi_arg(t, 0, 1);
173 c->y += n;
174 if (c->y >= t->rows)
175 c->y = t->rows - 1;
176 break;
177 }
178
179 case 'C': { /* CUF - cursor forward */
180 t->wrapnext = false;
181 int n = csi_arg(t, 0, 1);
182 c->x += n;
183 if (c->x >= t->cols)
184 c->x = t->cols - 1;
185 break;
186 }
187
188 case 'D': { /* CUB - cursor back */
189 t->wrapnext = false;
190 int n = csi_arg(t, 0, 1);
191 c->x -= n;
192 if (c->x < 0)
193 c->x = 0;
194 break;
195 }
196
197 case 'G': { /* CHA - cursor horizontal absolute */
198 t->wrapnext = false;
199 int col = csi_arg(t, 0, 1) - 1;
200
201 if (col < 0)
202 col = 0;
203 if (col >= t->cols)
204 col = t->cols - 1;
205
206 c->x = col;
207 break;
208 }
209
210 case 'd': { /* VPA - vertical position absolute */
211 t->wrapnext = false;
212 int row = csi_arg(t, 0, 1) - 1;
213
214 if (row < 0)
215 row = 0;
216 if (row >= t->rows)
217 row = t->rows - 1;
218
219 c->y = row;
220 break;
221 }
222
223 case 'L': /* IL - Insert Line */
224 t->wrapnext = false;
225 if (c->y >= t->scroll_top && c->y <= t->scroll_bot)
226 scroll_down(t, c->y, t->scroll_bot, csi_arg(t, 0, 1));
227 break;
228
229 case 'M': /* DL - Delete Line */
230 t->wrapnext = false;
231 if (c->y >= t->scroll_top && c->y <= t->scroll_bot)
232 scroll_up(t, c->y, t->scroll_bot, csi_arg(t, 0, 1));
233 break;
234
235 case 'S': /* SU - Scroll Up */
236 t->wrapnext = false;
237 scroll_up(t, t->scroll_top, t->scroll_bot, csi_arg(t, 0, 1));
238 break;
239
240 case 'T': /* SD - Scroll Down */
241 t->wrapnext = false;
242 scroll_down(t, t->scroll_top, t->scroll_bot, csi_arg(t, 0, 1));
243 break;
244
245 case 'h': /* set mode */
246 case 'l': { /* reset mode */
247 bool on = ch == 'h';
248 for (int i = 0; i <= t->csi_idx; i++) {
249 int p = t->csi_params[i];
250 if (p == 25)
251 t->cursor_visible = on;
252 if (p == 2004)
253 t->bracketed_paste = on;
254 if (p == 1048) {
255 if (on)
256 t->saved = *c;
257 else
258 *c = t->saved;
259 }
260 if (p == 47 || p == 1047 || p == 1049) {
261 if (on && p == 1049)
262 t->saved = *c;
263 alt_screen(t, c, on);
264 if (!on && p == 1049)
265 *c = t->saved;
266 }
267 }
268 break;
269 }
270 case 'r': { /* DECSTBM - scrolling region */
271 t->wrapnext = false;
272 int top = csi_arg(t, 0, 1) - 1;
273 int bot = csi_arg(t, 1, t->rows) - 1;
274 if (top < 0)
275 top = 0;
276 if (bot >= t->rows)
277 bot = t->rows - 1;
278 if (top < bot) {
279 t->scroll_top = top;
280 t->scroll_bot = bot;
281 c->x = 0;
282 c->y = 0;
283 }
284 break;
285 }
286 case 's': t->saved = *c; break; /* save cursor */
287 case 'u': t->wrapnext = false; *c = t->saved; break; /* restore cursor */
288
289 default: /* Unsupported CSI */
290 break;
291 }
292}
293
294/* erase CSI parameters */
295static void csi_reset(Term* t)
296{
297 for (int i = 0; i < CSI_PARAMS_MAX; i++)
298 t->csi_params[i] = 0;
299 t->csi_idx = 0;
300}
301
302/* TODO */
303static void erase_display(Term* t, Caret* c)
304{
305 int mode = csi_arg(t, 0, 0);
306
307 /* mode 2: whole screen */
308 if (mode == 2 || mode == 3) {
309 for (int y = 0; y < t->rows; y++) {
310 for (int x = 0; x < t->cols; x++)
311 reset_rune(t, x, y);
312 }
313 return;
314 }
315
316 /* mode 0: cursor to end of screen */
317 if (mode == 0) {
318 for (int y = c->y; y < t->rows; y++) {
319 int x0 = (y == c->y) ? c->x : 0;
320 for (int x = x0; x < t->cols; x++)
321 reset_rune(t, x, y);
322 }
323 return;
324 }
325
326 /* mode 1: start of screen to cursor */
327 if (mode == 1) {
328 for (int y = 0; y <= c->y; y++) {
329 int x1 = (y == c->y) ? c->x : t->cols - 1;
330 for (int x = 0; x <= x1; x++)
331 reset_rune(t, x, y);
332 }
333 }
334}
335
336/* TODO */
337static void erase_line(Term* t, Caret* c)
338{
339 int mode = csi_arg(t, 0, 0);
340 int from = mode == 0 ? c->x : 0;
341 int to = mode == 1 ? c->x : t->cols - 1;
342
343 for (int x = from; x <= to; x++)
344 reset_rune(t, x, c->y);
345}
346
347/* TODO */
348static void reset_rune(Term* t, int x, int y)
349{
350 Rune* r = &(RUNE(t, x, y));
351 r->cp = ' ';
352 r->fg = t->fg;
353 r->bg = t->bg;
354 r->attr = t->attr;
355 r->width = 1;
356 r->dmg = true;
357}
358
359/* TODO */
360static void rune_prepare(Term* t, int x, int y)
361{
362 if (x > 0 && RUNE(t, x, y).width == 0)
363 reset_rune(t, x - 1, y);
364 if (RUNE(t, x, y).width == 2 && x + 1 < t->cols)
365 reset_rune(t, x + 1, y);
366}
367
368/* TODO */
369static void scroll_down(Term* t, int top, int bot, int n)
370{
371 int cols = t->cols;
372 int rows = bot - top + 1;
373
374 if (n > rows)
375 n = rows;
376 memmove(
377 &RUNE(t, 0, top + n), &RUNE(t, 0, top),
378 sizeof(Rune) * cols * (rows - n)
379 );
380 for (int y = top; y < top + n; y++) {
381 for (int x = 0; x < cols; x++)
382 reset_rune(t, x, y);
383 }
384 term_damage_all(t);
385}
386
387/* TODO */
388static void scroll_up(Term* t, int top, int bot, int n)
389{
390 int cols = t->cols;
391 int rows = bot - top + 1;
392
393 if (n > rows)
394 n = rows;
395 memmove(
396 &RUNE(t, 0, top), &RUNE(t, 0, top + n),
397 sizeof(Rune) * cols * (rows - n)
398 );
399 for (int y = bot - n + 1; y <= bot; y++) {
400 for (int x = 0; x < cols; x++)
401 reset_rune(t, x, y);
402 }
403 term_damage_all(t);
404}
405
406/* TODO */
407static void sgr(Term* t)
408{
409 for (int i = 0; i <= t->csi_idx; i++) {
410 int p = t->csi_params[i];
411
412 if (p == 0) {
413 t->fg = default_fg;
414 t->bg = default_bg;
415 t->attr = 0;
416 }
417 else if (p == 7)
418 t->attr |= TERM_ATTR_REVERSE;
419 else if (p == 27)
420 t->attr &= ~TERM_ATTR_REVERSE; /* turn off reverse attribute bit */
421 else if (p >= 30 && p <= 37)
422 t->fg = color_table[p - 30];
423 else if (p >= 40 && p <= 47)
424 t->bg = color_table[p - 40];
425 else if (p >= 90 && p <= 97)
426 t->fg = color_table[p - 90 + 8];
427 else if (p >= 100 && p <= 107)
428 t->bg = color_table[p - 100 + 8];
429 else if (p == 39)
430 t->fg = default_fg;
431 else if (p == 49)
432 t->bg = default_bg;
433 else if ((p == 38 || p == 48) &&
434 i + 4 <= t->csi_idx && t->csi_params[i + 1] == 2) {
435 uint32_t col = rgba(
436 t->csi_params[i + 2],
437 t->csi_params[i + 3],
438 t->csi_params[i + 4], 255
439 );
440 if (p == 38)
441 t->fg = col;
442 else
443 t->bg = col;
444 i += 4;
445 }
446 }
447}
448
449void term_clear(Term* t, Caret* c)
450{
451 for (int y = 0; y < t->rows; y++) {
452 for (int x = 0; x < t->cols; x++) {
453 Rune* rune = &RUNE(t, x, y);
454 rune->cp = ' ';
455 rune->fg = t->fg;
456 rune->bg = t->bg;
457 rune->attr = t->attr;
458 rune->width = 1;
459 rune->dmg = true;
460 }
461 }
462
463 c->x = 0;
464 c->y = 0;
465 t->wrapnext = false;
466}
467
468void term_damage_all(Term* t)
469{
470 for (int y = 0; y < t->rows; y++) {
471 for (int x = 0; x < t->cols; x++)
472 RUNE(t, x, y).dmg = true;
473 }
474}
475
476void term_damage_rune(Term* t, int x, int y)
477{
478 if (x < 0 || y < 0 || x >= t->cols || y >= t->rows)
479 return;
480 RUNE(t, x, y).dmg = true;
481}
482
483void term_putc(Term* t, Caret* c, uint32_t cp)
484{
485 switch (cp) {
486 case '\r':
487 c->x = 0;
488 t->wrapnext = false;
489 break;
490 case '\n':
491 c->x = 0;
492 t->wrapnext = false;
493 if (c->y == t->scroll_bot) {
494 term_scroll(t);
495 }
496 else if (c->y < t->rows - 1)
497 c->y++;
498 break;
499 case '\b':
500 t->wrapnext = false;
501 if (c->x > 0)
502 c->x--;
503 break;
504 case '\t':
505 t->wrapnext = false;
506 /* move cursor to next tabstop */
507 c->x = (c->x+8) & ~0x7;
508 if (c->x >= t->cols)
509 term_putc(t, c, '\n');
510 break;
511 default: {
512 if (cp < 32)
513 break;
514
515 if (t->wrapnext) {
516 term_putc(t, c, '\n');
517 t->wrapnext = false;
518 }
519
520 rune_prepare(t, c->x, c->y);
521 Rune* r = &RUNE(t, c->x, c->y);
522 if (r->cp != cp || r->fg != t->fg || r->bg != t->bg ||
523 r->attr != t->attr || r->width != 1) {
524 r->cp = cp;
525 r->fg = t->fg;
526 r->bg = t->bg;
527 r->attr = t->attr;
528 r->width = 1;
529 r->dmg = true;
530 }
531
532 if (c->x + 1 >= t->cols)
533 t->wrapnext = true;
534 else
535 c->x++;
536
537 break;
538 }
539 }
540}
541
542int term_resize(Term* t, Caret* c, int cols, int rows)
543{
544 /* old */
545 Rune* or = t->runes;
546 int ocols = t->cols;
547 int orows = t->rows;
548
549 if (t->alt) {
550 free(t->runes);
551 t->runes = t->alt;
552 t->alt = NULL;
553 or = t->runes;
554 }
555
556 if (cols < 1)
557 cols = 1;
558 if (rows < 1)
559 rows = 1;
560
561 /* new */
562 Rune* nr = calloc(cols*rows, sizeof(*nr));
563 if (!nr)
564 return -1;
565
566 for (int y = 0; y < rows; y++) {
567 for (int x = 0; x < cols; x++) {
568 Rune* r = &nr[y * cols + x];
569 r->cp = ' ';
570 r->fg = t->fg;
571 r->bg = t->bg;
572 r->attr = t->attr;
573 r->width = 1;
574 r->dmg = true;
575 }
576 }
577
578 if (or) {
579 int copyrows = orows < rows ? orows : rows;
580 int copycols = ocols < cols ? ocols : cols;
581
582 for (int y = 0; y < copyrows; y++)
583 memcpy( &nr[y * cols], &or[y * ocols], sizeof(Rune) * copycols);
584 }
585
586 for (int y = 0; y < rows; y++) {
587 for (int x = 0; x < cols; x++)
588 nr[y * cols + x].dmg = true;
589 }
590
591 free(or);
592
593 t->runes = nr;
594 t->cols = cols;
595 t->rows = rows;
596 t->scroll_top = 0;
597 t->scroll_bot = rows - 1;
598
599 if (c->x >= cols)
600 c->x = cols - 1;
601 if (c->y >= rows)
602 c->y = rows - 1;
603 if (c->x < 0)
604 c->x = 0;
605 if (c->y < 0)
606 c->y = 0;
607 t->wrapnext = false;
608 if (t->saved.x >= cols)
609 t->saved.x = cols - 1;
610 if (t->saved.y >= rows)
611 t->saved.y = rows - 1;
612
613 return 0;
614}
615
616void term_scroll(Term* t)
617{
618 scroll_up(t, t->scroll_top, t->scroll_bot, 1);
619}
620
621bool term_write(Term* t, Caret* c, const char* s, size_t n)
622{
623 bool damaged = false;
624
625 for (size_t i = 0; i < n; i++) {
626 unsigned char ch = (unsigned char)s[i];
627 Caret old = *c;
628
629 switch (t->state) {
630 case TSTATE_NORMAL:
631 if (ch == '\x1B') {
632 t->state = TSTATE_ESC;
633 }
634 else if (ch == '\x0E') {
635 t->charset = 1;
636 }
637 else if (ch == '\x0F') {
638 t->charset = 0;
639 }
640 else if (ch >= 0x80) {
641 /* utf8 unsupported . ignore high bit bytes */
642 }
643 else {
644 term_putc(t, c, t->acs[t->charset] ? acs_map(ch) : ch);
645 }
646 break;
647
648 case TSTATE_ESC:
649 if (ch == '[') {
650 csi_reset(t);
651 t->state = TSTATE_CSI;
652 }
653 else if (ch == ']') {
654 t->state = TSTATE_OSC;
655 }
656 else if (ch == '(') {
657 t->charset_target = 0;
658 t->state = TSTATE_CHARSET;
659 }
660 else if (ch == ')') {
661 t->charset_target = 1;
662 t->state = TSTATE_CHARSET;
663 }
664 else if (ch == '*' || ch == '+') {
665 t->state = TSTATE_CHARSET_SKIP;
666 }
667 else if (ch == '7') {
668 t->saved = *c;
669 t->state = TSTATE_NORMAL;
670 }
671 else if (ch == '8') {
672 *c = t->saved;
673 t->state = TSTATE_NORMAL;
674 }
675 else if (ch == 'M') {
676 t->wrapnext = false;
677 if (c->y == t->scroll_top)
678 scroll_down(t, t->scroll_top, t->scroll_bot, 1);
679 else if (c->y > 0)
680 c->y--;
681 t->state = TSTATE_NORMAL;
682 }
683 else if (ch == 'D' || ch == 'E') {
684 t->wrapnext = false;
685 if (ch == 'E')
686 c->x = 0;
687 if (c->y == t->scroll_bot)
688 scroll_up(t, t->scroll_top, t->scroll_bot, 1);
689 else if (c->y < t->rows - 1)
690 c->y++;
691 t->state = TSTATE_NORMAL;
692 }
693 else {
694 t->state = TSTATE_NORMAL;
695 }
696 break;
697
698 case TSTATE_CSI:
699 if (ch >= '0' && ch <= '9') {
700 t->csi_params[t->csi_idx] *= 10;
701 t->csi_params[t->csi_idx] += ch - '0';
702 }
703 else if (ch == ';') {
704 if (t->csi_idx + 1 < CSI_PARAMS_MAX)
705 t->csi_idx++;
706 }
707 else if (ch == '?' || ch == '>' || ch == '=') {
708 /* private CSI marker */
709 }
710 else {
711 csi_dispatch(t, c, ch);
712 t->state = TSTATE_NORMAL;
713 }
714 break;
715
716 case TSTATE_OSC:
717 if (ch == '\a')
718 t->state = TSTATE_NORMAL;
719 else if (ch == '\x1B')
720 t->state = TSTATE_OSC_ESC;
721 break;
722
723 case TSTATE_OSC_ESC:
724 t->state = ch == '\\' ? TSTATE_NORMAL : TSTATE_OSC;
725 break;
726
727 case TSTATE_CHARSET:
728 t->acs[t->charset_target] = ch == '0';
729 t->state = TSTATE_NORMAL;
730 break;
731
732 case TSTATE_CHARSET_SKIP:
733 t->state = TSTATE_NORMAL;
734 break;
735 }
736
737 if (old.x != c->x || old.y != c->y)
738 damaged = true;
739 }
740
741 for (int y = 0; y < t->rows; y++) {
742 for (int x = 0; x < t->cols; x++) {
743 if (RUNE(t, x, y).dmg)
744 return true;
745 }
746 }
747
748 return damaged;
749}
750