module; #include #include #include #include #include export module ttwhy.routers:echo; import ttwhy.scanners; namespace ttwhy::routers { using namespace std::string_view_literals; constexpr auto vte_backspace_sequence = "\b \b"sv; constexpr auto vte_newline_sequence = "\r\n"sv; constexpr auto vte_horizontal_tab_sequence = "\t"sv; constexpr auto vte_cursor_left_sequence = "\x1b[D"sv; constexpr auto vte_cursor_right_sequence = "\x1b[C"sv; constexpr auto vte_delete_character_sequence = "\x1b[P"sv; constexpr auto vte_cursor_left_sequence_format = "\x1b[{}D"sv; constexpr auto vte_cursor_right_sequence_format = "\x1b[{}C"sv; export template struct echo { using terminal_policy = scanners::xterm_policy; explicit echo(StreamType & stream) : m_output_stream{stream} {} [[nodiscard]] auto cursor_position() const noexcept -> std::size_t { return m_cursor_position; } [[nodiscard]] auto max_position() const noexcept -> std::size_t { return m_max_position; } auto process(scanners::input_event event) -> asio::awaitable { co_await std::visit( [&](auto && event) -> asio::awaitable { using event_type = std::decay_t; if constexpr (std::same_as) { co_await process_character(event.value); } else if constexpr (std::same_as) { co_await process_control_key(event.key); } else if constexpr (std::same_as) { co_await process_navigation_key(event); } }, event); } private: auto process_character(char character) -> asio::awaitable { auto [error, bytes_written] = co_await asio::async_write(m_output_stream, asio::buffer(&character, 1), asio::as_tuple(asio::use_awaitable)); if (!error) { m_cursor_position += bytes_written; m_max_position = std::max(m_max_position, m_cursor_position); } } auto process_control_key(scanners::control_key key) -> asio::awaitable { switch (key) { case scanners::control_key::backspace: if (m_cursor_position > 0) { auto [error, bytes_written] = co_await asio::async_write( m_output_stream, asio::buffer(vte_backspace_sequence), asio::as_tuple(asio::use_awaitable)); if (!error) { if (m_cursor_position == m_max_position) { --m_max_position; } --m_cursor_position; } } break; case scanners::control_key::enter: { auto [error, bytes_written] = co_await asio::async_write(m_output_stream, asio::buffer(vte_newline_sequence), asio::as_tuple(asio::use_awaitable)); if (!error) { m_cursor_position = 0; m_max_position = 0; } break; } case scanners::control_key::tab: { auto [error, bytes_written] = co_await asio::async_write( m_output_stream, asio::buffer(vte_horizontal_tab_sequence), asio::as_tuple(asio::use_awaitable)); if (!error) { auto const tab_stop = 8uz - (m_cursor_position % 8uz); m_cursor_position += tab_stop; m_max_position = std::max(m_max_position, m_cursor_position); } break; } default: co_await asio::async_write(m_output_stream, asio::buffer(std::format("{{CTRL:{}}}", std::to_underlying(key))), asio::as_tuple(asio::use_awaitable)); break; }; } auto process_navigation_key(scanners::navigation_event event) -> asio::awaitable { switch (event.key) { case scanners::navigation_key::left: if (m_cursor_position > 0) { auto [error, bytes_written] = co_await asio::async_write( m_output_stream, asio::buffer(vte_cursor_left_sequence), asio::as_tuple(asio::use_awaitable)); if (!error) { --m_cursor_position; } } break; case scanners::navigation_key::right: if (m_cursor_position < m_max_position) { auto [error, bytes_written] = co_await asio::async_write( m_output_stream, asio::buffer(vte_cursor_right_sequence), asio::as_tuple(asio::use_awaitable)); if (!error) { ++m_cursor_position; } } break; case scanners::navigation_key::home: if (m_cursor_position > 0) { auto sequence = std::format(vte_cursor_left_sequence_format, m_cursor_position); auto [error, bytes_written] = co_await asio::async_write(m_output_stream, asio::buffer(sequence), asio::as_tuple(asio::use_awaitable)); if (!error) { m_cursor_position = 0; } } break; case scanners::navigation_key::end: if (m_cursor_position < m_max_position) { auto const distance = m_max_position - m_cursor_position; auto sequence = std::format(vte_cursor_right_sequence_format, distance); auto [error, bytes_written] = co_await asio::async_write(m_output_stream, asio::buffer(sequence), asio::as_tuple(asio::use_awaitable)); if (!error) { m_cursor_position = m_max_position; } } break; case scanners::navigation_key::delete_key: if (m_cursor_position < m_max_position) { auto [error, bytes_written] = co_await asio::async_write( m_output_stream, asio::buffer(vte_delete_character_sequence), asio::as_tuple(asio::use_awaitable)); if (!error) { --m_max_position; } } break; default: break; } } StreamType & m_output_stream; std::size_t m_cursor_position{}; std::size_t m_max_position{}; }; } // namespace ttwhy::routers