aboutsummaryrefslogtreecommitdiff
path: root/ttwhy/terminal/echo.cppm
diff options
context:
space:
mode:
Diffstat (limited to 'ttwhy/terminal/echo.cppm')
-rw-r--r--ttwhy/terminal/echo.cppm207
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