diff options
Diffstat (limited to 'ttwhy/terminal/echo.cppm')
| -rw-r--r-- | ttwhy/terminal/echo.cppm | 207 |
1 files changed, 207 insertions, 0 deletions
diff --git a/ttwhy/terminal/echo.cppm b/ttwhy/terminal/echo.cppm new file mode 100644 index 0000000..660cf42 --- /dev/null +++ b/ttwhy/terminal/echo.cppm @@ -0,0 +1,207 @@ +module; + +#include <asio.hpp> + +#include <algorithm> + +#include <cstddef> +#include <format> +#include <string_view> + +export module ttwhy.terminal:echo; + +import :events; +import ttwhy.core; + +namespace ttwhy::terminal +{ + + 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<typename StreamType> + struct echo + { + 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(input_event event) -> asio::awaitable<void> + { + co_await std::visit( + [&](auto && event) -> asio::awaitable<void> { + using event_type = std::decay_t<decltype(event)>; + + if constexpr (std::same_as<character_event, event_type>) + { + co_await process_character(event.value); + } + else if constexpr (std::same_as<control_event, event_type>) + { + co_await process_control_key(event.key); + } + else if constexpr (std::same_as<navigation_event, event_type>) + { + co_await process_navigation_key(event); + } + }, + event); + } + + private: + auto process_character(char character) -> asio::awaitable<void> + { + 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(control_key key) -> asio::awaitable<void> + { + switch (key) + { + case 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 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 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(navigation_event event) -> asio::awaitable<void> + { + switch (event.key) + { + case 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 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 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 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 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::terminal |
