diff options
| author | Felix Morgner <felix.morgner@gmail.com> | 2026-10-26 19:15:33 +0100 |
|---|---|---|
| committer | Felix Morgner <felix.morgner@gmail.com> | 2026-10-26 19:15:33 +0100 |
| commit | b051983db21290871c7a33cf8189f415c07f8a24 (patch) | |
| tree | 4258f9fdc05f5eec0341147047d8eb40ea055454 /ttwhy/terminal/scanner.tests.cpp | |
| parent | 79a35574ce6d472f7a7cc4a200a1ed7befe34066 (diff) | |
| download | ttwhy-b051983db21290871c7a33cf8189f415c07f8a24.tar.xz ttwhy-b051983db21290871c7a33cf8189f415c07f8a24.zip | |
terminal: improve function and control key handling
Diffstat (limited to 'ttwhy/terminal/scanner.tests.cpp')
| -rw-r--r-- | ttwhy/terminal/scanner.tests.cpp | 283 |
1 files changed, 283 insertions, 0 deletions
diff --git a/ttwhy/terminal/scanner.tests.cpp b/ttwhy/terminal/scanner.tests.cpp index 64acb2d..b2a7f1e 100644 --- a/ttwhy/terminal/scanner.tests.cpp +++ b/ttwhy/terminal/scanner.tests.cpp @@ -22,6 +22,22 @@ using namespace std::string_view_literals; return data != nullptr && data->key == expected; } +[[nodiscard]] constexpr auto static is_navigation(ttwhy::terminal::input_event const & event, + ttwhy::terminal::navigation_key expected_key, + ttwhy::terminal::modifier_set expected_modifiers = {}) -> bool +{ + auto const * data = std::get_if<ttwhy::terminal::navigation_event>(&event); + return data != nullptr && data->key == expected_key && data->modifiers == expected_modifiers; +} + +[[nodiscard]] constexpr auto static is_function(ttwhy::terminal::input_event const & event, + ttwhy::terminal::function_key expected_key, + ttwhy::terminal::modifier_set expected_modifiers = {}) -> bool +{ + auto const * data = std::get_if<ttwhy::terminal::function_key_event>(&event); + return data != nullptr && data->key == expected_key && data->modifiers == expected_modifiers; +} + SCENARIO("The ANSI scanner processes printable ASCII and standard C0 control characters", "[scanner][ansi]") { GIVEN("An initialized scanner and event sink") @@ -99,3 +115,270 @@ SCENARIO("The ANSI scanner processes printable ASCII and standard C0 control cha } } } + +SCENARIO("The ANSI scanner processes VT220 keypad and cursor navigation sequences", "[scanner][ansi]") +{ + GIVEN("An initialized ANSI scanner and event sink") + { + auto queue = std::vector<ttwhy::terminal::input_event>{}; + auto sink = [&queue](auto const & event) { + queue.push_back(event); + }; + auto scanner = ttwhy::terminal::ansi{sink}; + + WHEN("Processing VT220 keypad Home (\\x1b[1~)") + { + scanner.process("\x1b[1~"sv); + + THEN("It yields a navigation event for home") + { + REQUIRE(queue.size() == 1); + CHECK(is_navigation(queue.at(0), ttwhy::terminal::navigation_key::home)); + } + } + + WHEN("Processing VT220 keypad Insert (\\x1b[2~)") + { + scanner.process("\x1b[2~"sv); + + THEN("It yields a navigation event for insert") + { + REQUIRE(queue.size() == 1); + CHECK(is_navigation(queue.at(0), ttwhy::terminal::navigation_key::insert_key)); + } + } + + WHEN("Processing VT220 keypad Delete (\\x1b[3~)") + { + scanner.process("\x1b[3~"sv); + + THEN("It yields a navigation event for delete") + { + REQUIRE(queue.size() == 1); + CHECK(is_navigation(queue.at(0), ttwhy::terminal::navigation_key::delete_key)); + } + } + + WHEN("Processing VT220 keypad End (\\x1b[4~)") + { + scanner.process("\x1b[4~"sv); + + THEN("It yields a navigation event for end") + { + REQUIRE(queue.size() == 1); + CHECK(is_navigation(queue.at(0), ttwhy::terminal::navigation_key::end)); + } + } + + WHEN("Processing VT220 keypad Page Up (\\x1b[5~)") + { + scanner.process("\x1b[5~"sv); + + THEN("It yields a navigation event for page up") + { + REQUIRE(queue.size() == 1); + CHECK(is_navigation(queue.at(0), ttwhy::terminal::navigation_key::page_up)); + } + } + + WHEN("Processing VT220 keypad Page Down (\\x1b[6~)") + { + scanner.process("\x1b[6~"sv); + + THEN("It yields a navigation event for page down") + { + REQUIRE(queue.size() == 1); + CHECK(is_navigation(queue.at(0), ttwhy::terminal::navigation_key::page_down)); + } + } + + WHEN("Processing VT100 cursor arrows") + { + scanner.process("\x1b[A\x1b[B\x1b[C\x1b[D"sv); + + THEN("It yields navigation events for up, down, right, and left") + { + REQUIRE(queue.size() == 4); + CHECK(is_navigation(queue.at(0), ttwhy::terminal::navigation_key::up)); + CHECK(is_navigation(queue.at(1), ttwhy::terminal::navigation_key::down)); + CHECK(is_navigation(queue.at(2), ttwhy::terminal::navigation_key::right)); + CHECK(is_navigation(queue.at(3), ttwhy::terminal::navigation_key::left)); + } + } + } +} + +SCENARIO("The ANSI scanner processes multi-digit function keys and SS3 sequences", "[scanner][ansi]") +{ + GIVEN("An initialized ANSI scanner and event sink") + { + auto queue = std::vector<ttwhy::terminal::input_event>{}; + auto sink = [&queue](auto const & event) { + queue.push_back(event); + }; + auto scanner = ttwhy::terminal::ansi{sink}; + + WHEN("Processing F1 (\\x1b[11~)") + { + scanner.process("\x1b[11~"sv); + + THEN("It yields a function key event for F1 and not home") + { + REQUIRE(queue.size() == 1); + CHECK(is_function(queue.at(0), ttwhy::terminal::function_key::f1)); + } + } + + WHEN("Processing F5 (\\x1b[15~)") + { + scanner.process("\x1b[15~"sv); + + THEN("It yields a function key event for F5 and not home") + { + REQUIRE(queue.size() == 1); + CHECK(is_function(queue.at(0), ttwhy::terminal::function_key::f5)); + } + } + + WHEN("Processing F6 (\\x1b[17~)") + { + scanner.process("\x1b[17~"sv); + + THEN("It yields a function key event for F6") + { + REQUIRE(queue.size() == 1); + CHECK(is_function(queue.at(0), ttwhy::terminal::function_key::f6)); + } + } + + WHEN("Processing F12 (\\x1b[24~)") + { + scanner.process("\x1b[24~"sv); + + THEN("It yields a function key event for F12") + { + REQUIRE(queue.size() == 1); + CHECK(is_function(queue.at(0), ttwhy::terminal::function_key::f12)); + } + } + + WHEN("Processing F20 (\\x1b[34~)") + { + scanner.process("\x1b[34~"sv); + + THEN("It yields a function key event for F20") + { + REQUIRE(queue.size() == 1); + CHECK(is_function(queue.at(0), ttwhy::terminal::function_key::f20)); + } + } + + WHEN("Processing an unmapped multi-digit code (\\x1b[99~)") + { + scanner.process("\x1b[99~"sv); + + THEN("No event is emitted") + { + CHECK(queue.empty()); + } + } + + WHEN("Processing SS3 function keys F1 through F4 (\\x1bOP to \\x1bOS)") + { + scanner.process("\x1bOP\x1bOQ\x1bOR\x1bOS"sv); + + THEN("It yields function key events for F1 through F4") + { + REQUIRE(queue.size() == 4); + CHECK(is_function(queue.at(0), ttwhy::terminal::function_key::f1)); + CHECK(is_function(queue.at(1), ttwhy::terminal::function_key::f2)); + CHECK(is_function(queue.at(2), ttwhy::terminal::function_key::f3)); + CHECK(is_function(queue.at(3), ttwhy::terminal::function_key::f4)); + } + } + } +} + +SCENARIO("The xterm scanner decodes keypad sequences and function keys with modifiers", "[scanner][xterm]") +{ + GIVEN("An initialized xterm scanner and event sink") + { + auto queue = std::vector<ttwhy::terminal::input_event>{}; + auto sink = [&queue](auto const & event) { + queue.push_back(event); + }; + auto scanner = ttwhy::terminal::xterm{sink}; + + WHEN("Processing Shift+Home (\\x1b[1;2~)") + { + scanner.process("\x1b[1;2~"sv); + + THEN("It yields a navigation event for Home with shift modifier") + { + REQUIRE(queue.size() == 1); + CHECK(is_navigation(queue.at(0), ttwhy::terminal::navigation_key::home, + ttwhy::terminal::modifier::shift | ttwhy::terminal::modifier::none)); + } + } + + WHEN("Processing Ctrl+Delete (\\x1b[3;5~)") + { + scanner.process("\x1b[3;5~"sv); + + THEN("It yields a navigation event for Delete with ctrl modifier") + { + REQUIRE(queue.size() == 1); + CHECK(is_navigation(queue.at(0), ttwhy::terminal::navigation_key::delete_key, + ttwhy::terminal::modifier::ctrl | ttwhy::terminal::modifier::none)); + } + } + + WHEN("Processing Shift+F5 (\\x1b[15;2~)") + { + scanner.process("\x1b[15;2~"sv); + + THEN("It yields a function key event for F5 with shift modifier") + { + REQUIRE(queue.size() == 1); + CHECK(is_function(queue.at(0), ttwhy::terminal::function_key::f5, + ttwhy::terminal::modifier::shift | ttwhy::terminal::modifier::none)); + } + } + + WHEN("Processing Ctrl+F5 (\\x1b[15;5~)") + { + scanner.process("\x1b[15;5~"sv); + + THEN("It yields a function key event for F5 with ctrl modifier") + { + REQUIRE(queue.size() == 1); + CHECK(is_function(queue.at(0), ttwhy::terminal::function_key::f5, + ttwhy::terminal::modifier::ctrl | ttwhy::terminal::modifier::none)); + } + } + + WHEN("Processing Alt+F12 (\\x1b[24;3~)") + { + scanner.process("\x1b[24;3~"sv); + + THEN("It yields a function key event for F12 with alt modifier") + { + REQUIRE(queue.size() == 1); + CHECK(is_function(queue.at(0), ttwhy::terminal::function_key::f12, + ttwhy::terminal::modifier::alt | ttwhy::terminal::modifier::none)); + } + } + + WHEN("Processing Shift+Up cursor (\\x1b[1;2A)") + { + scanner.process("\x1b[1;2A"sv); + + THEN("It yields a navigation event for Up with shift modifier") + { + REQUIRE(queue.size() == 1); + CHECK(is_navigation(queue.at(0), ttwhy::terminal::navigation_key::up, + ttwhy::terminal::modifier::shift | ttwhy::terminal::modifier::none)); + } + } + } +} |
