Commit: e552852145827c5f8bedd5b4d0405259f7cc93d7
Parent: e87c83d5e54f4a762d738a2485a674b10ae26840
Author: Randy Palamar
Date: Tue, 27 Aug 2024 21:20:19 -0600
allow control characters to be embedded in csi sequences
Diffstat:
M | build.sh | | | 4 | ++-- |
M | terminal.c | | | 212 | +++++++++++++++++++++++++++++++++++++++++-------------------------------------- |
M | test.c | | | 39 | +++++++++++++++++++++++++++++++++++++-- |
M | util.h | | | 25 | +++++++++++++++++++++++++ |
4 files changed, 174 insertions(+), 106 deletions(-)
diff --git a/build.sh b/build.sh
@@ -16,10 +16,10 @@ testcflags="$cflags -O0 -ggdb -D_DEBUG -Wno-unused-function -Wno-undefined-inter
if [ $debug ]; then
# Hot Reloading/Debugging
- cflags="$cflags -ggdb -O0 -D_DEBUG -Wno-unused-function -Wno-undefined-internal"
+ cflags="$cflags -D_DEBUG -Wno-unused-function -Wno-undefined-internal"
#cflags="$cflags -fsanitize=address,undefined"
- libcflags="$cflags -fPIC -Wno-unused-function"
+ libcflags="$cflags -ggdb -O0 -fPIC"
libldflags="$ldflags -shared"
${cc} $libcflags vtgl.c -o vtgl.so $libldflags
diff --git a/terminal.c b/terminal.c
@@ -1,21 +1,5 @@
#include <immintrin.h>
-#define ESC_ARG_SIZ 6
-
-typedef struct {
- s8 raw;
- i32 argv[ESC_ARG_SIZ];
- i32 argc;
- u8 mode;
- u8 priv;
-} CSI;
-
-typedef struct {
- s8 raw;
- s8 arg;
- i32 cmd;
-} OSC;
-
static const u8 utf8overhangmask[32] = {
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
@@ -665,31 +649,25 @@ push_tab(Term *t, i32 n)
static i32
parse_csi(s8 *r, CSI *csi)
{
- *csi = (CSI){0};
- csi->raw = (s8){.data = r->data};
-
if (peek(*r, 0) == '?') {
csi->priv = 1;
- csi->raw.len++;
get_ascii(r);
}
while (r->len) {
u32 cp = get_ascii(r);
- csi->raw.len++;
- if (BETWEEN(cp, '0', '9')) {
+ if (ISCONTROL(cp)) {
+ continue;
+ } else if (BETWEEN(cp, '0', '9')) {
csi->argv[csi->argc] *= 10;
csi->argv[csi->argc] += cp - '0';
continue;
}
csi->argc++;
+
if (cp != ';' || csi->argc == ESC_ARG_SIZ) {
- if (cp == ';') {
- csi->mode = get_ascii(r);
- csi->raw.len++;
- } else {
- csi->mode = cp;
- }
+ if (cp == ';') csi->mode = get_ascii(r);
+ else csi->mode = cp;
return 0;
}
}
@@ -698,10 +676,10 @@ parse_csi(s8 *r, CSI *csi)
}
static void
-handle_csi(Term *t, s8 *raw)
+handle_csi(Term *t, CSI *csi)
{
- CSI csi;
- i32 ret = parse_csi(raw, &csi);
+ s8 raw = csi->raw;
+ i32 ret = parse_csi(&raw, csi);
ASSERT(ret != -1);
#define ORONE(x) (x)? (x) : 1
@@ -709,37 +687,38 @@ handle_csi(Term *t, s8 *raw)
iv2 p = t->cursor.pos;
u8 next;
- switch (csi.mode) {
- case 'A': cursor_step_raw(t, ORONE(csi.argv[0]), -1, 0); break;
- case 'B': cursor_step_raw(t, ORONE(csi.argv[0]), 1, 0); break;
- case 'C': cursor_step_raw(t, ORONE(csi.argv[0]), 0, 1); break;
- case 'D': cursor_step_raw(t, ORONE(csi.argv[0]), 0, -1); break;
- case 'E': cursor_move_to(t, p.y + ORONE(csi.argv[0]), 0); break;
- case 'F': cursor_move_to(t, p.y - ORONE(csi.argv[0]), 0); break;
- case 'G': cursor_move_to(t, p.y, csi.argv[0] - 1); break;
- case 'H': cursor_move_to(t, csi.argv[0] - 1, csi.argv[1] - 1); break;
- case 'J': erase_in_display(t, &csi); break;
- case 'K': erase_in_line(t, &csi); break;
- case 'L': insert_blank_lines(t, ORONE(csi.argv[0])); break;
- case 'M': erase_lines(t, ORONE(csi.argv[0])); break;
- case 'P': erase_characters(t, ORONE(csi.argv[0])); break;
- case 'X': erase_characters(t, ORONE(csi.argv[0])); break;
- case 'Z': push_tab(t, -(ORONE(csi.argv[0]))); break;
- case 'a': cursor_step_raw(t, ORONE(csi.argv[0]), 0, 1); break;
- case 'd': cursor_move_to(t, csi.argv[0] - 1, p.x); break;
- case 'e': cursor_step_raw(t, ORONE(csi.argv[0]), 1, 0); break;
- case 'f': cursor_move_to(t, csi.argv[0] - 1, csi.argv[1] - 1); break;
- case 'h': set_mode(t, &csi, 1); break;
- case 'l': set_mode(t, &csi, 0); break;
- case 'm': set_colours(t, &csi); break;
+ switch (csi->mode) {
+ case 'A': cursor_step_raw(t, ORONE(csi->argv[0]), -1, 0); break;
+ case 'B': cursor_step_raw(t, ORONE(csi->argv[0]), 1, 0); break;
+ case 'C': cursor_step_raw(t, ORONE(csi->argv[0]), 0, 1); break;
+ case 'D': cursor_step_raw(t, ORONE(csi->argv[0]), 0, -1); break;
+ case 'E': cursor_move_to(t, p.y + ORONE(csi->argv[0]), 0); break;
+ case 'F': cursor_move_to(t, p.y - ORONE(csi->argv[0]), 0); break;
+ case 'G': cursor_move_to(t, p.y, csi->argv[0] - 1); break;
+ case 'H': cursor_move_to(t, csi->argv[0] - 1, csi->argv[1] - 1); break;
+ case 'J': erase_in_display(t, csi); break;
+ case 'K': erase_in_line(t, csi); break;
+ case 'L': insert_blank_lines(t, ORONE(csi->argv[0])); break;
+ case 'M': erase_lines(t, ORONE(csi->argv[0])); break;
+ case 'P': erase_characters(t, ORONE(csi->argv[0])); break;
+ case 'X': erase_characters(t, ORONE(csi->argv[0])); break;
+ case 'T': insert_blank_lines(t, ORONE(csi->argv[0])); break;
+ case 'Z': push_tab(t, -(ORONE(csi->argv[0]))); break;
+ case 'a': cursor_step_raw(t, ORONE(csi->argv[0]), 0, 1); break;
+ case 'd': cursor_move_to(t, csi->argv[0] - 1, p.x); break;
+ case 'e': cursor_step_raw(t, ORONE(csi->argv[0]), 1, 0); break;
+ case 'f': cursor_move_to(t, csi->argv[0] - 1, csi->argv[1] - 1); break;
+ case 'h': set_mode(t, csi, 1); break;
+ case 'l': set_mode(t, csi, 0); break;
+ case 'm': set_colours(t, csi); break;
case 'r':
- if (csi.priv)
+ if (csi->priv)
goto unknown;
- set_scrolling_region(t, &csi);
+ set_scrolling_region(t, csi);
break;
- case 't': window_manipulation(t, &csi); break;
+ case 't': window_manipulation(t, csi); break;
case '!':
- next = get_ascii(raw);
+ next = get_ascii(&raw);
if (next == 'p') {
/* NOTE: DECSTR: soft terminal reset IGNORED */
break;
@@ -748,7 +727,7 @@ handle_csi(Term *t, s8 *raw)
default:
unknown:
fputs("unknown csi: ", stderr);
- dump_csi(&csi);
+ dump_csi(csi);
}
}
@@ -796,6 +775,13 @@ parse_osc(s8 *raw, OSC *osc)
}
static void
+reset_csi(CSI *csi, s8 *raw)
+{
+ *csi = (CSI){0};
+ csi->raw.data = raw->data;
+}
+
+static void
dump_osc(OSC *osc)
{
fputs("ESC]", stderr);
@@ -837,8 +823,8 @@ static void
handle_escape(Term *t, s8 *raw, Arena a)
{
u32 cp = get_ascii(raw);
- switch(cp) {
- case '[': handle_csi(t, raw); break;
+ switch (cp) {
+ case '[': reset_csi(&t->csi, raw); t->escape |= EM_CSI; break;
case ']': handle_osc(t, raw, a); break;
case '(': /* GZD4 -- set primary charset G0 */
case ')': /* G1D4 -- set secondary charset G1 */
@@ -873,6 +859,24 @@ handle_escape(Term *t, s8 *raw, Arena a)
}
}
+static void
+push_control(Term *t, s8 *line, u32 cp, Arena a)
+{
+ switch (cp) {
+ case 0x1B: handle_escape(t, line, a); break;
+ case '\r': t->cursor.pos.x = 0; break;
+ case '\n': push_newline(t, 0); break;
+ case '\t': push_tab(t, 1); break;
+ case '\a': /* TODO: ding ding? */ break;
+ case '\b':
+ cursor_move_to(t, t->cursor.pos.y, t->cursor.pos.x - 1);
+ break;
+ }
+ if (cp != 0x1B && t->escape & EM_CSI) {
+ t->csi.raw.len++;
+ }
+}
+
enum escape_moves_cursor_result {
EMC_NORMAL_RETURN,
EMC_NEEDS_MORE_BYTES,
@@ -894,7 +898,7 @@ static enum escape_moves_cursor_result
check_if_csi_moves_cursor(Term *t, s8 *raw)
{
enum escape_moves_cursor_result result = EMC_NORMAL_RETURN;
- CSI csi;
+ CSI csi = {0};
if (parse_csi(raw, &csi) == -1)
return EMC_NEEDS_MORE_BYTES;
@@ -1073,53 +1077,55 @@ push_line(Term *t, Line *line, Arena a)
/* TODO: handle error case */
ASSERT(cp != (u32)-1);
+ if (ISCONTROL(cp)) {
+ push_control(t, &l, cp, a);
+ continue;
+ } else if (t->escape & EM_CSI) {
+ t->csi.raw.len++;
+ if (BETWEEN(cp, '@', '~')) {
+ handle_csi(t, &t->csi);
+ t->escape &= ~EM_CSI;
+ }
+ continue;
+ }
+
+ if (t->mode & TM_AUTO_WRAP && t->cursor.state & CURSOR_WRAP_NEXT)
+ push_newline(t, 1);
+
i32 width;
- switch (cp) {
- case 0x1B: handle_escape(t, &l, a); break;
- case '\r': t->cursor.pos.x = 0; break;
- case '\n': push_newline(t, 0); break;
- case '\t': push_tab(t, 1); break;
- case '\a': /* TODO: ding ding? */ break;
- case '\b':
- cursor_move_to(t, t->cursor.pos.y, t->cursor.pos.x - 1);
- break;
- default:
- if (t->mode & TM_AUTO_WRAP && t->cursor.state & CURSOR_WRAP_NEXT)
+ if (line->has_unicode) {
+ width = wcwidth(cp);
+ ASSERT(width != -1);
+ } else {
+ width = 1;
+ }
+
+ if (t->cursor.pos.x + width > t->size.w) {
+ /* NOTE: make space for character if mode enabled else
+ * clobber whatever was on the end of the line */
+ if (t->mode & TM_AUTO_WRAP)
push_newline(t, 1);
+ else
+ cursor_move_to(t, t->cursor.pos.y, t->size.w - width);
+ }
- if (line->has_unicode) {
- width = wcwidth(cp);
- ASSERT(width != -1);
- } else {
- width = 1;
- }
+ c = &tv->fb.rows[t->cursor.pos.y][t->cursor.pos.x];
+ c->cp = cp;
+ c->style = t->cursor.style;
- if (t->cursor.pos.x + width > t->size.w) {
- /* NOTE: make space for character if mode enabled else
- * clobber whatever was on the end of the line */
- if (t->mode & TM_AUTO_WRAP)
- push_newline(t, 1);
- else
- cursor_move_to(t, t->cursor.pos.y, t->size.w - width);
+ if (width == 2) {
+ c->style.attr |= ATTR_WIDE;
+ if (t->cursor.pos.x + 1 < t->size.w) {
+ Cell *nc = c + 1;
+ nc->style.attr |= ATTR_WDUMMY;
}
+ }
- c = &tv->fb.rows[t->cursor.pos.y][t->cursor.pos.x];
- c->cp = cp;
- c->style = t->cursor.style;
-
- if (width == 2) {
- c->style.attr |= ATTR_WIDE;
- if (t->cursor.pos.x + 1 < t->size.w) {
- Cell *nc = c + 1;
- nc->style.attr |= ATTR_WDUMMY;
- }
- }
+ if (t->cursor.pos.x + width < t->size.w)
+ cursor_step_column(t, width);
+ else
+ t->cursor.state |= CURSOR_WRAP_NEXT;
- if (t->cursor.pos.x + width < t->size.w)
- cursor_step_column(t, width);
- else
- t->cursor.state |= CURSOR_WRAP_NEXT;
- }
if (is_selected(&t->selection, t->cursor.pos.x, t->cursor.pos.y))
selection_clear(&t->selection);
}
@@ -1154,6 +1160,8 @@ blit_lines(Term *t, Arena a, size line_count)
tv->last_cursor_pos = t->cursor.pos;
tv->last_line_idx = line_idx;
push_line(t, tv->lines.buf + line_idx, a);
+ /* TODO: can we avoid this? */
+ ASSERT(t->escape == 0);
}
t->gl.flags &= ~(NEEDS_FULL_REFILL|NEEDS_REFILL);
diff --git a/test.c b/test.c
@@ -17,6 +17,7 @@ struct test_result { b32 status; const char *info; };
typedef TEST_FN(Test_Fn);
#define TESTS \
+ X(csi_embedded_control) \
X(colour_setting) \
X(cursor_at_line_boundary) \
X(cursor_movement) \
@@ -87,6 +88,41 @@ launder_static_string(Term *term, s8 static_str)
return raw;
}
+static TEST_FN(csi_embedded_control)
+{
+ struct test_result result = {.info = __FUNCTION__};
+
+ /* NOTE: print a '1' with default style then start changing the colour,
+ * but backspace within the escape sequence so the cursor is now over the
+ * '1', but then put a newline inside the sequence so that cursor is on
+ * the next line, finally print a ' 2' with the completed colour sequence. */
+ launder_static_string(term, s8("1"));
+ launder_static_string(term, CSI(48;2;7\b5;63;4\n2m));
+ s8 raw = launder_static_string(term, s8(" 2"));
+ size parsed_lines = split_raw_input_to_lines(term, raw);
+ blit_lines(term, arena, parsed_lines);
+
+ #if 0
+ dump_csi(&term->csi);
+ #endif
+
+ Cell c1 = { .cp = '1', .style = {
+ .bg = g_colours.data[g_colours.bgidx],
+ .fg = g_colours.data[g_colours.fgidx],
+ .attr = (ATTR_NULL),
+ }};
+ Cell c2 = { .cp = '2', .style = {
+ .bg = (Colour){.r = 75, .g = 63, .b = 42, .a = 0xFF},
+ .fg = g_colours.data[3],
+ .attr = (ATTR_INVISIBLE|ATTR_STRUCK),
+ }};
+ result.status = term->cursor.pos.y == 1 && term->cursor.pos.x == 3;
+ result.status |= check_cells_equal(&c1, &term->views[term->view_idx].fb.rows[0][0]);
+ result.status |= check_cells_equal(&c2, &term->views[term->view_idx].fb.rows[1][1]);
+
+ return result;
+}
+
static TEST_FN(colour_setting)
{
struct test_result result = {.info = __FUNCTION__};
@@ -239,8 +275,7 @@ main(void)
for (u32 i = 0; i < ARRAY_COUNT(tests); i++) {
buffer_reset(&term);
- cursor_reset(&term);
- cursor_move_to(&term, 0, 0);
+ term_reset(&term);
struct test_result result = tests[i](&term, memory);
if (result.status == 0) {
failure_count++;
diff --git a/util.h b/util.h
@@ -31,6 +31,8 @@
#define MIN(a, b) ((a) <= (b) ? (a) : (b))
#define SGN(a) ((a) < 0 ? (-1) : (1))
+#define ISCONTROLC0(c) (BETWEEN((c), 0, 0x1F) || (c) == 0x7F)
+#define ISCONTROL(c) (ISCONTROLC0(c))
#define ISSPACE(c) ((c) == ' ' || (c) == '\n' || (c) == '\t')
#define ISPRINT(c) BETWEEN((c), ' ', '~')
@@ -336,6 +338,26 @@ typedef struct {
enum selection_states state;
} Selection;
+#define ESC_ARG_SIZ 6
+typedef struct {
+ s8 raw;
+ i32 argv[ESC_ARG_SIZ];
+ i32 argc;
+ u8 mode;
+ u8 priv;
+} CSI;
+
+typedef struct {
+ s8 raw;
+ s8 arg;
+ i32 cmd;
+} OSC;
+
+enum escape_mode {
+ EM_CSI = 1 << 0,
+ EM_STR = 1 << 1,
+};
+
enum terminal_mode {
TM_ALTSCREEN = 1 << 0,
TM_REPLACE = 1 << 1,
@@ -363,6 +385,9 @@ typedef struct {
/* NOTE: scrolling region */
u32 top, bot;
+ CSI csi;
+
+ enum escape_mode escape;
enum terminal_mode mode;
char saved_title[1024];