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 }