From 912e7b10facdd3b2565423206ae25b1d6e4f7dd9 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Sat, 20 Jun 2026 11:54:06 +0200 Subject: lib: add xterm policy --- ttwhy/scanners/concepts.cppm | 28 ------ ttwhy/scanners/events.cppm | 52 ---------- ttwhy/scanners/mod.cppm | 4 +- ttwhy/scanners/terminal_concepts.cppm | 27 +++++ ttwhy/scanners/terminal_events.cppm | 100 +++++++++++++++++++ ttwhy/scanners/terminal_policies.cppm | 180 +++++++++++++++++++++++++--------- ttwhy/scanners/terminal_scanner.cppm | 6 +- 7 files changed, 268 insertions(+), 129 deletions(-) delete mode 100644 ttwhy/scanners/concepts.cppm delete mode 100644 ttwhy/scanners/events.cppm create mode 100644 ttwhy/scanners/terminal_concepts.cppm create mode 100644 ttwhy/scanners/terminal_events.cppm (limited to 'ttwhy/scanners') diff --git a/ttwhy/scanners/concepts.cppm b/ttwhy/scanners/concepts.cppm deleted file mode 100644 index 8db3ae6..0000000 --- a/ttwhy/scanners/concepts.cppm +++ /dev/null @@ -1,28 +0,0 @@ -module; - -#include -#include -#include - -export module ttwhy.scanners:concepts; - -import :events; - -namespace ttwhy -{ - - export template - concept scanner = requires(Candidate a, std::span buffer) { - { a.process(buffer) } -> std::same_as; - { a.timeout() } -> std::same_as; - { a.is_pending() } -> std::same_as; - }; - - export template - concept ansi_sink = requires(Sink sink) { - { sink(std::declval()) } -> std::same_as; - { sink(std::declval()) } -> std::same_as; - { sink(std::declval()) } -> std::same_as; - }; - -} // namespace ttwhy diff --git a/ttwhy/scanners/events.cppm b/ttwhy/scanners/events.cppm deleted file mode 100644 index 03ab9eb..0000000 --- a/ttwhy/scanners/events.cppm +++ /dev/null @@ -1,52 +0,0 @@ -module; - -#include - -export module ttwhy.scanners:events; - -namespace ttwhy::scanners -{ - export enum class control_key - { - enter, - escape, - tab, - backspace, - }; - - export enum class navigation_key - { - up, - down, - left, - right, - home, - end, - delete_key, - insert_key, - page_up, - page_down, - }; - - export struct character_event - { - char value; - }; - - export struct control_event - { - control_key key; - }; - - export struct navigation_event - { - navigation_key key; - }; - - export struct ctrl_chord_event - { - char key; - }; - - export using input_event = std::variant; -} // namespace ttwhy::scanners diff --git a/ttwhy/scanners/mod.cppm b/ttwhy/scanners/mod.cppm index 54f1e0e..a1f45c1 100644 --- a/ttwhy/scanners/mod.cppm +++ b/ttwhy/scanners/mod.cppm @@ -1,6 +1,6 @@ export module ttwhy.scanners; -export import :concepts; -export import :events; +export import :terminal_concepts; +export import :terminal_events; export import :terminal_policies; export import :terminal_scanner; diff --git a/ttwhy/scanners/terminal_concepts.cppm b/ttwhy/scanners/terminal_concepts.cppm new file mode 100644 index 0000000..e82a8d0 --- /dev/null +++ b/ttwhy/scanners/terminal_concepts.cppm @@ -0,0 +1,27 @@ +module; + +#include +#include + +export module ttwhy.scanners:terminal_concepts; + +import :terminal_events; + +namespace ttwhy +{ + + export template + concept scanner = requires(Candidate a, std::span buffer) { + { a.process(buffer) } -> std::same_as; + { a.timeout() } -> std::same_as; + { a.is_pending() } -> std::same_as; + }; + + export template + concept ansi_sink = requires(Sink sink) { + { sink(std::declval()) } -> std::same_as; + { sink(std::declval()) } -> std::same_as; + { sink(std::declval()) } -> std::same_as; + }; + +} // namespace ttwhy diff --git a/ttwhy/scanners/terminal_events.cppm b/ttwhy/scanners/terminal_events.cppm new file mode 100644 index 0000000..3e4c849 --- /dev/null +++ b/ttwhy/scanners/terminal_events.cppm @@ -0,0 +1,100 @@ +module; + +#include +#include +#include + +export module ttwhy.scanners:terminal_events; + +export namespace ttwhy::scanners +{ + enum struct control_key : std::uint8_t + { + enter, + escape, + tab, + backspace, + }; + + enum struct navigation_key : std::uint8_t + { + up, + down, + left, + right, + home, + end, + delete_key, + insert_key, + page_up, + page_down, + }; + + enum struct modifier : std::uint8_t + { + none = 0, + shift = 1, + alt = 2, + ctrl = 4, + meta = 8, + }; + + enum struct modifier_set : std::uint8_t + { + }; + + constexpr auto operator|(modifier lhs, modifier rhs) -> modifier_set + { + return static_cast(std::to_underlying(lhs) | std::to_underlying(rhs)); + } + + constexpr auto operator|(modifier_set lhs, modifier rhs) -> modifier_set + { + return static_cast(std::to_underlying(lhs) | std::to_underlying(rhs)); + } + + constexpr auto operator&(modifier lhs, modifier rhs) -> modifier_set + { + return static_cast(std::to_underlying(lhs) & std::to_underlying(rhs)); + } + + constexpr auto operator&(modifier_set lhs, modifier rhs) -> modifier_set + { + return static_cast(std::to_underlying(lhs) & std::to_underlying(rhs)); + } + + template... Modifier> + constexpr auto has_all(modifier_set mask, Modifier... flag) -> bool + { + return (true && ... && (mask & flag)); + } + + template... Modifier> + constexpr auto has_any(modifier_set mask, Modifier... flag) -> bool + { + return (false || ... || (mask & flag)); + } + + struct character_event + { + char value{}; + }; + + struct control_event + { + control_key key{}; + }; + + struct navigation_event + { + navigation_key key{}; + modifier_set modifiers{}; + }; + + struct ctrl_chord_event + { + char key{}; + }; + + using input_event = std::variant; +} // namespace ttwhy::scanners diff --git a/ttwhy/scanners/terminal_policies.cppm b/ttwhy/scanners/terminal_policies.cppm index e5863d3..bf7c126 100644 --- a/ttwhy/scanners/terminal_policies.cppm +++ b/ttwhy/scanners/terminal_policies.cppm @@ -1,70 +1,162 @@ module; +#include #include export module ttwhy.scanners:terminal_policies; -import :events; -import :concepts; +import :terminal_events; +import :terminal_concepts; namespace ttwhy::scanners { + namespace detail + { + constexpr auto map_vt220_keypad(char terminator) -> std::optional + { + switch (terminator) + { + case '1': + case '7': + return navigation_key::home; + case '2': + return navigation_key::insert_key; + case '3': + return navigation_key::delete_key; + case '4': + case '8': + return navigation_key::end; + case '5': + return navigation_key::page_up; + case '6': + return navigation_key::page_down; + default: + return std::nullopt; + } + } + + constexpr auto map_vt100_cursor(char terminator) -> std::optional + { + switch (terminator) + { + case 'A': + return navigation_key::up; + case 'B': + return navigation_key::down; + case 'C': + return navigation_key::right; + case 'D': + return navigation_key::left; + case 'H': + return navigation_key::home; + case 'F': + return navigation_key::end; + default: + return std::nullopt; + } + } + + } // namespace detail + + export template + using associated_terminal_policy_t = typename T::terminal_policy; + export struct ansi_policy { constexpr auto static resolve_vt220_keypad(std::string & buffer, ttwhy::ansi_sink auto & sink) -> void { - auto key = [](auto terminator) { - switch (terminator) - { - case '1': - case '7': - return navigation_key::home; - case '2': - return navigation_key::insert_key; - case '3': - return navigation_key::delete_key; - case '4': - case '8': - return navigation_key::end; - case '5': - return navigation_key::page_up; - case '6': - return navigation_key::page_down; - default: - return static_cast(-1); - } - }(buffer.back()); - sink(navigation_event{key}); + if (buffer.empty()) + { + return; + } + + if (auto key = detail::map_vt220_keypad(buffer.front())) + { + sink(navigation_event{*key}); + } + buffer.clear(); } - constexpr auto static resolve_vt100_cursor(char terminator, ttwhy::ansi_sink auto & sink) -> void + constexpr auto static resolve_vt100_cursor(char terminator, std::string &, ttwhy::ansi_sink auto & sink) -> void { - auto key = [terminator] { - switch (terminator) + if (auto key = detail::map_vt100_cursor(terminator)) + { + sink(navigation_event{*key}); + } + } + + constexpr auto static resolve_ss3(char terminator, ttwhy::ansi_sink auto & sink) -> void + { + if (auto key = detail::map_vt100_cursor(terminator)) + { + sink(navigation_event{*key}); + } + } + }; + + export struct xterm_policy + { + constexpr auto static parse_modifiers(std::string_view buffer) -> modifier_set + { + auto delimiter = buffer.find(';'); + if (delimiter == std::string_view::npos) + { + return {}; + } + + auto modifier_string = buffer.substr(delimiter + 1); + auto modifier_value = 0u; + + for (auto character : modifier_string) + { + if (character >= '0' && character <= '9') { - case 'A': - return navigation_key::up; - case 'B': - return navigation_key::down; - case 'C': - return navigation_key::right; - case 'D': - return navigation_key::left; - case 'H': - return navigation_key::home; - case 'F': - return navigation_key::end; - default: - return static_cast(-1); + modifier_value = modifier_value * 10 + static_cast(character - '0'); } - }(); - sink(navigation_event{key}); + } + + if (modifier_value <= 1) + { + return {}; + } + + return static_cast(modifier_value - 1); + } + + constexpr auto static resolve_vt220_keypad(std::string & buffer, ttwhy::ansi_sink auto & sink) -> void + { + if (buffer.empty()) + { + return; + } + + auto modifiers = parse_modifiers(buffer); + + if (auto key = detail::map_vt220_keypad(buffer.front())) + { + sink(navigation_event{*key, modifiers}); + } + buffer.clear(); + } + + constexpr auto static resolve_vt100_cursor(char terminator, std::string & buffer, ttwhy::ansi_sink auto & sink) + -> void + { + auto modifiers = parse_modifiers(buffer); + + if (auto key = detail::map_vt100_cursor(terminator)) + { + sink(navigation_event{*key, modifiers}); + } } constexpr auto static resolve_ss3(char terminator, ttwhy::ansi_sink auto & sink) -> void { - return resolve_vt100_cursor(terminator, sink); + if (auto key = detail::map_vt100_cursor(terminator)) + { + sink(navigation_event{*key}); + } } }; diff --git a/ttwhy/scanners/terminal_scanner.cppm b/ttwhy/scanners/terminal_scanner.cppm index d877350..510299c 100644 --- a/ttwhy/scanners/terminal_scanner.cppm +++ b/ttwhy/scanners/terminal_scanner.cppm @@ -9,8 +9,8 @@ module; export module ttwhy.scanners:terminal_scanner; -import :concepts; -import :events; +import :terminal_concepts; +import :terminal_events; import :terminal_policies; namespace ttwhy::scanners::detail @@ -153,7 +153,7 @@ namespace ttwhy::scanners::detail }; constexpr auto resolve_vt100_cursor = [](byte_received event, std::string & buffer, Sink & sink) { - TerminalPolicy::resolve_vt100_cursor(event.value, sink); + TerminalPolicy::resolve_vt100_cursor(event.value, buffer, sink); buffer.clear(); }; -- cgit v1.2.3