vis

a vi-like editor based on Plan 9's structural regular expressions

git clone https://9o.is/git/vis.git

ui-terminal-vt100.c

(6306B)


      1 /* This file is included from ui-terminal.c
      2  *
      3  * The goal is *not* to reimplement curses. Instead we aim to provide the
      4  * simplest possible drawing backend for VT-100 compatible terminals.
      5  * This is useful for debugging and fuzzing purposes as well as for environments
      6  * with no curses support.
      7  *
      8  * Currently no attempt is made to optimize terminal output. The amount of
      9  * flickering will depend on the smartness of your terminal emulator.
     10  *
     11  * The following terminal escape sequences are used:
     12  *
     13  *  - CSI ? 1049 h             Save cursor and use Alternate Screen Buffer (DECSET)
     14  *  - CSI ? 1049 l             Use Normal Screen Buffer and restore cursor (DECRST)
     15  *  - CSI ? 25 l               Hide Cursor (DECTCEM)
     16  *  - CSI ? 25 h               Show Cursor (DECTCEM)
     17  *  - CSI 2 J                  Erase in Display (ED)
     18  *  - CSI row ; column H       Cursor Position (CUP)
     19  *  - CSI ... m                Character Attributes (SGR)
     20  *    - CSI 0 m                     Normal
     21  *    - CSI 1 m                     Bold
     22  *    - CSI 3 m                     Italicized
     23  *    - CSI 4 m                     Underlined
     24  *    - CSI 5 m                     Blink
     25  *    - CSI 7 m                     Inverse
     26  *    - CSI 22 m                    Normal (not bold)
     27  *    - CSI 23 m                    Not italicized
     28  *    - CSI 24 m                    Not underlined
     29  *    - CSI 25 m                    Not blinking
     30  *    - CSI 27 m                    Not inverse
     31  *    - CSI 30-37,39                Set foreground color
     32  *    - CSI 38 ; 2 ; R ; G ; B m    Set RGB foreground color
     33  *    - CSI 40-47,49                Set background color
     34  *    - CSI 48 ; 2 ; R ; G ; B m    Set RGB background color
     35  *
     36  * See http://invisible-island.net/xterm/ctlseqs/ctlseqs.txt
     37  * for further information.
     38  */
     39 #include "buffer.h"
     40 
     41 #define UI_TERMKEY_FLAGS TERMKEY_FLAG_UTF8
     42 
     43 #define CELL_COLOR_BLACK   { .index = 0 }
     44 #define CELL_COLOR_RED     { .index = 1 }
     45 #define CELL_COLOR_GREEN   { .index = 2 }
     46 #define CELL_COLOR_YELLOW  { .index = 3 }
     47 #define CELL_COLOR_BLUE    { .index = 4 }
     48 #define CELL_COLOR_MAGENTA { .index = 5 }
     49 #define CELL_COLOR_CYAN    { .index = 6 }
     50 #define CELL_COLOR_WHITE   { .index = 7 }
     51 #define CELL_COLOR_DEFAULT { .index = 9 }
     52 
     53 #define CELL_ATTR_NORMAL    0
     54 #define CELL_ATTR_UNDERLINE (1 << 0)
     55 #define CELL_ATTR_REVERSE   (1 << 1)
     56 #define CELL_ATTR_BLINK     (1 << 2)
     57 #define CELL_ATTR_BOLD      (1 << 3)
     58 #define CELL_ATTR_ITALIC    (1 << 4)
     59 #define CELL_ATTR_DIM       (1 << 5)
     60 
     61 static inline bool cell_color_equal(CellColor c1, CellColor c2) {
     62 	if (c1.index != (uint8_t)-1 || c2.index != (uint8_t)-1)
     63 		return c1.index == c2.index;
     64 	return c1.r == c2.r && c1.g == c2.g && c1.b == c2.b;
     65 }
     66 
     67 static CellColor color_rgb(Ui *ui, uint8_t r, uint8_t g, uint8_t b) {
     68 	return (CellColor){ .r = r, .g = g, .b = b, .index = (uint8_t)-1 };
     69 }
     70 
     71 static CellColor color_terminal(Ui *ui, uint8_t index) {
     72 	return (CellColor){ .r = 0, .g = 0, .b = 0, .index = index };
     73 }
     74 
     75 
     76 static void output(const char *data, size_t len) {
     77 	write(STDERR_FILENO, data, len);
     78 }
     79 
     80 static void output_literal(const char *data) {
     81 	output(data, strlen(data));
     82 }
     83 
     84 static void screen_alternate(bool alternate) {
     85 	output_literal(alternate ? "\x1b[?1049h" : "\x1b[0m" "\x1b[?1049l" "\x1b[0m" );
     86 }
     87 
     88 static void cursor_visible(bool visible) {
     89 	output_literal(visible ? "\x1b[?25h" : "\x1b[?25l");
     90 }
     91 
     92 static void ui_term_backend_blit(Ui *tui) {
     93 	Buffer *buf = tui->ctx;
     94 	buf->len    = 0;
     95 	CellAttr attr = CELL_ATTR_NORMAL;
     96 	CellColor fg = CELL_COLOR_DEFAULT, bg = CELL_COLOR_DEFAULT;
     97 	int w = tui->width, h = tui->height;
     98 	Cell *cell = tui->cells;
     99 	/* reposition cursor, erase screen, reset attributes */
    100 	buffer_append0(buf, "\x1b[H" "\x1b[J" "\x1b[0m");
    101 	for (int y = 0; y < h; y++) {
    102 		for (int x = 0; x < w; x++) {
    103 			CellStyle *style = &cell->style;
    104 			if (style->attr != attr) {
    105 
    106 				static const struct {
    107 					CellAttr attr;
    108 					char on[4], off[4];
    109 				} cell_attrs[] = {
    110 					{ CELL_ATTR_BOLD, "1", "22" },
    111 					{ CELL_ATTR_DIM, "2", "22" },
    112 					{ CELL_ATTR_ITALIC, "3", "23" },
    113 					{ CELL_ATTR_UNDERLINE, "4", "24" },
    114 					{ CELL_ATTR_BLINK, "5", "25" },
    115 					{ CELL_ATTR_REVERSE, "7", "27" },
    116 				};
    117 
    118 				for (size_t i = 0; i < LENGTH(cell_attrs); i++) {
    119 					CellAttr a = cell_attrs[i].attr;
    120 					if ((style->attr & a) == (attr & a))
    121 						continue;
    122 					buffer_appendf(buf, "\x1b[%sm",
    123 					               style->attr & a ?
    124 					               cell_attrs[i].on :
    125 					               cell_attrs[i].off);
    126 				}
    127 
    128 				attr = style->attr;
    129 			}
    130 
    131 			if (!cell_color_equal(fg, style->fg)) {
    132 				fg = style->fg;
    133 				if (fg.index != (uint8_t)-1) {
    134 					buffer_appendf(buf, "\x1b[%dm", 30 + fg.index);
    135 				} else {
    136 					buffer_appendf(buf, "\x1b[38;2;%d;%d;%dm",
    137 					               fg.r, fg.g, fg.b);
    138 				}
    139 			}
    140 
    141 			if (!cell_color_equal(bg, style->bg)) {
    142 				bg = style->bg;
    143 				if (bg.index != (uint8_t)-1) {
    144 					buffer_appendf(buf, "\x1b[%dm", 40 + bg.index);
    145 				} else {
    146 					buffer_appendf(buf, "\x1b[48;2;%d;%d;%dm",
    147 					               bg.r, bg.g, bg.b);
    148 				}
    149 			}
    150 
    151 			buffer_append0(buf, cell->data);
    152 			cell++;
    153 		}
    154 	}
    155 	output(buf->data, buffer_length0(buf));
    156 }
    157 
    158 static void ui_term_backend_clear(Ui *tui) { }
    159 
    160 static bool ui_term_backend_resize(Ui *tui, int width, int height) {
    161 	return true;
    162 }
    163 
    164 static void ui_term_backend_save(Ui *tui, bool fscr) {
    165 	cursor_visible(true);
    166 }
    167 
    168 static void ui_term_backend_restore(Ui *tui) {
    169 	cursor_visible(false);
    170 }
    171 
    172 int ui_terminal_colors(void) {
    173 	char *term = getenv("TERM");
    174 	return (term && strstr(term, "-256color")) ? 256 : 16;
    175 }
    176 
    177 static void ui_term_backend_suspend(Ui *tui) {
    178 	if (!tui->termkey) return;
    179 	termkey_stop(tui->termkey);
    180 	cursor_visible(true);
    181 	screen_alternate(false);
    182 }
    183 
    184 void ui_terminal_resume(Ui *tui) {
    185 	screen_alternate(true);
    186 	cursor_visible(false);
    187 	termkey_start(tui->termkey);
    188 }
    189 
    190 static bool ui_term_backend_init(Ui *tui, char *term) {
    191 	ui_terminal_resume(tui);
    192 	return true;
    193 }
    194 
    195 static bool ui_backend_init(Ui *ui) {
    196 	Buffer *buf = calloc(1, sizeof(Buffer));
    197 	if (!buf)
    198 		return false;
    199 	ui->ctx = buf;
    200 	return true;
    201 }
    202 
    203 static void ui_term_backend_free(Ui *tui) {
    204 	Buffer *buf = tui->ctx;
    205 	ui_term_backend_suspend(tui);
    206 	buffer_release(buf);
    207 	free(buf);
    208 }
    209 
    210 static bool is_default_color(CellColor c) {
    211 	return c.index == ((CellColor) CELL_COLOR_DEFAULT).index;
    212 }