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 reset_rune(t, x, y);
454 }
455
456 c->x = 0;
457 c->y = 0;
458 t->wrapnext = false;
459}
460
461void term_damage_all(Term* t)
462{
463 for (int y = 0; y < t->rows; y++) {
464 for (int x = 0; x < t->cols; x++)
465 RUNE(t, x, y).dmg = true;
466 }
467}
468
469void term_damage_rune(Term* t, int x, int y)
470{
471 if (x < 0 || y < 0 || x >= t->cols || y >= t->rows)
472 return;
473 RUNE(t, x, y).dmg = true;
474}
475
476void term_putc(Term* t, Caret* c, uint32_t cp)
477{
478 switch (cp) {
479 case '\r':
480 c->x = 0;
481 t->wrapnext = false;
482 break;
483 case '\n':
484 c->x = 0;
485 t->wrapnext = false;
486 if (c->y == t->scroll_bot) {
487 term_scroll(t);
488 }
489 else if (c->y < t->rows - 1)
490 c->y++;
491 break;
492 case '\b':
493 t->wrapnext = false;
494 if (c->x > 0)
495 c->x--;
496 break;
497 case '\t':
498 t->wrapnext = false;
499 /* move cursor to next tabstop */
500 c->x = (c->x+8) & ~0x7;
501 if (c->x >= t->cols)
502 term_putc(t, c, '\n');
503 break;
504 default: {
505 if (cp < 32)
506 break;
507
508 if (t->wrapnext) {
509 term_putc(t, c, '\n');
510 t->wrapnext = false;
511 }
512
513 rune_prepare(t, c->x, c->y);
514 Rune* r = &RUNE(t, c->x, c->y);
515 if (r->cp != cp || r->fg != t->fg || r->bg != t->bg ||
516 r->attr != t->attr || r->width != 1) {
517 r->cp = cp;
518 r->fg = t->fg;
519 r->bg = t->bg;
520 r->attr = t->attr;
521 r->width = 1;
522 r->dmg = true;
523 }
524
525 if (c->x + 1 >= t->cols)
526 t->wrapnext = true;
527 else
528 c->x++;
529
530 break;
531 }
532 }
533}
534
535int term_resize(Term* t, Caret* c, int cols, int rows)
536{
537 /* old */
538 Rune* or = t->runes;
539 int ocols = t->cols;
540 int orows = t->rows;
541
542 if (t->alt) {
543 free(t->runes);
544 t->runes = t->alt;
545 t->alt = NULL;
546 or = t->runes;
547 }
548
549 if (cols < 1)
550 cols = 1;
551 if (rows < 1)
552 rows = 1;
553
554 /* new */
555 Rune* nr = calloc(cols*rows, sizeof(*nr));
556 if (!nr)
557 return -1;
558
559 for (int y = 0; y < rows; y++) {
560 for (int x = 0; x < cols; x++) {
561 Rune* r = &nr[y * cols + x];
562 r->cp = ' ';
563 r->fg = t->fg;
564 r->bg = t->bg;
565 r->attr = t->attr;
566 r->width = 1;
567 r->dmg = true;
568 }
569 }
570
571 if (or) {
572 int copyrows = orows < rows ? orows : rows;
573 int copycols = ocols < cols ? ocols : cols;
574
575 for (int y = 0; y < copyrows; y++)
576 memcpy( &nr[y * cols], &or[y * ocols], sizeof(Rune) * copycols);
577 }
578
579 for (int y = 0; y < rows; y++) {
580 for (int x = 0; x < cols; x++)
581 nr[y * cols + x].dmg = true;
582 }
583
584 free(or);
585
586 t->runes = nr;
587 t->cols = cols;
588 t->rows = rows;
589 t->scroll_top = 0;
590 t->scroll_bot = rows - 1;
591
592 if (c->x >= cols)
593 c->x = cols - 1;
594 if (c->y >= rows)
595 c->y = rows - 1;
596 if (c->x < 0)
597 c->x = 0;
598 if (c->y < 0)
599 c->y = 0;
600 t->wrapnext = false;
601 if (t->saved.x >= cols)
602 t->saved.x = cols - 1;
603 if (t->saved.y >= rows)
604 t->saved.y = rows - 1;
605
606 return 0;
607}
608
609void term_scroll(Term* t)
610{
611 scroll_up(t, t->scroll_top, t->scroll_bot, 1);
612}
613
614bool term_write(Term* t, Caret* c, const char* s, size_t n)
615{
616 bool damaged = false;
617
618 for (size_t i = 0; i < n; i++) {
619 unsigned char ch = (unsigned char)s[i];
620 Caret old = *c;
621
622 switch (t->state) {
623 case TSTATE_NORMAL:
624 if (ch == '\x1B') {
625 t->state = TSTATE_ESC;
626 }
627 else if (ch == '\x0E') {
628 t->charset = 1;
629 }
630 else if (ch == '\x0F') {
631 t->charset = 0;
632 }
633 else if (ch >= 0x80) {
634 /* utf8 unsupported . ignore high bit bytes */
635 }
636 else {
637 term_putc(t, c, t->acs[t->charset] ? acs_map(ch) : ch);
638 }
639 break;
640
641 case TSTATE_ESC:
642 if (ch == '[') {
643 csi_reset(t);
644 t->state = TSTATE_CSI;
645 }
646 else if (ch == ']') {
647 t->state = TSTATE_OSC;
648 }
649 else if (ch == '(') {
650 t->charset_target = 0;
651 t->state = TSTATE_CHARSET;
652 }
653 else if (ch == ')') {
654 t->charset_target = 1;
655 t->state = TSTATE_CHARSET;
656 }
657 else if (ch == '*' || ch == '+') {
658 t->state = TSTATE_CHARSET_SKIP;
659 }
660 else if (ch == '7') {
661 t->saved = *c;
662 t->state = TSTATE_NORMAL;
663 }
664 else if (ch == '8') {
665 *c = t->saved;
666 t->state = TSTATE_NORMAL;
667 }
668 else if (ch == 'M') {
669 t->wrapnext = false;
670 if (c->y == t->scroll_top)
671 scroll_down(t, t->scroll_top, t->scroll_bot, 1);
672 else if (c->y > 0)
673 c->y--;
674 t->state = TSTATE_NORMAL;
675 }
676 else if (ch == 'D' || ch == 'E') {
677 t->wrapnext = false;
678 if (ch == 'E')
679 c->x = 0;
680 if (c->y == t->scroll_bot)
681 scroll_up(t, t->scroll_top, t->scroll_bot, 1);
682 else if (c->y < t->rows - 1)
683 c->y++;
684 t->state = TSTATE_NORMAL;
685 }
686 else {
687 t->state = TSTATE_NORMAL;
688 }
689 break;
690
691 case TSTATE_CSI:
692 if (ch >= '0' && ch <= '9') {
693 t->csi_params[t->csi_idx] *= 10;
694 t->csi_params[t->csi_idx] += ch - '0';
695 }
696 else if (ch == ';') {
697 if (t->csi_idx + 1 < CSI_PARAMS_MAX)
698 t->csi_idx++;
699 }
700 else if (ch == '?' || ch == '>' || ch == '=') {
701 /* private CSI marker */
702 }
703 else {
704 csi_dispatch(t, c, ch);
705 t->state = TSTATE_NORMAL;
706 }
707 break;
708
709 case TSTATE_OSC:
710 if (ch == '\a')
711 t->state = TSTATE_NORMAL;
712 else if (ch == '\x1B')
713 t->state = TSTATE_OSC_ESC;
714 break;
715
716 case TSTATE_OSC_ESC:
717 t->state = ch == '\\' ? TSTATE_NORMAL : TSTATE_OSC;
718 break;
719
720 case TSTATE_CHARSET:
721 t->acs[t->charset_target] = ch == '0';
722 t->state = TSTATE_NORMAL;
723 break;
724
725 case TSTATE_CHARSET_SKIP:
726 t->state = TSTATE_NORMAL;
727 break;
728 }
729
730 if (old.x != c->x || old.y != c->y)
731 damaged = true;
732 }
733
734 return damaged;
735}
736