Fast and reasonably complete (framebuffer) terminal emulator (Zig fork)
1#include <stdint.h>
2#include <stddef.h>
3#include <stdbool.h>
4
5#include "term.h"
6
7static const uint32_t col256[] = {
8 0x000000, 0x00005f, 0x000087, 0x0000af, 0x0000d7, 0x0000ff, 0x005f00, 0x005f5f,
9 0x005f87, 0x005faf, 0x005fd7, 0x005fff, 0x008700, 0x00875f, 0x008787, 0x0087af,
10 0x0087d7, 0x0087ff, 0x00af00, 0x00af5f, 0x00af87, 0x00afaf, 0x00afd7, 0x00afff,
11 0x00d700, 0x00d75f, 0x00d787, 0x00d7af, 0x00d7d7, 0x00d7ff, 0x00ff00, 0x00ff5f,
12 0x00ff87, 0x00ffaf, 0x00ffd7, 0x00ffff, 0x5f0000, 0x5f005f, 0x5f0087, 0x5f00af,
13 0x5f00d7, 0x5f00ff, 0x5f5f00, 0x5f5f5f, 0x5f5f87, 0x5f5faf, 0x5f5fd7, 0x5f5fff,
14 0x5f8700, 0x5f875f, 0x5f8787, 0x5f87af, 0x5f87d7, 0x5f87ff, 0x5faf00, 0x5faf5f,
15 0x5faf87, 0x5fafaf, 0x5fafd7, 0x5fafff, 0x5fd700, 0x5fd75f, 0x5fd787, 0x5fd7af,
16 0x5fd7d7, 0x5fd7ff, 0x5fff00, 0x5fff5f, 0x5fff87, 0x5fffaf, 0x5fffd7, 0x5fffff,
17 0x870000, 0x87005f, 0x870087, 0x8700af, 0x8700d7, 0x8700ff, 0x875f00, 0x875f5f,
18 0x875f87, 0x875faf, 0x875fd7, 0x875fff, 0x878700, 0x87875f, 0x878787, 0x8787af,
19 0x8787d7, 0x8787ff, 0x87af00, 0x87af5f, 0x87af87, 0x87afaf, 0x87afd7, 0x87afff,
20 0x87d700, 0x87d75f, 0x87d787, 0x87d7af, 0x87d7d7, 0x87d7ff, 0x87ff00, 0x87ff5f,
21 0x87ff87, 0x87ffaf, 0x87ffd7, 0x87ffff, 0xaf0000, 0xaf005f, 0xaf0087, 0xaf00af,
22 0xaf00d7, 0xaf00ff, 0xaf5f00, 0xaf5f5f, 0xaf5f87, 0xaf5faf, 0xaf5fd7, 0xaf5fff,
23 0xaf8700, 0xaf875f, 0xaf8787, 0xaf87af, 0xaf87d7, 0xaf87ff, 0xafaf00, 0xafaf5f,
24 0xafaf87, 0xafafaf, 0xafafd7, 0xafafff, 0xafd700, 0xafd75f, 0xafd787, 0xafd7af,
25 0xafd7d7, 0xafd7ff, 0xafff00, 0xafff5f, 0xafff87, 0xafffaf, 0xafffd7, 0xafffff,
26 0xd70000, 0xd7005f, 0xd70087, 0xd700af, 0xd700d7, 0xd700ff, 0xd75f00, 0xd75f5f,
27 0xd75f87, 0xd75faf, 0xd75fd7, 0xd75fff, 0xd78700, 0xd7875f, 0xd78787, 0xd787af,
28 0xd787d7, 0xd787ff, 0xd7af00, 0xd7af5f, 0xd7af87, 0xd7afaf, 0xd7afd7, 0xd7afff,
29 0xd7d700, 0xd7d75f, 0xd7d787, 0xd7d7af, 0xd7d7d7, 0xd7d7ff, 0xd7ff00, 0xd7ff5f,
30 0xd7ff87, 0xd7ffaf, 0xd7ffd7, 0xd7ffff, 0xff0000, 0xff005f, 0xff0087, 0xff00af,
31 0xff00d7, 0xff00ff, 0xff5f00, 0xff5f5f, 0xff5f87, 0xff5faf, 0xff5fd7, 0xff5fff,
32 0xff8700, 0xff875f, 0xff8787, 0xff87af, 0xff87d7, 0xff87ff, 0xffaf00, 0xffaf5f,
33 0xffaf87, 0xffafaf, 0xffafd7, 0xffafff, 0xffd700, 0xffd75f, 0xffd787, 0xffd7af,
34 0xffd7d7, 0xffd7ff, 0xffff00, 0xffff5f, 0xffff87, 0xffffaf, 0xffffd7, 0xffffff,
35 0x080808, 0x121212, 0x1c1c1c, 0x262626, 0x303030, 0x3a3a3a, 0x444444, 0x4e4e4e,
36 0x585858, 0x626262, 0x6c6c6c, 0x767676, 0x808080, 0x8a8a8a, 0x949494, 0x9e9e9e,
37 0xa8a8a8, 0xb2b2b2, 0xbcbcbc, 0xc6c6c6, 0xd0d0d0, 0xdadada, 0xe4e4e4, 0xeeeeee
38};
39
40// Tries to implement this standard for terminfo
41// https://man7.org/linux/man-pages/man4/console_codes.4.html
42
43#define CHARSET_DEFAULT 0
44#define CHARSET_DEC_SPECIAL 1
45
46void term_context_reinit(struct term_context *ctx) {
47 ctx->tab_size = 8;
48 ctx->autoflush = true;
49 ctx->cursor_enabled = true;
50 ctx->scroll_enabled = true;
51 ctx->control_sequence = false;
52 ctx->csi = false;
53 ctx->escape = false;
54 ctx->osc = false;
55 ctx->osc_escape = false;
56 ctx->rrr = false;
57 ctx->discard_next = false;
58 ctx->bold = false;
59 ctx->bg_bold = false;
60 ctx->reverse_video = false;
61 ctx->dec_private = false;
62 ctx->insert_mode = false;
63 ctx->unicode_remaining = 0;
64 ctx->g_select = 0;
65 ctx->charsets[0] = CHARSET_DEFAULT;
66 ctx->charsets[1] = CHARSET_DEC_SPECIAL;
67 ctx->current_charset = 0;
68 ctx->escape_offset = 0;
69 ctx->esc_values_i = 0;
70 ctx->saved_cursor_x = 0;
71 ctx->saved_cursor_y = 0;
72 ctx->current_primary = (size_t)-1;
73 ctx->current_bg = (size_t)-1;
74 ctx->scroll_top_margin = 0;
75 ctx->scroll_bottom_margin = ctx->rows;
76 ctx->oob_output = TERM_OOB_OUTPUT_ONLCR;
77}
78
79static void term_putchar(struct term_context *ctx, uint8_t c);
80
81void term_write(struct term_context *ctx, const char *buf, size_t count) {
82 for (size_t i = 0; i < count; i++) {
83 term_putchar(ctx, buf[i]);
84 }
85
86 if (ctx->autoflush) {
87 ctx->double_buffer_flush(ctx);
88 }
89}
90
91static void sgr(struct term_context *ctx) {
92 size_t i = 0;
93
94 if (!ctx->esc_values_i)
95 goto def;
96
97 for (; i < ctx->esc_values_i; i++) {
98 size_t offset;
99
100 if (ctx->esc_values[i] == 0) {
101def:
102 if (ctx->reverse_video) {
103 ctx->reverse_video = false;
104 ctx->swap_palette(ctx);
105 }
106 ctx->bold = false;
107 ctx->bg_bold = false;
108 ctx->current_primary = (size_t)-1;
109 ctx->current_bg = (size_t)-1;
110 ctx->set_text_bg_default(ctx);
111 ctx->set_text_fg_default(ctx);
112 continue;
113 }
114
115 else if (ctx->esc_values[i] == 1) {
116 ctx->bold = true;
117 if (ctx->current_primary != (size_t)-1) {
118 if (!ctx->reverse_video) {
119 ctx->set_text_fg_bright(ctx, ctx->current_primary);
120 } else {
121 ctx->set_text_bg_bright(ctx, ctx->current_primary);
122 }
123 } else {
124 if (!ctx->reverse_video) {
125 ctx->set_text_fg_default_bright(ctx);
126 } else {
127 ctx->set_text_bg_default_bright(ctx);
128 }
129 }
130 continue;
131 }
132
133 else if (ctx->esc_values[i] == 5) {
134 ctx->bg_bold = true;
135 if (ctx->current_bg != (size_t)-1) {
136 if (!ctx->reverse_video) {
137 ctx->set_text_bg_bright(ctx, ctx->current_bg);
138 } else {
139 ctx->set_text_fg_bright(ctx, ctx->current_bg);
140 }
141 } else {
142 if (!ctx->reverse_video) {
143 ctx->set_text_bg_default_bright(ctx);
144 } else {
145 ctx->set_text_fg_default_bright(ctx);
146 }
147 }
148 continue;
149 }
150
151 else if (ctx->esc_values[i] == 22) {
152 ctx->bold = false;
153 if (ctx->current_primary != (size_t)-1) {
154 if (!ctx->reverse_video) {
155 ctx->set_text_fg(ctx, ctx->current_primary);
156 } else {
157 ctx->set_text_bg(ctx, ctx->current_primary);
158 }
159 } else {
160 if (!ctx->reverse_video) {
161 ctx->set_text_fg_default(ctx);
162 } else {
163 ctx->set_text_bg_default(ctx);
164 }
165 }
166 continue;
167 }
168
169 else if (ctx->esc_values[i] == 25) {
170 ctx->bg_bold = false;
171 if (ctx->current_bg != (size_t)-1) {
172 if (!ctx->reverse_video) {
173 ctx->set_text_bg(ctx, ctx->current_bg);
174 } else {
175 ctx->set_text_fg(ctx, ctx->current_bg);
176 }
177 } else {
178 if (!ctx->reverse_video) {
179 ctx->set_text_bg_default(ctx);
180 } else {
181 ctx->set_text_fg_default(ctx);
182 }
183 }
184 continue;
185 }
186
187 else if (ctx->esc_values[i] >= 30 && ctx->esc_values[i] <= 37) {
188 offset = 30;
189 ctx->current_primary = ctx->esc_values[i] - offset;
190
191 if (ctx->reverse_video) {
192 goto set_bg;
193 }
194
195set_fg:
196 if ((ctx->bold && !ctx->reverse_video)
197 || (ctx->bg_bold && ctx->reverse_video)) {
198 ctx->set_text_fg_bright(ctx, ctx->esc_values[i] - offset);
199 } else {
200 ctx->set_text_fg(ctx, ctx->esc_values[i] - offset);
201 }
202 continue;
203 }
204
205 else if (ctx->esc_values[i] >= 40 && ctx->esc_values[i] <= 47) {
206 offset = 40;
207 ctx->current_bg = ctx->esc_values[i] - offset;
208
209 if (ctx->reverse_video) {
210 goto set_fg;
211 }
212
213set_bg:
214 if ((ctx->bold && ctx->reverse_video)
215 || (ctx->bg_bold && !ctx->reverse_video)) {
216 ctx->set_text_bg_bright(ctx, ctx->esc_values[i] - offset);
217 } else {
218 ctx->set_text_bg(ctx, ctx->esc_values[i] - offset);
219 }
220 continue;
221 }
222
223 else if (ctx->esc_values[i] >= 90 && ctx->esc_values[i] <= 97) {
224 offset = 90;
225 ctx->current_primary = ctx->esc_values[i] - offset;
226
227 if (ctx->reverse_video) {
228 goto set_bg_bright;
229 }
230
231set_fg_bright:
232 ctx->set_text_fg_bright(ctx, ctx->esc_values[i] - offset);
233 continue;
234 }
235
236 else if (ctx->esc_values[i] >= 100 && ctx->esc_values[i] <= 107) {
237 offset = 100;
238 ctx->current_bg = ctx->esc_values[i] - offset;
239
240 if (ctx->reverse_video) {
241 goto set_fg_bright;
242 }
243
244set_bg_bright:
245 ctx->set_text_bg_bright(ctx, ctx->esc_values[i] - offset);
246 continue;
247 }
248
249 else if (ctx->esc_values[i] == 39) {
250 ctx->current_primary = (size_t)-1;
251
252 if (ctx->reverse_video) {
253 ctx->swap_palette(ctx);
254 }
255
256 if (!ctx->bold) {
257 ctx->set_text_fg_default(ctx);
258 } else {
259 ctx->set_text_fg_default_bright(ctx);
260 }
261
262 if (ctx->reverse_video) {
263 ctx->swap_palette(ctx);
264 }
265
266 continue;
267 }
268
269 else if (ctx->esc_values[i] == 49) {
270 ctx->current_bg = (size_t)-1;
271
272 if (ctx->reverse_video) {
273 ctx->swap_palette(ctx);
274 }
275
276 if (!ctx->bg_bold) {
277 ctx->set_text_bg_default(ctx);
278 } else {
279 ctx->set_text_bg_default_bright(ctx);
280 }
281
282 if (ctx->reverse_video) {
283 ctx->swap_palette(ctx);
284 }
285
286 continue;
287 }
288
289 else if (ctx->esc_values[i] == 7) {
290 if (!ctx->reverse_video) {
291 ctx->reverse_video = true;
292 ctx->swap_palette(ctx);
293 }
294 continue;
295 }
296
297 else if (ctx->esc_values[i] == 27) {
298 if (ctx->reverse_video) {
299 ctx->reverse_video = false;
300 ctx->swap_palette(ctx);
301 }
302 continue;
303 }
304
305 // 256/RGB
306 else if (ctx->esc_values[i] == 38 || ctx->esc_values[i] == 48) {
307 bool fg = ctx->esc_values[i] == 38;
308
309 i++;
310 if (i >= ctx->esc_values_i) {
311 break;
312 }
313
314 switch (ctx->esc_values[i]) {
315 case 2: { // RGB
316 if (i + 3 >= ctx->esc_values_i) {
317 goto out;
318 }
319
320 uint32_t rgb_value = 0;
321
322 rgb_value |= ctx->esc_values[i + 1] << 16;
323 rgb_value |= ctx->esc_values[i + 2] << 8;
324 rgb_value |= ctx->esc_values[i + 3];
325
326 i += 3;
327
328 (fg ? ctx->set_text_fg_rgb : ctx->set_text_bg_rgb)(ctx, rgb_value);
329
330 break;
331 }
332 case 5: { // 256 colors
333 if (i + 1 >= ctx->esc_values_i) {
334 goto out;
335 }
336
337 uint32_t col = ctx->esc_values[i + 1];
338
339 i++;
340
341 if (col < 8) {
342 (fg ? ctx->set_text_fg : ctx->set_text_bg)(ctx, col);
343 } else if (col < 16) {
344 (fg ? ctx->set_text_fg_bright : ctx->set_text_bg_bright)(ctx, col - 8);
345 } else {
346 uint32_t rgb_value = col256[col - 16];
347 (fg ? ctx->set_text_fg_rgb : ctx->set_text_bg_rgb)(ctx, rgb_value);
348 }
349
350 break;
351 }
352 default: continue;
353 }
354 }
355 }
356
357out:;
358}
359
360static void dec_private_parse(struct term_context *ctx, uint8_t c) {
361 ctx->dec_private = false;
362
363 if (ctx->esc_values_i == 0) {
364 return;
365 }
366
367 bool set;
368
369 switch (c) {
370 case 'h':
371 set = true; break;
372 case 'l':
373 set = false; break;
374 default:
375 return;
376 }
377
378 switch (ctx->esc_values[0]) {
379 case 25: {
380 if (set) {
381 ctx->cursor_enabled = true;
382 } else {
383 ctx->cursor_enabled = false;
384 }
385 return;
386 }
387 }
388
389 if (ctx->callback != NULL) {
390 ctx->callback(ctx, TERM_CB_DEC, ctx->esc_values_i, (uintptr_t)ctx->esc_values, c);
391 }
392}
393
394static void linux_private_parse(struct term_context *ctx) {
395 if (ctx->esc_values_i == 0) {
396 return;
397 }
398
399 if (ctx->callback != NULL) {
400 ctx->callback(ctx, TERM_CB_LINUX, ctx->esc_values_i, (uintptr_t)ctx->esc_values, 0);
401 }
402}
403
404static void mode_toggle(struct term_context *ctx, uint8_t c) {
405 if (ctx->esc_values_i == 0) {
406 return;
407 }
408
409 bool set;
410
411 switch (c) {
412 case 'h':
413 set = true; break;
414 case 'l':
415 set = false; break;
416 default:
417 return;
418 }
419
420 switch (ctx->esc_values[0]) {
421 case 4:
422 ctx->insert_mode = set; return;
423 }
424
425 if (ctx->callback != NULL) {
426 ctx->callback(ctx, TERM_CB_MODE, ctx->esc_values_i, (uintptr_t)ctx->esc_values, c);
427 }
428}
429
430static void osc_parse(struct term_context *ctx, uint8_t c) {
431 if (ctx->osc_escape && c == '\\') {
432 goto cleanup;
433 }
434
435 ctx->osc_escape = false;
436
437 switch (c) {
438 case 0x1b:
439 ctx->osc_escape = true;
440 break;
441 case '\a':
442 goto cleanup;
443 }
444
445 return;
446
447cleanup:
448 ctx->osc_escape = false;
449 ctx->osc = false;
450 ctx->escape = false;
451}
452
453static void control_sequence_parse(struct term_context *ctx, uint8_t c) {
454 if (ctx->escape_offset == 2) {
455 switch (c) {
456 case '[':
457 ctx->discard_next = true;
458 goto cleanup;
459 case '?':
460 ctx->dec_private = true;
461 return;
462 }
463 }
464
465 if (c >= '0' && c <= '9') {
466 if (ctx->esc_values_i == TERM_MAX_ESC_VALUES) {
467 return;
468 }
469 ctx->rrr = true;
470 ctx->esc_values[ctx->esc_values_i] *= 10;
471 ctx->esc_values[ctx->esc_values_i] += c - '0';
472 return;
473 }
474
475 if (ctx->rrr == true) {
476 ctx->esc_values_i++;
477 ctx->rrr = false;
478 if (c == ';')
479 return;
480 } else if (c == ';') {
481 if (ctx->esc_values_i == TERM_MAX_ESC_VALUES) {
482 return;
483 }
484 ctx->esc_values[ctx->esc_values_i] = 0;
485 ctx->esc_values_i++;
486 return;
487 }
488
489 size_t esc_default;
490 switch (c) {
491 case 'J': case 'K': case 'q':
492 esc_default = 0; break;
493 default:
494 esc_default = 1; break;
495 }
496
497 for (size_t i = ctx->esc_values_i; i < TERM_MAX_ESC_VALUES; i++) {
498 ctx->esc_values[i] = esc_default;
499 }
500
501 if (ctx->dec_private == true) {
502 dec_private_parse(ctx, c);
503 goto cleanup;
504 }
505
506 bool r = ctx->scroll_enabled;
507 ctx->scroll_enabled = false;
508 size_t x, y;
509 ctx->get_cursor_pos(ctx, &x, &y);
510
511 switch (c) {
512 case 'F':
513 x = 0;
514 // FALLTHRU
515 case 'A': {
516 if (ctx->esc_values[0] > y)
517 ctx->esc_values[0] = y;
518 size_t orig_y = y;
519 size_t dest_y = y - ctx->esc_values[0];
520 bool will_be_in_scroll_region = false;
521 if ((ctx->scroll_top_margin >= dest_y && ctx->scroll_top_margin <= orig_y)
522 || (ctx->scroll_bottom_margin >= dest_y && ctx->scroll_bottom_margin <= orig_y)) {
523 will_be_in_scroll_region = true;
524 }
525 if (will_be_in_scroll_region && dest_y < ctx->scroll_top_margin) {
526 dest_y = ctx->scroll_top_margin;
527 }
528 ctx->set_cursor_pos(ctx, x, dest_y);
529 break;
530 }
531 case 'E':
532 x = 0;
533 // FALLTHRU
534 case 'e':
535 case 'B': {
536 if (y + ctx->esc_values[0] > ctx->rows - 1)
537 ctx->esc_values[0] = (ctx->rows - 1) - y;
538 size_t orig_y = y;
539 size_t dest_y = y + ctx->esc_values[0];
540 bool will_be_in_scroll_region = false;
541 if ((ctx->scroll_top_margin >= orig_y && ctx->scroll_top_margin <= dest_y)
542 || (ctx->scroll_bottom_margin >= orig_y && ctx->scroll_bottom_margin <= dest_y)) {
543 will_be_in_scroll_region = true;
544 }
545 if (will_be_in_scroll_region && dest_y >= ctx->scroll_bottom_margin) {
546 dest_y = ctx->scroll_bottom_margin - 1;
547 }
548 ctx->set_cursor_pos(ctx, x, dest_y);
549 break;
550 }
551 case 'a':
552 case 'C':
553 if (x + ctx->esc_values[0] > ctx->cols - 1)
554 ctx->esc_values[0] = (ctx->cols - 1) - x;
555 ctx->set_cursor_pos(ctx, x + ctx->esc_values[0], y);
556 break;
557 case 'D':
558 if (ctx->esc_values[0] > x)
559 ctx->esc_values[0] = x;
560 ctx->set_cursor_pos(ctx, x - ctx->esc_values[0], y);
561 break;
562 case 'c':
563 if (ctx->callback != NULL) {
564 ctx->callback(ctx, TERM_CB_PRIVATE_ID, 0, 0, 0);
565 }
566 break;
567 case 'd':
568 ctx->esc_values[0] -= 1;
569 if (ctx->esc_values[0] >= ctx->rows)
570 ctx->esc_values[0] = ctx->rows - 1;
571 ctx->set_cursor_pos(ctx, x, ctx->esc_values[0]);
572 break;
573 case 'G':
574 case '`':
575 ctx->esc_values[0] -= 1;
576 if (ctx->esc_values[0] >= ctx->cols)
577 ctx->esc_values[0] = ctx->cols - 1;
578 ctx->set_cursor_pos(ctx, ctx->esc_values[0], y);
579 break;
580 case 'H':
581 case 'f':
582 if (ctx->esc_values[0] != 0) {
583 ctx->esc_values[0]--;
584 }
585 if (ctx->esc_values[1] != 0) {
586 ctx->esc_values[1]--;
587 }
588 if (ctx->esc_values[1] >= ctx->cols)
589 ctx->esc_values[1] = ctx->cols - 1;
590 if (ctx->esc_values[0] >= ctx->rows)
591 ctx->esc_values[0] = ctx->rows - 1;
592 ctx->set_cursor_pos(ctx, ctx->esc_values[1], ctx->esc_values[0]);
593 break;
594 case 'M':
595 for (size_t i = 0; i < ctx->esc_values[0]; i++) {
596 ctx->scroll(ctx);
597 }
598 break;
599 case 'L': {
600 size_t old_scroll_top_margin = ctx->scroll_top_margin;
601 ctx->scroll_top_margin = y;
602 for (size_t i = 0; i < ctx->esc_values[0]; i++) {
603 ctx->revscroll(ctx);
604 }
605 ctx->scroll_top_margin = old_scroll_top_margin;
606 break;
607 }
608 case 'n':
609 switch (ctx->esc_values[0]) {
610 case 5:
611 if (ctx->callback != NULL) {
612 ctx->callback(ctx, TERM_CB_STATUS_REPORT, 0, 0, 0);
613 }
614 break;
615 case 6:
616 if (ctx->callback != NULL) {
617 ctx->callback(ctx, TERM_CB_POS_REPORT, x + 1, y + 1, 0);
618 }
619 break;
620 }
621 break;
622 case 'q':
623 if (ctx->callback != NULL) {
624 ctx->callback(ctx, TERM_CB_KBD_LEDS, ctx->esc_values[0], 0, 0);
625 }
626 break;
627 case 'J':
628 switch (ctx->esc_values[0]) {
629 case 0: {
630 size_t rows_remaining = ctx->rows - (y + 1);
631 size_t cols_diff = ctx->cols - (x + 1);
632 size_t to_clear = rows_remaining * ctx->cols + cols_diff + 1;
633 for (size_t i = 0; i < to_clear; i++) {
634 ctx->raw_putchar(ctx, ' ');
635 }
636 ctx->set_cursor_pos(ctx, x, y);
637 break;
638 }
639 case 1: {
640 ctx->set_cursor_pos(ctx, 0, 0);
641 bool b = false;
642 for (size_t yc = 0; yc < ctx->rows; yc++) {
643 for (size_t xc = 0; xc < ctx->cols; xc++) {
644 ctx->raw_putchar(ctx, ' ');
645 if (xc == x && yc == y) {
646 ctx->set_cursor_pos(ctx, x, y);
647 b = true;
648 break;
649 }
650 }
651 if (b == true)
652 break;
653 }
654 break;
655 }
656 case 2:
657 case 3:
658 ctx->clear(ctx, false);
659 break;
660 }
661 break;
662 case '@':
663 for (size_t i = ctx->cols - 1; ; i--) {
664 ctx->move_character(ctx, i + ctx->esc_values[0], y, i, y);
665 ctx->set_cursor_pos(ctx, i, y);
666 ctx->raw_putchar(ctx, ' ');
667 if (i == x) {
668 break;
669 }
670 }
671 ctx->set_cursor_pos(ctx, x, y);
672 break;
673 case 'P':
674 for (size_t i = x + ctx->esc_values[0]; i < ctx->cols; i++)
675 ctx->move_character(ctx, i - ctx->esc_values[0], y, i, y);
676 ctx->set_cursor_pos(ctx, ctx->cols - ctx->esc_values[0], y);
677 // FALLTHRU
678 case 'X':
679 for (size_t i = 0; i < ctx->esc_values[0]; i++)
680 ctx->raw_putchar(ctx, ' ');
681 ctx->set_cursor_pos(ctx, x, y);
682 break;
683 case 'm':
684 sgr(ctx);
685 break;
686 case 's':
687 ctx->get_cursor_pos(ctx, &ctx->saved_cursor_x, &ctx->saved_cursor_y);
688 break;
689 case 'u':
690 ctx->set_cursor_pos(ctx, ctx->saved_cursor_x, ctx->saved_cursor_y);
691 break;
692 case 'K':
693 switch (ctx->esc_values[0]) {
694 case 0: {
695 for (size_t i = x; i < ctx->cols; i++)
696 ctx->raw_putchar(ctx, ' ');
697 ctx->set_cursor_pos(ctx, x, y);
698 break;
699 }
700 case 1: {
701 ctx->set_cursor_pos(ctx, 0, y);
702 for (size_t i = 0; i < x; i++)
703 ctx->raw_putchar(ctx, ' ');
704 break;
705 }
706 case 2: {
707 ctx->set_cursor_pos(ctx, 0, y);
708 for (size_t i = 0; i < ctx->cols; i++)
709 ctx->raw_putchar(ctx, ' ');
710 ctx->set_cursor_pos(ctx, x, y);
711 break;
712 }
713 }
714 break;
715 case 'r':
716 if (ctx->esc_values[0] == 0) {
717 ctx->esc_values[0] = 1;
718 }
719 if (ctx->esc_values[1] == 0) {
720 ctx->esc_values[1] = 1;
721 }
722 ctx->scroll_top_margin = 0;
723 ctx->scroll_bottom_margin = ctx->rows;
724 if (ctx->esc_values_i > 0) {
725 ctx->scroll_top_margin = ctx->esc_values[0] - 1;
726 }
727 if (ctx->esc_values_i > 1) {
728 ctx->scroll_bottom_margin = ctx->esc_values[1];
729 }
730 if (ctx->scroll_top_margin >= ctx->rows
731 || ctx->scroll_bottom_margin > ctx->rows
732 || ctx->scroll_top_margin >= (ctx->scroll_bottom_margin - 1)) {
733 ctx->scroll_top_margin = 0;
734 ctx->scroll_bottom_margin = ctx->rows;
735 }
736 ctx->set_cursor_pos(ctx, 0, 0);
737 break;
738 case 'l':
739 case 'h':
740 mode_toggle(ctx, c);
741 break;
742 case ']':
743 linux_private_parse(ctx);
744 break;
745 }
746
747 ctx->scroll_enabled = r;
748
749cleanup:
750 ctx->control_sequence = false;
751 ctx->escape = false;
752}
753
754static void restore_state(struct term_context *ctx) {
755 ctx->bold = ctx->saved_state_bold;
756 ctx->bg_bold = ctx->saved_state_bg_bold;
757 ctx->reverse_video = ctx->saved_state_reverse_video;
758 ctx->current_charset = ctx->saved_state_current_charset;
759 ctx->current_primary = ctx->saved_state_current_primary;
760 ctx->current_bg = ctx->saved_state_current_bg;
761
762 ctx->restore_state(ctx);
763}
764
765static void save_state(struct term_context *ctx) {
766 ctx->save_state(ctx);
767
768 ctx->saved_state_bold = ctx->bold;
769 ctx->saved_state_bg_bold = ctx->bg_bold;
770 ctx->saved_state_reverse_video = ctx->reverse_video;
771 ctx->saved_state_current_charset = ctx->current_charset;
772 ctx->saved_state_current_primary = ctx->current_primary;
773 ctx->saved_state_current_bg = ctx->current_bg;
774}
775
776static void escape_parse(struct term_context *ctx, uint8_t c) {
777 ctx->escape_offset++;
778
779 if (ctx->osc == true) {
780 osc_parse(ctx, c);
781 return;
782 }
783
784 if (ctx->control_sequence == true) {
785 control_sequence_parse(ctx, c);
786 return;
787 }
788
789 if (ctx->csi == true) {
790 ctx->csi = false;
791 goto is_csi;
792 }
793
794 size_t x, y;
795 ctx->get_cursor_pos(ctx, &x, &y);
796
797 switch (c) {
798 case ']':
799 ctx->osc_escape = false;
800 ctx->osc = true;
801 return;
802 case '[':
803is_csi:
804 for (size_t i = 0; i < TERM_MAX_ESC_VALUES; i++)
805 ctx->esc_values[i] = 0;
806 ctx->esc_values_i = 0;
807 ctx->rrr = false;
808 ctx->control_sequence = true;
809 return;
810 case '7':
811 save_state(ctx);
812 break;
813 case '8':
814 restore_state(ctx);
815 break;
816 case 'c':
817 term_context_reinit(ctx);
818 ctx->clear(ctx, true);
819 break;
820 case 'D':
821 if (y == ctx->scroll_bottom_margin - 1) {
822 ctx->scroll(ctx);
823 ctx->set_cursor_pos(ctx, x, y);
824 } else {
825 ctx->set_cursor_pos(ctx, x, y + 1);
826 }
827 break;
828 case 'E':
829 if (y == ctx->scroll_bottom_margin - 1) {
830 ctx->scroll(ctx);
831 ctx->set_cursor_pos(ctx, 0, y);
832 } else {
833 ctx->set_cursor_pos(ctx, 0, y + 1);
834 }
835 break;
836 case 'M':
837 // "Reverse linefeed"
838 if (y == ctx->scroll_top_margin) {
839 ctx->revscroll(ctx);
840 ctx->set_cursor_pos(ctx, 0, y);
841 } else {
842 ctx->set_cursor_pos(ctx, 0, y - 1);
843 }
844 break;
845 case 'Z':
846 if (ctx->callback != NULL) {
847 ctx->callback(ctx, TERM_CB_PRIVATE_ID, 0, 0, 0);
848 }
849 break;
850 case '(':
851 case ')':
852 ctx->g_select = c - '\'';
853 break;
854 case 0x1b:
855 if (ctx->in_bootloader == true) {
856 ctx->raw_putchar(ctx, c);
857 }
858 break;
859 }
860
861 ctx->escape = false;
862}
863
864static uint8_t dec_special_to_cp437(uint8_t c) {
865 switch (c) {
866 case '`': return 0x04;
867 case '0': return 0xdb;
868 case '-': return 0x18;
869 case ',': return 0x1b;
870 case '.': return 0x19;
871 case 'a': return 0xb1;
872 case 'f': return 0xf8;
873 case 'g': return 0xf1;
874 case 'h': return 0xb0;
875 case 'j': return 0xd9;
876 case 'k': return 0xbf;
877 case 'l': return 0xda;
878 case 'm': return 0xc0;
879 case 'n': return 0xc5;
880 case 'q': return 0xc4;
881 case 's': return 0x5f;
882 case 't': return 0xc3;
883 case 'u': return 0xb4;
884 case 'v': return 0xc1;
885 case 'w': return 0xc2;
886 case 'x': return 0xb3;
887 case 'y': return 0xf3;
888 case 'z': return 0xf2;
889 case '~': return 0xfa;
890 case '_': return 0xff;
891 case '+': return 0x1a;
892 case '{': return 0xe3;
893 case '}': return 0x9c;
894 }
895
896 return c;
897}
898
899// Following wcwidth related code inherited from:
900// https://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c
901
902struct interval {
903 wchar_t first;
904 wchar_t last;
905};
906
907/* auxiliary function for binary search in interval table */
908static int bisearch(wchar_t ucs, const struct interval *table, int max) {
909 int min = 0;
910 int mid;
911
912 if (ucs < table[0].first || ucs > table[max].last)
913 return 0;
914 while (max >= min) {
915 mid = (min + max) / 2;
916 if (ucs > table[mid].last)
917 min = mid + 1;
918 else if (ucs < table[mid].first)
919 max = mid - 1;
920 else
921 return 1;
922 }
923
924 return 0;
925}
926
927int mk_wcwidth(wchar_t ucs) {
928 /* sorted list of non-overlapping intervals of non-spacing characters */
929 /* generated by "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c" */
930 static const struct interval combining[] = {
931 { 0x0300, 0x036F }, { 0x0483, 0x0486 }, { 0x0488, 0x0489 },
932 { 0x0591, 0x05BD }, { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 },
933 { 0x05C4, 0x05C5 }, { 0x05C7, 0x05C7 }, { 0x0600, 0x0603 },
934 { 0x0610, 0x0615 }, { 0x064B, 0x065E }, { 0x0670, 0x0670 },
935 { 0x06D6, 0x06E4 }, { 0x06E7, 0x06E8 }, { 0x06EA, 0x06ED },
936 { 0x070F, 0x070F }, { 0x0711, 0x0711 }, { 0x0730, 0x074A },
937 { 0x07A6, 0x07B0 }, { 0x07EB, 0x07F3 }, { 0x0901, 0x0902 },
938 { 0x093C, 0x093C }, { 0x0941, 0x0948 }, { 0x094D, 0x094D },
939 { 0x0951, 0x0954 }, { 0x0962, 0x0963 }, { 0x0981, 0x0981 },
940 { 0x09BC, 0x09BC }, { 0x09C1, 0x09C4 }, { 0x09CD, 0x09CD },
941 { 0x09E2, 0x09E3 }, { 0x0A01, 0x0A02 }, { 0x0A3C, 0x0A3C },
942 { 0x0A41, 0x0A42 }, { 0x0A47, 0x0A48 }, { 0x0A4B, 0x0A4D },
943 { 0x0A70, 0x0A71 }, { 0x0A81, 0x0A82 }, { 0x0ABC, 0x0ABC },
944 { 0x0AC1, 0x0AC5 }, { 0x0AC7, 0x0AC8 }, { 0x0ACD, 0x0ACD },
945 { 0x0AE2, 0x0AE3 }, { 0x0B01, 0x0B01 }, { 0x0B3C, 0x0B3C },
946 { 0x0B3F, 0x0B3F }, { 0x0B41, 0x0B43 }, { 0x0B4D, 0x0B4D },
947 { 0x0B56, 0x0B56 }, { 0x0B82, 0x0B82 }, { 0x0BC0, 0x0BC0 },
948 { 0x0BCD, 0x0BCD }, { 0x0C3E, 0x0C40 }, { 0x0C46, 0x0C48 },
949 { 0x0C4A, 0x0C4D }, { 0x0C55, 0x0C56 }, { 0x0CBC, 0x0CBC },
950 { 0x0CBF, 0x0CBF }, { 0x0CC6, 0x0CC6 }, { 0x0CCC, 0x0CCD },
951 { 0x0CE2, 0x0CE3 }, { 0x0D41, 0x0D43 }, { 0x0D4D, 0x0D4D },
952 { 0x0DCA, 0x0DCA }, { 0x0DD2, 0x0DD4 }, { 0x0DD6, 0x0DD6 },
953 { 0x0E31, 0x0E31 }, { 0x0E34, 0x0E3A }, { 0x0E47, 0x0E4E },
954 { 0x0EB1, 0x0EB1 }, { 0x0EB4, 0x0EB9 }, { 0x0EBB, 0x0EBC },
955 { 0x0EC8, 0x0ECD }, { 0x0F18, 0x0F19 }, { 0x0F35, 0x0F35 },
956 { 0x0F37, 0x0F37 }, { 0x0F39, 0x0F39 }, { 0x0F71, 0x0F7E },
957 { 0x0F80, 0x0F84 }, { 0x0F86, 0x0F87 }, { 0x0F90, 0x0F97 },
958 { 0x0F99, 0x0FBC }, { 0x0FC6, 0x0FC6 }, { 0x102D, 0x1030 },
959 { 0x1032, 0x1032 }, { 0x1036, 0x1037 }, { 0x1039, 0x1039 },
960 { 0x1058, 0x1059 }, { 0x1160, 0x11FF }, { 0x135F, 0x135F },
961 { 0x1712, 0x1714 }, { 0x1732, 0x1734 }, { 0x1752, 0x1753 },
962 { 0x1772, 0x1773 }, { 0x17B4, 0x17B5 }, { 0x17B7, 0x17BD },
963 { 0x17C6, 0x17C6 }, { 0x17C9, 0x17D3 }, { 0x17DD, 0x17DD },
964 { 0x180B, 0x180D }, { 0x18A9, 0x18A9 }, { 0x1920, 0x1922 },
965 { 0x1927, 0x1928 }, { 0x1932, 0x1932 }, { 0x1939, 0x193B },
966 { 0x1A17, 0x1A18 }, { 0x1B00, 0x1B03 }, { 0x1B34, 0x1B34 },
967 { 0x1B36, 0x1B3A }, { 0x1B3C, 0x1B3C }, { 0x1B42, 0x1B42 },
968 { 0x1B6B, 0x1B73 }, { 0x1DC0, 0x1DCA }, { 0x1DFE, 0x1DFF },
969 { 0x200B, 0x200F }, { 0x202A, 0x202E }, { 0x2060, 0x2063 },
970 { 0x206A, 0x206F }, { 0x20D0, 0x20EF }, { 0x302A, 0x302F },
971 { 0x3099, 0x309A }, { 0xA806, 0xA806 }, { 0xA80B, 0xA80B },
972 { 0xA825, 0xA826 }, { 0xFB1E, 0xFB1E }, { 0xFE00, 0xFE0F },
973 { 0xFE20, 0xFE23 }, { 0xFEFF, 0xFEFF }, { 0xFFF9, 0xFFFB },
974 { 0x10A01, 0x10A03 }, { 0x10A05, 0x10A06 }, { 0x10A0C, 0x10A0F },
975 { 0x10A38, 0x10A3A }, { 0x10A3F, 0x10A3F }, { 0x1D167, 0x1D169 },
976 { 0x1D173, 0x1D182 }, { 0x1D185, 0x1D18B }, { 0x1D1AA, 0x1D1AD },
977 { 0x1D242, 0x1D244 }, { 0xE0001, 0xE0001 }, { 0xE0020, 0xE007F },
978 { 0xE0100, 0xE01EF }
979 };
980
981 /* test for 8-bit control characters */
982 if (ucs == 0)
983 return 0;
984 if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0))
985 return 1;
986
987 /* binary search in table of non-spacing characters */
988 if (bisearch(ucs, combining,
989 sizeof(combining) / sizeof(struct interval) - 1))
990 return 0;
991
992 /* if we arrive here, ucs is not a combining or C0/C1 control character */
993
994 return 1 +
995 (ucs >= 0x1100 &&
996 (ucs <= 0x115f || /* Hangul Jamo init. consonants */
997 ucs == 0x2329 || ucs == 0x232a ||
998 (ucs >= 0x2e80 && ucs <= 0xa4cf &&
999 ucs != 0x303f) || /* CJK ... Yi */
1000 (ucs >= 0xac00 && ucs <= 0xd7a3) || /* Hangul Syllables */
1001 (ucs >= 0xf900 && ucs <= 0xfaff) || /* CJK Compatibility Ideographs */
1002 (ucs >= 0xfe10 && ucs <= 0xfe19) || /* Vertical forms */
1003 (ucs >= 0xfe30 && ucs <= 0xfe6f) || /* CJK Compatibility Forms */
1004 (ucs >= 0xff00 && ucs <= 0xff60) || /* Fullwidth Forms */
1005 (ucs >= 0xffe0 && ucs <= 0xffe6) ||
1006 (ucs >= 0x20000 && ucs <= 0x2fffd) ||
1007 (ucs >= 0x30000 && ucs <= 0x3fffd)));
1008}
1009
1010// End of https://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c inherited code
1011
1012static int unicode_to_cp437(uint64_t code_point) {
1013 switch (code_point) {
1014 case 0x263a: return 1;
1015 case 0x263b: return 2;
1016 case 0x2665: return 3;
1017 case 0x2666: return 4;
1018 case 0x2663: return 5;
1019 case 0x2660: return 6;
1020 case 0x2022: return 7;
1021 case 0x25d8: return 8;
1022 case 0x25cb: return 9;
1023 case 0x25d9: return 10;
1024 case 0x2642: return 11;
1025 case 0x2640: return 12;
1026 case 0x266a: return 13;
1027 case 0x266b: return 14;
1028 case 0x263c: return 15;
1029 case 0x25ba: return 16;
1030 case 0x25c4: return 17;
1031 case 0x2195: return 18;
1032 case 0x203c: return 19;
1033 case 0x00b6: return 20;
1034 case 0x00a7: return 21;
1035 case 0x25ac: return 22;
1036 case 0x21a8: return 23;
1037 case 0x2191: return 24;
1038 case 0x2193: return 25;
1039 case 0x2192: return 26;
1040 case 0x2190: return 27;
1041 case 0x221f: return 28;
1042 case 0x2194: return 29;
1043 case 0x25b2: return 30;
1044 case 0x25bc: return 31;
1045
1046 case 0x2302: return 127;
1047 case 0x00c7: return 128;
1048 case 0x00fc: return 129;
1049 case 0x00e9: return 130;
1050 case 0x00e2: return 131;
1051 case 0x00e4: return 132;
1052 case 0x00e0: return 133;
1053 case 0x00e5: return 134;
1054 case 0x00e7: return 135;
1055 case 0x00ea: return 136;
1056 case 0x00eb: return 137;
1057 case 0x00e8: return 138;
1058 case 0x00ef: return 139;
1059 case 0x00ee: return 140;
1060 case 0x00ec: return 141;
1061 case 0x00c4: return 142;
1062 case 0x00c5: return 143;
1063 case 0x00c9: return 144;
1064 case 0x00e6: return 145;
1065 case 0x00c6: return 146;
1066 case 0x00f4: return 147;
1067 case 0x00f6: return 148;
1068 case 0x00f2: return 149;
1069 case 0x00fb: return 150;
1070 case 0x00f9: return 151;
1071 case 0x00ff: return 152;
1072 case 0x00d6: return 153;
1073 case 0x00dc: return 154;
1074 case 0x00a2: return 155;
1075 case 0x00a3: return 156;
1076 case 0x00a5: return 157;
1077 case 0x20a7: return 158;
1078 case 0x0192: return 159;
1079 case 0x00e1: return 160;
1080 case 0x00ed: return 161;
1081 case 0x00f3: return 162;
1082 case 0x00fa: return 163;
1083 case 0x00f1: return 164;
1084 case 0x00d1: return 165;
1085 case 0x00aa: return 166;
1086 case 0x00ba: return 167;
1087 case 0x00bf: return 168;
1088 case 0x2310: return 169;
1089 case 0x00ac: return 170;
1090 case 0x00bd: return 171;
1091 case 0x00bc: return 172;
1092 case 0x00a1: return 173;
1093 case 0x00ab: return 174;
1094 case 0x00bb: return 175;
1095 case 0x2591: return 176;
1096 case 0x2592: return 177;
1097 case 0x2593: return 178;
1098 case 0x2502: return 179;
1099 case 0x2524: return 180;
1100 case 0x2561: return 181;
1101 case 0x2562: return 182;
1102 case 0x2556: return 183;
1103 case 0x2555: return 184;
1104 case 0x2563: return 185;
1105 case 0x2551: return 186;
1106 case 0x2557: return 187;
1107 case 0x255d: return 188;
1108 case 0x255c: return 189;
1109 case 0x255b: return 190;
1110 case 0x2510: return 191;
1111 case 0x2514: return 192;
1112 case 0x2534: return 193;
1113 case 0x252c: return 194;
1114 case 0x251c: return 195;
1115 case 0x2500: return 196;
1116 case 0x253c: return 197;
1117 case 0x255e: return 198;
1118 case 0x255f: return 199;
1119 case 0x255a: return 200;
1120 case 0x2554: return 201;
1121 case 0x2569: return 202;
1122 case 0x2566: return 203;
1123 case 0x2560: return 204;
1124 case 0x2550: return 205;
1125 case 0x256c: return 206;
1126 case 0x2567: return 207;
1127 case 0x2568: return 208;
1128 case 0x2564: return 209;
1129 case 0x2565: return 210;
1130 case 0x2559: return 211;
1131 case 0x2558: return 212;
1132 case 0x2552: return 213;
1133 case 0x2553: return 214;
1134 case 0x256b: return 215;
1135 case 0x256a: return 216;
1136 case 0x2518: return 217;
1137 case 0x250c: return 218;
1138 case 0x2588: return 219;
1139 case 0x2584: return 220;
1140 case 0x258c: return 221;
1141 case 0x2590: return 222;
1142 case 0x2580: return 223;
1143 case 0x03b1: return 224;
1144 case 0x00df: return 225;
1145 case 0x0393: return 226;
1146 case 0x03c0: return 227;
1147 case 0x03a3: return 228;
1148 case 0x03c3: return 229;
1149 case 0x00b5: return 230;
1150 case 0x03c4: return 231;
1151 case 0x03a6: return 232;
1152 case 0x0398: return 233;
1153 case 0x03a9: return 234;
1154 case 0x03b4: return 235;
1155 case 0x221e: return 236;
1156 case 0x03c6: return 237;
1157 case 0x03b5: return 238;
1158 case 0x2229: return 239;
1159 case 0x2261: return 240;
1160 case 0x00b1: return 241;
1161 case 0x2265: return 242;
1162 case 0x2264: return 243;
1163 case 0x2320: return 244;
1164 case 0x2321: return 245;
1165 case 0x00f7: return 246;
1166 case 0x2248: return 247;
1167 case 0x00b0: return 248;
1168 case 0x2219: return 249;
1169 case 0x00b7: return 250;
1170 case 0x221a: return 251;
1171 case 0x207f: return 252;
1172 case 0x00b2: return 253;
1173 case 0x25a0: return 254;
1174 }
1175
1176 return -1;
1177}
1178
1179static void term_putchar(struct term_context *ctx, uint8_t c) {
1180 if (ctx->discard_next || (ctx->in_bootloader == false && (c == 0x18 || c == 0x1a))) {
1181 ctx->discard_next = false;
1182 ctx->escape = false;
1183 ctx->csi = false;
1184 ctx->control_sequence = false;
1185 ctx->unicode_remaining = 0;
1186 ctx->osc = false;
1187 ctx->osc_escape = false;
1188 ctx->g_select = 0;
1189 return;
1190 }
1191
1192 if (ctx->unicode_remaining != 0) {
1193 if ((c & 0xc0) != 0x80) {
1194 ctx->unicode_remaining = 0;
1195 goto unicode_error;
1196 }
1197
1198 ctx->unicode_remaining--;
1199 ctx->code_point |= (c & 0x3f) << (6 * ctx->unicode_remaining);
1200 if (ctx->unicode_remaining != 0) {
1201 return;
1202 }
1203
1204 int cc = unicode_to_cp437(ctx->code_point);
1205
1206 if (cc == -1) {
1207 size_t replacement_width = mk_wcwidth(ctx->code_point);
1208 if (replacement_width > 0) {
1209 ctx->raw_putchar(ctx, 0xfe);
1210 }
1211 for (size_t i = 1; i < replacement_width; i++) {
1212 ctx->raw_putchar(ctx, ' ');
1213 }
1214 } else {
1215 ctx->raw_putchar(ctx, cc);
1216 }
1217 return;
1218 }
1219
1220unicode_error:
1221 if (c >= 0xc0 && c <= 0xf7 && ctx->in_bootloader == false) {
1222 if (c >= 0xc0 && c <= 0xdf) {
1223 ctx->unicode_remaining = 1;
1224 ctx->code_point = (c & 0x1f) << 6;
1225 } else if (c >= 0xe0 && c <= 0xef) {
1226 ctx->unicode_remaining = 2;
1227 ctx->code_point = (c & 0x0f) << (6 * 2);
1228 } else if (c >= 0xf0 && c <= 0xf7) {
1229 ctx->unicode_remaining = 3;
1230 ctx->code_point = (c & 0x07) << (6 * 3);
1231 }
1232 return;
1233 }
1234
1235 if (ctx->escape == true) {
1236 escape_parse(ctx, c);
1237 return;
1238 }
1239
1240 if (ctx->g_select) {
1241 ctx->g_select--;
1242 switch (c) {
1243 case 'B':
1244 ctx->charsets[ctx->g_select] = CHARSET_DEFAULT; break;
1245 case '0':
1246 ctx->charsets[ctx->g_select] = CHARSET_DEC_SPECIAL; break;
1247 }
1248 ctx->g_select = 0;
1249 return;
1250 }
1251
1252 size_t x, y;
1253 ctx->get_cursor_pos(ctx, &x, &y);
1254
1255 switch (c) {
1256 case 0x00:
1257 case 0x7f:
1258 return;
1259 case 0x9b:
1260 ctx->csi = true;
1261 // FALLTHRU
1262 case 0x1b:
1263 ctx->escape_offset = 0;
1264 ctx->escape = true;
1265 return;
1266 case '\t':
1267 if ((x / ctx->tab_size + 1) >= ctx->cols) {
1268 ctx->set_cursor_pos(ctx, ctx->cols - 1, y);
1269 return;
1270 }
1271 ctx->set_cursor_pos(ctx, (x / ctx->tab_size + 1) * ctx->tab_size, y);
1272 return;
1273 case 0x0b:
1274 case 0x0c:
1275 case '\n':
1276 if (y == ctx->scroll_bottom_margin - 1) {
1277 ctx->scroll(ctx);
1278 ctx->set_cursor_pos(ctx, (ctx->oob_output & TERM_OOB_OUTPUT_ONLCR) ? 0 : x, y);
1279 } else {
1280 ctx->set_cursor_pos(ctx, (ctx->oob_output & TERM_OOB_OUTPUT_ONLCR) ? 0 : x, y + 1);
1281 }
1282 return;
1283 case '\b':
1284 ctx->set_cursor_pos(ctx, x - 1, y);
1285 return;
1286 case '\r':
1287 ctx->set_cursor_pos(ctx, 0, y);
1288 return;
1289 case '\a':
1290 // The bell is handled by the kernel
1291 if (ctx->callback != NULL) {
1292 ctx->callback(ctx, TERM_CB_BELL, 0, 0, 0);
1293 }
1294 return;
1295 case 14:
1296 // Move to G1 set
1297 ctx->current_charset = 1;
1298 return;
1299 case 15:
1300 // Move to G0 set
1301 ctx->current_charset = 0;
1302 return;
1303 }
1304
1305 if (ctx->insert_mode == true) {
1306 for (size_t i = ctx->cols - 1; ; i--) {
1307 ctx->move_character(ctx, i + 1, y, i, y);
1308 if (i == x) {
1309 break;
1310 }
1311 }
1312 }
1313
1314 // Translate character set
1315 switch (ctx->charsets[ctx->current_charset]) {
1316 case CHARSET_DEFAULT:
1317 break;
1318 case CHARSET_DEC_SPECIAL:
1319 c = dec_special_to_cp437(c);
1320 break;
1321 }
1322
1323 ctx->raw_putchar(ctx, c);
1324}