vtgl

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

Commit: 5cfed867fbcbf3da79163eb1a960e2956bd6e14d
Parent: 4421c6c6fbfa37fb60c7d1b348c4bc8a210bbca2
Author: Randy Palamar
Date:   Fri, 14 Feb 2025 20:23:02 -0700

tests: add a handful of tests from the ghostty wiki

this is not all of them but enough to get started fixing things.
Tests which are failing are supposed to be working but are
implemented incorrectly. Unsupported tests should be implemented
but are not currently (mostl these are all tied to left-right
margins).

Diffstat:
Mtests/test.c | 635++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 614 insertions(+), 21 deletions(-)

diff --git a/tests/test.c b/tests/test.c @@ -33,16 +33,49 @@ typedef TEST_FN(Test_Fn); 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) +/* TODO(rnp): skipped the following wiki pages + * - Cursor Horizontal Tabulation (CHT) + * - Cursor Next Line (CNL) + * - Cursor Previous Line (CPL) + * - Cursor Forward (CUF) + * - Cursor Up (CUU) + * - Set Left and Right Margins (DECSLRM) + */ + #define GHOSTTY_TESTS \ X(cursor_backwards_tabulation_v1) \ X(cursor_backwards_tabulation_v2) \ X(cursor_backwards_tabulation_v3) \ - X(cursor_backwards_tabulation_v4) + X(cursor_backwards_tabulation_v4) \ + X(cursor_backwards_v1) \ + X(cursor_backwards_v2) \ + X(cursor_backwards_v3) \ + X(cursor_backwards_v4) \ + X(cursor_backwards_v5) \ + X(cursor_backwards_v6) \ + X(cursor_backwards_v7) \ + X(cursor_down_v1) \ + X(cursor_down_v2) \ + X(cursor_down_v3) \ + X(cursor_position_v1) \ + X(cursor_position_v2) \ + X(cursor_position_v3) \ + X(cursor_position_v4) \ + X(cursor_position_v5) \ + X(cursor_position_v6) \ + X(delete_characters_v1) \ + X(delete_characters_v2) \ + X(delete_characters_v3) \ + X(delete_characters_v4) \ + X(delete_characters_v5) \ + X(set_top_bottom_margins_v1) \ + X(set_top_bottom_margins_v2) \ + X(set_top_bottom_margins_v3) \ + X(set_top_bottom_margins_v4) #define X(fn) static TEST_FN(fn); TESTS @@ -224,21 +257,6 @@ 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__}; @@ -322,7 +340,7 @@ static TEST_FN(working_ringbuffer) /* GHOSTTY TESTS (https://ghostty.org/docs/vt) */ /***********************************************/ -/* NOTE: Left Beyond First Column */ +/* NOTE: CBT V-1: Left Beyond First Column */ static TEST_FN(cursor_backwards_tabulation_v1) { struct test_result result = {.info = __FUNCTION__}; @@ -339,7 +357,7 @@ static TEST_FN(cursor_backwards_tabulation_v1) return result; } -/* NOTE: Left Starting After Tab Stop */ +/* NOTE: CBT V-2: Left Starting After Tab Stop */ static TEST_FN(cursor_backwards_tabulation_v2) { struct test_result result = {.info = __FUNCTION__}; @@ -357,7 +375,7 @@ static TEST_FN(cursor_backwards_tabulation_v2) return result; } -/* NOTE: Left Starting At Tab Stop */ +/* NOTE: CBT V-3: Left Starting on Tabstop */ static TEST_FN(cursor_backwards_tabulation_v3) { struct test_result result = {.info = __FUNCTION__}; @@ -377,7 +395,7 @@ static TEST_FN(cursor_backwards_tabulation_v3) return result; } -/* NOTE: Left Margin in Origin Mode */ +/* NOTE: CBT V-4: Left Margin with Origin Mode */ static TEST_FN(cursor_backwards_tabulation_v4) { struct test_result result = {.info = __FUNCTION__}; @@ -403,6 +421,581 @@ static TEST_FN(cursor_backwards_tabulation_v4) return result; } +/* NOTE: CUB V-1: Pending Wrap is Unset */ +static TEST_FN(cursor_backwards_v1) +{ + struct test_result result = {.info = __FUNCTION__}; + + u8 buffer_store[32]; + Stream buffer = {.buf = buffer_store, .cap = sizeof(buffer_store)}; + stream_push_s8(&buffer, s8("\x1B[")); + stream_push_u64(&buffer, term->size.w); + stream_push_byte(&buffer, 'G'); + launder_static_string(term, stream_to_s8(&buffer)); + + launder_static_string(term, s8("A")); + launder_static_string(term, CSI(D)); + s8 raw = launder_static_string(term, s8("XYZ")); + handle_input(term, arena, raw); + + result.status = term->views[term->view_idx].fb.rows[0][term->size.w - 2].cp == 'X'; + result.status &= term->views[term->view_idx].fb.rows[0][term->size.w - 1].cp == 'Y'; + result.status &= term->views[term->view_idx].fb.rows[1][0].cp == 'Z'; + result.status &= term->cursor.pos.y == 1 && term->cursor.pos.x == 1; + + return result; +} + +/* NOTE: CUB V-2: Leftmost Boundary with Reverse Wrap Disabled */ +static TEST_FN(cursor_backwards_v2) +{ + struct test_result result = {.info = __FUNCTION__}; + + launder_static_string(term, CSI(?45l)); + launder_static_string(term, s8("A\r\n")); + launder_static_string(term, CSI(10D)); + s8 raw = launder_static_string(term, s8("B")); + handle_input(term, arena, raw); + + result.status = term->views[term->view_idx].fb.rows[0][0].cp == 'A'; + result.status &= term->views[term->view_idx].fb.rows[1][0].cp == 'B'; + result.status &= term->cursor.pos.y == 1 && term->cursor.pos.x == 1; + + return result; +} + +/* NOTE: CUB V-3: Reverse Wrap */ +static TEST_FN(cursor_backwards_v3) +{ + struct test_result result = {.info = __FUNCTION__}; + result.status = UNSUPPORTED; + + #if 0 + u8 buffer_store[32]; + Stream buffer = {.buf = buffer_store, .cap = sizeof(buffer_store)}; + stream_push_s8(&buffer, s8("\x1B[")); + stream_push_u64(&buffer, term->size.w); + stream_push_byte(&buffer, 'G'); + + launder_static_string(term, CSI(?7h)); + launder_static_string(term, CSI(?45h)); + launder_static_string(term, stream_to_s8(&buffer)); + launder_static_string(term, s8("AB")); + launder_static_string(term, CSI(D)); + s8 raw = launder_static_string(term, s8("X")); + handle_input(term, arena, raw); + + result.status = term->views[term->view_idx].fb.rows[0][term->size.w - 1].cp == 'X'; + result.status &= term->views[term->view_idx].fb.rows[1][0].cp == 'B'; + result.status &= term->cursor.pos.y == 0 && term->cursor.pos.x == term->size.w - 1; + result.status &= (term->cursor.state & CURSOR_WRAP_NEXT) != 0; + #endif + + return result; +} + +/* NOTE: CUB V-4: Extended Reverse Wrap Single Line */ +static TEST_FN(cursor_backwards_v4) +{ + struct test_result result = {.info = __FUNCTION__}; + result.status = UNSUPPORTED; + + #if 0 + launder_static_string(term, CSI(?7h)); + launder_static_string(term, CSI(?1045h)); + launder_static_string(term, s8("A\r\nB")); + launder_static_string(term, CSI(2D)); + s8 raw = launder_static_string(term, s8("X")); + handle_input(term, arena, raw); + + result.status = term->views[term->view_idx].fb.rows[0][0].cp == 'A'; + result.status &= term->views[term->view_idx].fb.rows[1][0].cp == 'B'; + result.status &= term->views[term->view_idx].fb.rows[0][term->size.w - 1].cp == 'X'; + result.status &= term->cursor.pos.y == 0 && term->cursor.pos.x == term->size.w - 1; + result.status &= (term->cursor.state & CURSOR_WRAP_NEXT) != 0; + #endif + + return result; +} + +/* NOTE: CUB V-5: Extended Reverse Wrap Wraps to Bottom */ +static TEST_FN(cursor_backwards_v5) +{ + struct test_result result = {.info = __FUNCTION__}; + result.status = UNSUPPORTED; + + #if 0 + u8 buffer_store[32]; + Stream buffer = {.buf = buffer_store, .cap = sizeof(buffer_store)}; + stream_push_s8(&buffer, s8("\x1B[")); + stream_push_u64(&buffer, term->size.w); + stream_push_byte(&buffer, 'D'); + + launder_static_string(term, CSI(?7h)); + launder_static_string(term, CSI(?45h)); + launder_static_string(term, CSI(1;3r)); + launder_static_string(term, s8("A\r\nB")); + launder_static_string(term, CSI(D)); + launder_static_string(term, stream_to_s8(&buffer)); + launder_static_string(term, CSI(D)); + s8 raw = launder_static_string(term, s8("X")); + handle_input(term, arena, raw); + + result.status = term->views[term->view_idx].fb.rows[0][0].cp == 'A'; + result.status &= term->views[term->view_idx].fb.rows[1][0].cp == 'B'; + result.status &= term->views[term->view_idx].fb.rows[2][term->size.w - 1].cp == 'X'; + result.status &= term->cursor.pos.y == 0 && term->cursor.pos.x == term->size.w - 1; + result.status &= (term->cursor.state & CURSOR_WRAP_NEXT) != 0; + #endif + + return result; +} + +/* NOTE: CUB V-6: Reverse Wrap Outside of Margins */ +static TEST_FN(cursor_backwards_v6) +{ + struct test_result result = {.info = __FUNCTION__}; + result.status = UNSUPPORTED; + + #if 0 + launder_static_string(term, CSI(?45h)); + launder_static_string(term, CSI(3r)); + s8 raw = launder_static_string(term, s8("\bX")); + handle_input(term, arena, raw); + + result.status = term->views[term->view_idx].fb.rows[2][0].cp == 'X'; + result.status &= term->cursor.pos.y == 2 && term->cursor.pos.x == 1; + #endif + + return result; +} + +/* NOTE: CUB V-7: Reverse Wrap with Pending Wrap State */ +static TEST_FN(cursor_backwards_v7) +{ + struct test_result result = {.info = __FUNCTION__}; + result.status = UNSUPPORTED; + + #if 0 + u8 buffer_store[32]; + Stream buffer = {.buf = buffer_store, .cap = sizeof(buffer_store)}; + stream_push_s8(&buffer, s8("\x1B[")); + stream_push_u64(&buffer, term->size.w); + stream_push_byte(&buffer, 'G'); + + launder_static_string(term, CSI(?45h)); + launder_static_string(term, stream_to_s8(&buffer)); + launder_static_string(term, CSI(4D)); + launder_static_string(term, s8("ABCDE")); + launder_static_string(term, CSI(D)); + s8 raw = launder_static_string(term, s8("X")); + handle_input(term, arena, raw); + + result.status = term->views[term->view_idx].fb.rows[0][term->size.w - 1].cp == 'X'; + result.status &= term->views[term->view_idx].fb.rows[0][term->size.w - 2].cp == 'D'; + result.status &= term->views[term->view_idx].fb.rows[0][term->size.w - 3].cp == 'C'; + result.status &= term->views[term->view_idx].fb.rows[0][term->size.w - 4].cp == 'B'; + result.status &= term->views[term->view_idx].fb.rows[0][term->size.w - 5].cp == 'A'; + result.status &= term->cursor.pos.y == 0 && term->cursor.pos.x == term->size.w - 1; + result.status &= (term->cursor.state & CURSOR_WRAP_NEXT) != 0; + #endif + + return result; +} + +/* NOTE: CUD V-1: Cursor Down */ +static TEST_FN(cursor_down_v1) +{ + struct test_result result = {.info = __FUNCTION__}; + + launder_static_string(term, s8("A")); + launder_static_string(term, CSI(2B)); + s8 raw = launder_static_string(term, s8("X")); + handle_input(term, arena, raw); + + result.status = term->views[term->view_idx].fb.rows[0][0].cp == 'A'; + result.status &= term->views[term->view_idx].fb.rows[2][1].cp == 'X'; + result.status &= term->cursor.pos.y == 2 && term->cursor.pos.x == 2; + + return result; +} + +/* NOTE: CUD V-2: Cursor Down Above Bottom Margin */ +static TEST_FN(cursor_down_v2) +{ + struct test_result result = {.info = __FUNCTION__}; + + launder_static_string(term, CSI(1;3r)); + launder_static_string(term, s8("A")); + launder_static_string(term, CSI(5B)); + s8 raw = launder_static_string(term, s8("X")); + handle_input(term, arena, raw); + + result.status = term->views[term->view_idx].fb.rows[0][0].cp == 'A'; + result.status &= term->views[term->view_idx].fb.rows[2][1].cp == 'X'; + result.status &= term->cursor.pos.y == 2 && term->cursor.pos.x == 2; + + return result; +} + +/* NOTE: CUD V-3: Cursor Down Below Bottom Margin */ +static TEST_FN(cursor_down_v3) +{ + struct test_result result = {.info = __FUNCTION__}; + + launder_static_string(term, CSI(1;3r)); + launder_static_string(term, s8("A")); + launder_static_string(term, CSI(4;1H)); + launder_static_string(term, CSI(5B)); + s8 raw = launder_static_string(term, s8("X")); + handle_input(term, arena, raw); + + result.status = term->views[term->view_idx].fb.rows[0][0].cp == 'A'; + result.status &= term->views[term->view_idx].fb.rows[4][1].cp == 'X'; + result.status &= term->cursor.pos.y == 4 && term->cursor.pos.x == 2; + + return result; +} + +/* NOTE: CUP V-1: Normal Usage */ +static TEST_FN(cursor_position_v1) +{ + struct test_result result = {.info = __FUNCTION__}; + + launder_static_string(term, CSI(2;3H)); + s8 raw = launder_static_string(term, s8("A")); + handle_input(term, arena, raw); + + result.status = term->views[term->view_idx].fb.rows[1][2].cp == 'A'; + result.status &= term->cursor.pos.y == 1 && term->cursor.pos.x == 3; + + return result; +} + +/* NOTE: CUP V-2: Off the Screen */ +static TEST_FN(cursor_position_v2) +{ + struct test_result result = {.info = __FUNCTION__}; + + launder_static_string(term, CSI(500;500H)); + s8 raw = launder_static_string(term, s8("A")); + handle_input(term, arena, raw); + + result.status = term->views[term->view_idx].fb.rows[term->size.h - 1][term->size.w - 1].cp == 'A'; + result.status &= term->cursor.pos.y == (term->size.h - 1); + result.status &= term->cursor.pos.x == (term->size.w - 1); + result.status &= (term->cursor.state & CURSOR_WRAP_NEXT) != 0; + + return result; +} + +/* NOTE: CUP V-3: Relative to Origin */ +static TEST_FN(cursor_position_v3) +{ + struct test_result result = {.info = __FUNCTION__}; + + launder_static_string(term, CSI(2;3r)); + launder_static_string(term, CSI(?6h)); + launder_static_string(term, CSI(1;1H)); + s8 raw = launder_static_string(term, s8("X")); + handle_input(term, arena, raw); + + result.status = term->views[term->view_idx].fb.rows[1][0].cp == 'X'; + + return result; +} + +/* NOTE: CUP V-4: Relative to Origin with Left/Right Margins */ +static TEST_FN(cursor_position_v4) +{ + struct test_result result = {.info = __FUNCTION__}; + result.status = UNSUPPORTED; + + #if 0 + launder_static_string(term, CSI(?69h)); + launder_static_string(term, CSI(3;5s)); + launder_static_string(term, CSI(2;3r)); + launder_static_string(term, CSI(?6h)); + launder_static_string(term, CSI(1;1H)); + s8 raw = launder_static_string(term, s8("X")); + handle_input(term, arena, raw); + + result.status = term->views[term->view_idx].fb.rows[1][2].cp == 'X'; + #endif + + return result; +} + +/* NOTE: CUP V-5: Limits with Scroll Region and Origin Mode */ +static TEST_FN(cursor_position_v5) +{ + struct test_result result = {.info = __FUNCTION__}; + result.status = UNSUPPORTED; + + #if 0 + launder_static_string(term, CSI(?69h)); + launder_static_string(term, CSI(3;5s)); + launder_static_string(term, CSI(2;3r)); + launder_static_string(term, CSI(?6h)); + launder_static_string(term, CSI(500;500H)); + s8 raw = launder_static_string(term, s8("X")); + handle_input(term, arena, raw); + + result.status = term->views[term->view_idx].fb.rows[2][4].cp == 'X'; + #endif + + return result; +} + +/* NOTE: CUP V-6: Pending Wrap is Unset */ +static TEST_FN(cursor_position_v6) +{ + struct test_result result = {.info = __FUNCTION__}; + + u8 buffer_store[32]; + Stream buffer = {.buf = buffer_store, .cap = sizeof(buffer_store)}; + stream_push_s8(&buffer, s8("\x1B[")); + stream_push_u64(&buffer, term->size.w); + stream_push_byte(&buffer, 'G'); + + launder_static_string(term, stream_to_s8(&buffer)); + launder_static_string(term, s8("A")); + launder_static_string(term, CSI(1;1H)); + s8 raw = launder_static_string(term, s8("X")); + handle_input(term, arena, raw); + + result.status = term->views[term->view_idx].fb.rows[0][0].cp == 'X'; + result.status &= term->views[term->view_idx].fb.rows[0][term->size.w - 1].cp == 'A'; + result.status &= term->cursor.pos.y == 0 && term->cursor.pos.x == 1; + + return result; +} + +/* NOTE: DCH V-1: Simple Delete Character */ +static TEST_FN(delete_characters_v1) +{ + struct test_result result = {.info = __FUNCTION__}; + + launder_static_string(term, s8("ABC123")); + launder_static_string(term, CSI(3G)); + s8 raw = launder_static_string(term, CSI(2P)); + handle_input(term, arena, raw); + + Row row = term->views[term->view_idx].fb.rows[0]; + result.status = row[0].cp == 'A'; + result.status &= row[1].cp == 'B'; + result.status &= row[2].cp == '2'; + result.status &= row[3].cp == '3'; + result.status &= row[4].cp == ' '; + result.status &= row[5].cp == ' '; + + return result; +} + +/* NOTE: DCH V-2: SGR State */ +static TEST_FN(delete_characters_v2) +{ + struct test_result result = {.info = __FUNCTION__}; + + launder_static_string(term, s8("ABC123")); + launder_static_string(term, CSI(3G)); + launder_static_string(term, CSI(41m)); + s8 raw = launder_static_string(term, CSI(2P)); + handle_input(term, arena, raw); + + Cell cell = {.cp = ' ', + .bg = SHADER_PACK_BG(g_colours.data[1].rgba, ATTR_NULL), + .fg = SHADER_PACK_FG(g_colours.data[g_colours.fgidx].rgba, ATTR_NULL), + }; + + Row row = term->views[term->view_idx].fb.rows[0]; + result.status = row[0].cp == 'A'; + result.status &= row[1].cp == 'B'; + result.status &= row[2].cp == '2'; + result.status &= row[3].cp == '3'; + result.status &= check_cells_equal(&cell, &row[term->size.w - 2]); + result.status &= check_cells_equal(&cell, &row[term->size.w - 1]); + + return result; +} + +/* NOTE: DCH V-3: Outside Left/Right Scroll Region */ +static TEST_FN(delete_characters_v3) +{ + struct test_result result = {.info = __FUNCTION__}; + result.status = UNSUPPORTED; + + #if 0 + launder_static_string(term, s8("ABC123")); + launder_static_string(term, CSI(?69h)); + launder_static_string(term, CSI(3;5s)); + launder_static_string(term, CSI(2G)); + s8 raw = launder_static_string(term, CSI(P)); + handle_input(term, arena, raw); + + Row row = term->views[term->view_idx].fb.rows[0]; + result.status = row[0].cp == 'A'; + result.status &= row[1].cp == 'B'; + result.status &= row[2].cp == 'C'; + result.status &= row[3].cp == '1'; + result.status &= row[4].cp == '2'; + result.status &= row[5].cp == '3'; + #endif + + return result; +} + +/* NOTE: DCH V-4: Inside Left/Right Scroll Region */ +static TEST_FN(delete_characters_v4) +{ + struct test_result result = {.info = __FUNCTION__}; + result.status = UNSUPPORTED; + + #if 0 + launder_static_string(term, s8("ABC123")); + launder_static_string(term, CSI(?69h)); + launder_static_string(term, CSI(3;5s)); + launder_static_string(term, CSI(4G)); + s8 raw = launder_static_string(term, CSI(P)); + handle_input(term, arena, raw); + + Row row = term->views[term->view_idx].fb.rows[0]; + result.status = row[0].cp == 'A'; + result.status &= row[1].cp == 'B'; + result.status &= row[2].cp == 'C'; + result.status &= row[3].cp == '2'; + result.status &= row[4].cp == ' '; + result.status &= row[5].cp == '3'; + #endif + + return result; +} + +/* NOTE: DCH V-5: Split Wide Character */ +static TEST_FN(delete_characters_v5) +{ + struct test_result result = {.info = __FUNCTION__}; + + launder_static_string(term, s8("Ać©‹123")); + launder_static_string(term, CSI(3G)); + s8 raw = launder_static_string(term, CSI(P)); + handle_input(term, arena, raw); + + Row row = term->views[term->view_idx].fb.rows[0]; + result.status = row[0].cp == 'A'; + result.status &= row[1].cp == ' '; + result.status &= row[2].cp == '1'; + result.status &= row[3].cp == '2'; + result.status &= row[4].cp == '3'; + + return result; +} + +/* NOTE: DECSTBM V-1: Full Screen */ +static TEST_FN(set_top_bottom_margins_v1) +{ + struct test_result result = {.info = __FUNCTION__}; + + launder_static_string(term, s8("ABC\nDEF\nGHI\n")); + launder_static_string(term, CSI(r)); + s8 raw = launder_static_string(term, CSI(T)); + handle_input(term, arena, raw); + + Row *rows = term->views[term->view_idx].fb.rows; + result.status = rows[0][0].cp == ' '; + result.status &= rows[0][1].cp == ' '; + result.status &= rows[0][2].cp == ' '; + result.status &= rows[1][0].cp == 'A'; + result.status &= rows[1][1].cp == 'B'; + result.status &= rows[1][2].cp == 'C'; + result.status &= rows[2][0].cp == 'D'; + result.status &= rows[2][1].cp == 'E'; + result.status &= rows[2][2].cp == 'F'; + result.status &= rows[3][0].cp == 'G'; + result.status &= rows[3][1].cp == 'H'; + result.status &= rows[3][2].cp == 'I'; + result.status &= term->cursor.pos.x == 0 && term->cursor.pos.y == 0; + + return result; +} + +/* NOTE: DECSTBM V-2: Top Only */ +static TEST_FN(set_top_bottom_margins_v2) +{ + struct test_result result = {.info = __FUNCTION__}; + + launder_static_string(term, s8("ABC\nDEF\nGHI\n")); + launder_static_string(term, CSI(2r)); + s8 raw = launder_static_string(term, CSI(T)); + handle_input(term, arena, raw); + + Row *rows = term->views[term->view_idx].fb.rows; + result.status = rows[0][0].cp == 'A'; + result.status &= rows[0][1].cp == 'B'; + result.status &= rows[0][2].cp == 'C'; + result.status &= rows[1][0].cp == ' '; + result.status &= rows[1][1].cp == ' '; + result.status &= rows[1][2].cp == ' '; + result.status &= rows[2][0].cp == 'D'; + result.status &= rows[2][1].cp == 'E'; + result.status &= rows[2][2].cp == 'F'; + result.status &= rows[3][0].cp == 'G'; + result.status &= rows[3][1].cp == 'H'; + result.status &= rows[3][2].cp == 'I'; + + return result; +} + +/* NOTE: DECSTBM V-3: Top and Bottom */ +static TEST_FN(set_top_bottom_margins_v3) +{ + struct test_result result = {.info = __FUNCTION__}; + + launder_static_string(term, s8("ABC\nDEF\nGHI\n")); + launder_static_string(term, CSI(1;2r)); + s8 raw = launder_static_string(term, CSI(T)); + handle_input(term, arena, raw); + + Row *rows = term->views[term->view_idx].fb.rows; + result.status = rows[0][0].cp == ' '; + result.status &= rows[0][1].cp == ' '; + result.status &= rows[0][2].cp == ' '; + result.status &= rows[1][0].cp == 'A'; + result.status &= rows[1][1].cp == 'B'; + result.status &= rows[1][2].cp == 'C'; + result.status &= rows[2][0].cp == 'G'; + result.status &= rows[2][1].cp == 'H'; + result.status &= rows[2][2].cp == 'I'; + + return result; +} + +/* NOTE: DECSTBM V-4: Top Equal to Bottom */ +static TEST_FN(set_top_bottom_margins_v4) +{ + struct test_result result = {.info = __FUNCTION__}; + + launder_static_string(term, s8("ABC\nDEF\nGHI\n")); + launder_static_string(term, CSI(2;2r)); + s8 raw = launder_static_string(term, CSI(T)); + handle_input(term, arena, raw); + + Row *rows = term->views[term->view_idx].fb.rows; + result.status = rows[0][0].cp == ' '; + result.status &= rows[0][1].cp == ' '; + result.status &= rows[0][2].cp == ' '; + result.status &= rows[1][0].cp == 'A'; + result.status &= rows[1][1].cp == 'B'; + result.status &= rows[1][2].cp == 'C'; + result.status &= rows[2][0].cp == 'D'; + result.status &= rows[2][1].cp == 'E'; + result.status &= rows[2][2].cp == 'F'; + result.status &= rows[3][0].cp == 'G'; + result.status &= rows[3][1].cp == 'H'; + result.status &= rows[3][2].cp == 'I'; + + return result; +} static Term * place_term_into_memory(MemoryBlock memory, i32 rows, i32 columns)