vtgl

terminal emulator implemented in OpenGL
git clone anongit@rnpnr.xyz:vtgl.git
Log | Files | Refs | Feed | LICENSE

Commit: 74a07eb867da5ff89d82991b946540bc99610802
Parent: 21f08e5cafd29dae841324424e7debc7f51009c0
Author: Randy Palamar
Date:   Thu, 31 Oct 2024 06:14:43 -0600

fix tab advancement for arbitrary tabstop locations

Diffstat:
Mterminal.c | 19+++++++++++++++----
Mtest.c | 136+++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------
2 files changed, 107 insertions(+), 48 deletions(-)

diff --git a/terminal.c b/terminal.c @@ -334,15 +334,26 @@ cursor_step_raw(Term *t, i32 step, i32 rows, i32 cols) static i32 next_tab_position(Term *t, b32 backwards) { + static_assert(ARRAY_COUNT(t->tabs) == 8 * sizeof(*t->tabs), + "Term.tabs must be same length as the bitwidth of it's type"); u32 col = t->cursor.pos.x; u32 idx = col / ARRAY_COUNT(t->tabs); u32 bit = col % ARRAY_COUNT(t->tabs); u32 mask = safe_left_shift(1, bit - backwards) - 1; - i32 result = 32 * idx; - if (backwards) result += 32 - _lzcnt_u32(t->tabs[idx] & mask); - else result += _tzcnt_u32(t->tabs[idx] & ~mask) + 1; - ASSERT(ABS(result) < t->size.w); + u32 result = 32 * idx, zeroes; + if (backwards) zeroes = 32 - _lzcnt_u32(t->tabs[idx++] & mask); + else zeroes = _tzcnt_u32(t->tabs[idx++] & ~mask); + result += zeroes; + + /* NOTE we may need to advance across idx boundaries */ + while (idx < ARRAY_COUNT(t->tabs) && zeroes == 32) { + if (backwards) zeroes = 32 - _lzcnt_u32(t->tabs[idx++]); + else zeroes = _tzcnt_u32(t->tabs[idx++]); + result += zeroes; + } + result += !backwards; + ASSERT(result < t->size.w); return result; } diff --git a/test.c b/test.c @@ -60,12 +60,13 @@ struct test_result { b32 status; const char *info; }; #define TEST_FN(name) struct test_result name(Term *term, Arena arena) typedef TEST_FN(Test_Fn); -#define TESTS \ - X(csi_embedded_control) \ - X(colour_setting) \ - X(cursor_at_line_boundary) \ - X(cursor_movement) \ - X(cursor_tabs) \ +#define TESTS \ + X(csi_embedded_control) \ + X(colour_setting) \ + X(cursor_at_line_boundary) \ + X(cursor_movement) \ + X(cursor_tabs) \ + X(cursor_tabs_across_boundary) \ X(working_ringbuffer) #define X(fn) static TEST_FN(fn); @@ -76,6 +77,7 @@ TESTS static Test_Fn *tests[] = { TESTS }; +#undef X #define ESC(a) s8("\x1B"#a) #define CSI(a) ESC([a) @@ -194,43 +196,6 @@ static TEST_FN(colour_setting) return result; } -static TEST_FN(cursor_movement) -{ - struct test_result result = {.info = __FUNCTION__}; - - s8 raw = launder_static_string(term, CSI(17;2H)); - handle_input(term, arena, raw); - - raw = launder_static_string(term, CSI(7G)); - handle_input(term, arena, raw); - - result.status = term->cursor.pos.y == 16 && term->cursor.pos.x == 6; - - return result; -} - -static TEST_FN(cursor_tabs) -{ - struct test_result result = {.info = __FUNCTION__}; - - /* NOTE: first test advancing to a tabstop */ - s8 raw = launder_static_string(term, s8("123\t")); - handle_input(term, arena, raw); - - result.status = term->cursor.pos.x == (g_tabstop); - - /* NOTE: now test negative tabstop movement and tabstop setting */ - launder_static_string(term, s8("12")); - launder_static_string(term, ESC(H)); - launder_static_string(term, s8("34\t")); - raw = launder_static_string(term, CSI(2Z)); - handle_input(term, arena, raw); - - result.status &= term->cursor.pos.x == (g_tabstop); - - return result; -} - static TEST_FN(cursor_at_line_boundary) { /* NOTE: two tests here @@ -289,6 +254,83 @@ static TEST_FN(cursor_at_line_boundary) return result; } + +static TEST_FN(cursor_movement) +{ + struct test_result result = {.info = __FUNCTION__}; + + s8 raw = launder_static_string(term, CSI(17;2H)); + handle_input(term, arena, raw); + + raw = launder_static_string(term, CSI(7G)); + handle_input(term, arena, raw); + + result.status = term->cursor.pos.y == 16 && term->cursor.pos.x == 6; + + return result; +} + +static TEST_FN(cursor_tabs) +{ + struct test_result result = {.info = __FUNCTION__}; + + /* NOTE: first test advancing to a tabstop */ + s8 raw = launder_static_string(term, s8("123\t")); + handle_input(term, arena, raw); + + result.status = term->cursor.pos.x == (g_tabstop); + + /* NOTE: now test negative tabstop movement and tabstop setting */ + launder_static_string(term, s8("12")); + launder_static_string(term, ESC(H)); + launder_static_string(term, s8("34\t")); + raw = launder_static_string(term, CSI(2Z)); + handle_input(term, arena, raw); + + result.status &= term->cursor.pos.x == (g_tabstop); + + return result; +} + +static TEST_FN(cursor_tabs_across_boundary) +{ + struct test_result result = {.info = __FUNCTION__}; + + /* NOTE: clear tabstops then set one beyond multiple boundaries */ + launder_static_string(term, CSI(3g)); + launder_static_string(term, CSI(1;67H)); + launder_static_string(term, ESC(H)); + launder_static_string(term, CSI(1;1H)); + s8 raw = launder_static_string(term, s8("\t")); + handle_input(term, arena, raw); + + result.status = term->cursor.pos.x == 66; + + /* NOTE: now set one exactly on a boundary */ + launder_static_string(term, CSI(1;34H)); + launder_static_string(term, ESC(H)); + launder_static_string(term, CSI(1;1H)); + raw = launder_static_string(term, s8("\t")); + handle_input(term, arena, raw); + + result.status &= term->cursor.pos.x == 33; + + /* NOTE: now set one right before the previous */ + launder_static_string(term, CSI(1;33H)); + launder_static_string(term, ESC(H)); + launder_static_string(term, CSI(1;1H)); + + raw = launder_static_string(term, s8("\t")); + handle_input(term, arena, raw); + result.status &= term->cursor.pos.x == 32; + + raw = launder_static_string(term, s8("\t")); + handle_input(term, arena, raw); + result.status &= term->cursor.pos.x == 33; + + return result; +} + static TEST_FN(working_ringbuffer) { struct test_result result = {.info = __FUNCTION__}; @@ -331,6 +373,12 @@ main(void) os_alloc_framebuffer(&term.views[0].fb, term.size.h, term.size.w); os_alloc_framebuffer(&term.views[1].fb, term.size.h, term.size.w); + u32 max_name_len = 0; + #define X(name) if (sizeof(#name) - 1 > max_name_len) max_name_len = sizeof(#name) - 1; + TESTS + #undef X + max_name_len += 1; + for (u32 i = 0; i < ARRAY_COUNT(tests); i++) { buffer_reset(&term); term_reset(&term); @@ -339,7 +387,7 @@ main(void) stream_push_s8(&log, fn); stream_push_s8(&log, s8(":")); size count = fn.len; - while (count < 26) { stream_push_byte(&log, ' '); count++; } + while (count < max_name_len) { stream_push_byte(&log, ' '); count++; } if (result.status == 0) { failure_count++; stream_push_s8(&log, failure_string);