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			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