main uint/cterm / source / term.c
  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