From 79a35574ce6d472f7a7cc4a200a1ed7befe34066 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Thu, 24 Sep 2026 07:59:43 +0200 Subject: doc: add API documentation --- ttwhy/core/concepts.cppm | 11 +++++++++ ttwhy/terminal/echo.cppm | 16 +++++++++++++ ttwhy/terminal/events.cppm | 44 +++++++++++++++++++++++++++++++++++ ttwhy/terminal/policies.cppm | 31 ++++++++++++++++++++++++ ttwhy/terminal/readers.cppm | 12 ++++++++++ ttwhy/terminal/scanner.cppm | 17 ++++++++++++++ ttwhy/terminal/scoped_attributes.cppm | 29 ++++++++++++++++++++--- ttwhy/terminal/signal_handling.cppm | 11 +++++++++ 8 files changed, 168 insertions(+), 3 deletions(-) (limited to 'ttwhy') diff --git a/ttwhy/core/concepts.cppm b/ttwhy/core/concepts.cppm index f7ac583..eea97e2 100644 --- a/ttwhy/core/concepts.cppm +++ b/ttwhy/core/concepts.cppm @@ -10,6 +10,9 @@ export module ttwhy.core:concepts; namespace ttwhy { + //! Validates that a candidate type implements the byte scanner interface. + //! + //! @tparam Candidate The type to validate. export template concept scanner = requires(Candidate a, std::span buffer) { { a.process(buffer) } -> std::same_as; @@ -17,11 +20,19 @@ namespace ttwhy { a.is_pending() } -> std::same_as; }; + //! Validates that a candidate type implements an asynchronous event router interface. + //! + //! @tparam Candidate The type to validate. + //! @tparam Event The type of the event to process. export template concept router = requires(Candidate candidate, Event event) { { candidate.process(event) } -> std::same_as>; }; + //! Validates that a sink type can receive an event of the given type. + //! + //! @tparam Sink The sink callable type. + //! @tparam Event The type of the event to deliver. export template concept event_sink = requires(Sink sink, Event event) { { sink(event) } -> std::same_as; diff --git a/ttwhy/terminal/echo.cppm b/ttwhy/terminal/echo.cppm index 660cf42..6f1ac2e 100644 --- a/ttwhy/terminal/echo.cppm +++ b/ttwhy/terminal/echo.cppm @@ -27,23 +27,39 @@ namespace ttwhy::terminal constexpr auto vte_cursor_left_sequence_format = "\x1b[{}D"sv; constexpr auto vte_cursor_right_sequence_format = "\x1b[{}C"sv; + //! An asynchronous router that echoes characters and handles terminal navigation. + //! + //! @tparam StreamType The output stream descriptor type. export template struct echo { + //! Construct an echo router writing to an output stream. + //! + //! @param stream The output stream to write echoed text and escape sequences to. explicit echo(StreamType & stream) : m_output_stream{stream} {} + //! Get the current cursor position within the printed line. + //! + //! @return The zero-based cursor position. [[nodiscard]] auto cursor_position() const noexcept -> std::size_t { return m_cursor_position; } + //! Get the maximum cursor position reached on the current line. + //! + //! @return The maximum position reached. [[nodiscard]] auto max_position() const noexcept -> std::size_t { return m_max_position; } + //! Asynchronously process an incoming terminal input event. + //! + //! @param event The input event to handle. + //! @return An awaitable task that completes when processing finishes. auto process(input_event event) -> asio::awaitable { co_await std::visit( diff --git a/ttwhy/terminal/events.cppm b/ttwhy/terminal/events.cppm index 93b2326..ce4a7d5 100644 --- a/ttwhy/terminal/events.cppm +++ b/ttwhy/terminal/events.cppm @@ -10,6 +10,7 @@ import ttwhy.core; export namespace ttwhy::terminal { + //! Represents standard C0 terminal control keys. enum struct control_key : std::uint8_t { enter, @@ -18,6 +19,7 @@ export namespace ttwhy::terminal backspace, }; + //! Represents terminal cursor and keypad navigation keys. enum struct navigation_key : std::uint8_t { up, @@ -32,6 +34,7 @@ export namespace ttwhy::terminal page_down, }; + //! Represents individual keyboard modifier keys. enum struct modifier : std::uint8_t { none = 0, @@ -41,65 +44,106 @@ export namespace ttwhy::terminal meta = 8, }; + //! Represents a bitmask combination of keyboard modifiers. enum struct modifier_set : std::uint8_t { }; + //! Combine two keyboard modifiers into a modifier set. + //! + //! @param lhs The first modifier to combine. + //! @param rhs The second modifier to combine. + //! @return A modifier set containing both modifiers. constexpr auto operator|(modifier lhs, modifier rhs) -> modifier_set { return static_cast(std::to_underlying(lhs) | std::to_underlying(rhs)); } + //! Add a keyboard modifier to an existing modifier set. + //! + //! @param lhs The existing modifier set. + //! @param rhs The modifier to add. + //! @return The resulting modifier set. constexpr auto operator|(modifier_set lhs, modifier rhs) -> modifier_set { return static_cast(std::to_underlying(lhs) | std::to_underlying(rhs)); } + //! Compute the bitwise intersection of two keyboard modifiers. + //! + //! @param lhs The first modifier. + //! @param rhs The second modifier. + //! @return The intersection as a modifier set. constexpr auto operator&(modifier lhs, modifier rhs) -> modifier_set { return static_cast(std::to_underlying(lhs) & std::to_underlying(rhs)); } + //! Check for the presence of a keyboard modifier in a modifier set. + //! + //! @param lhs The modifier set. + //! @param rhs The modifier to test. + //! @return The intersection as a modifier set. constexpr auto operator&(modifier_set lhs, modifier rhs) -> modifier_set { return static_cast(std::to_underlying(lhs) & std::to_underlying(rhs)); } + //! Check whether all specified modifiers are present in a modifier set. + //! + //! @tparam Modifier The modifier types to check. + //! @param mask The modifier set to inspect. + //! @param flag The modifier flags that must all be present. + //! @return True if all specified flags are present, false otherwise. template... Modifier> constexpr auto has_all(modifier_set mask, Modifier... flag) -> bool { return (true && ... && (mask & flag)); } + //! Check whether any of the specified modifiers are present in a modifier set. + //! + //! @tparam Modifier The modifier types to check. + //! @param mask The modifier set to inspect. + //! @param flag The modifier flags to test for. + //! @return True if at least one of the specified flags is present, false otherwise. template... Modifier> constexpr auto has_any(modifier_set mask, Modifier... flag) -> bool { return (false || ... || (mask & flag)); } + //! Represents a printable ASCII or raw character event. struct character_event { char value{}; }; + //! Represents a terminal control key event. struct control_event { control_key key{}; }; + //! Represents a terminal cursor or keypad navigation event. struct navigation_event { navigation_key key{}; modifier_set modifiers{}; }; + //! Represents a Control-key chord event. struct ctrl_chord_event { char key{}; }; + //! Variant representing any input event produced by a terminal scanner. using input_event = std::variant; + //! Validates that a sink can receive all terminal input event types. + //! + //! @tparam Sink The sink callable type to validate. template concept event_sink = ttwhy::event_sink && ttwhy::event_sink && ttwhy::event_sink; diff --git a/ttwhy/terminal/policies.cppm b/ttwhy/terminal/policies.cppm index acf9371..83694b8 100644 --- a/ttwhy/terminal/policies.cppm +++ b/ttwhy/terminal/policies.cppm @@ -58,8 +58,13 @@ namespace ttwhy::terminal } // namespace detail + //! Policy for decoding standard ANSI/VT escape sequences. export struct ansi_policy { + //! Resolve a VT220 keypad sequence and emit the corresponding navigation event. + //! + //! @param buffer The CSI parameter buffer. + //! @param sink The event sink to receive the resolved navigation event. constexpr auto static resolve_vt220_keypad(std::string & buffer, event_sink auto & sink) -> void { if (buffer.empty()) @@ -74,6 +79,10 @@ namespace ttwhy::terminal buffer.clear(); } + //! Resolve a VT100 cursor sequence and emit the corresponding navigation event. + //! + //! @param terminator The CSI terminator character indicating direction or key. + //! @param sink The event sink to receive the resolved navigation event. constexpr auto static resolve_vt100_cursor(char terminator, std::string &, event_sink auto & sink) -> void { if (auto key = detail::map_vt100_cursor(terminator)) @@ -82,6 +91,10 @@ namespace ttwhy::terminal } } + //! Resolve an SS3 single-shift sequence and emit the corresponding navigation event. + //! + //! @param terminator The SS3 terminator character indicating direction or key. + //! @param sink The event sink to receive the resolved navigation event. constexpr auto static resolve_ss3(char terminator, event_sink auto & sink) -> void { if (auto key = detail::map_vt100_cursor(terminator)) @@ -91,8 +104,13 @@ namespace ttwhy::terminal } }; + //! Policy for decoding xterm escape sequences, including modifier parameters. export struct xterm_policy { + //! Parse keyboard modifier parameters from a CSI parameter buffer. + //! + //! @param buffer The CSI parameter buffer string view. + //! @return The parsed modifier set, or empty if none present. constexpr auto static parse_modifiers(std::string_view buffer) -> modifier_set { auto delimiter = buffer.find(';'); @@ -120,6 +138,10 @@ namespace ttwhy::terminal return static_cast(modifier_value - 1); } + //! Resolve a VT220 keypad sequence with modifiers and emit the navigation event. + //! + //! @param buffer The CSI parameter buffer containing key and modifier values. + //! @param sink The event sink to receive the resolved navigation event. constexpr auto static resolve_vt220_keypad(std::string & buffer, event_sink auto & sink) -> void { if (buffer.empty()) @@ -136,6 +158,11 @@ namespace ttwhy::terminal buffer.clear(); } + //! Resolve a VT100 cursor sequence with modifiers and emit the navigation event. + //! + //! @param terminator The CSI terminator character indicating direction or key. + //! @param buffer The CSI parameter buffer containing modifier values. + //! @param sink The event sink to receive the resolved navigation event. constexpr auto static resolve_vt100_cursor(char terminator, std::string & buffer, event_sink auto & sink) -> void { auto modifiers = parse_modifiers(buffer); @@ -146,6 +173,10 @@ namespace ttwhy::terminal } } + //! Resolve an SS3 single-shift sequence and emit the corresponding navigation event. + //! + //! @param terminator The SS3 terminator character indicating direction or key. + //! @param sink The event sink to receive the resolved navigation event. constexpr auto static resolve_ss3(char terminator, event_sink auto & sink) -> void { if (auto key = detail::map_vt100_cursor(terminator)) diff --git a/ttwhy/terminal/readers.cppm b/ttwhy/terminal/readers.cppm index ffa5687..c59f30b 100644 --- a/ttwhy/terminal/readers.cppm +++ b/ttwhy/terminal/readers.cppm @@ -18,6 +18,18 @@ import ttwhy.core; namespace ttwhy::terminal { + //! Asynchronously read raw bytes from an input stream, decode terminal events, and dispatch them to a router. + //! + //! Reads chunks from the input stream, feeding them to a terminal scanner configured with the specified + //! policy. If a sequence is pending and no data arrives before the inter-byte timeout, triggers a timeout + //! resolution. Dispatches all resulting events to the provided router. + //! + //! @tparam TerminalPolicy The terminal escape decoding policy (defaults to xterm_policy). + //! @tparam InputStream The input stream type supporting asynchronous reads. + //! @tparam AppRouter The router type that processes terminal input events. + //! @param stream The input stream descriptor to read from. + //! @param router The application router to receive and process input events. + //! @return An awaitable task that runs until the stream ends or encounters an error. export template AppRouter> auto read_events(InputStream & stream, AppRouter & router) -> asio::awaitable { diff --git a/ttwhy/terminal/scanner.cppm b/ttwhy/terminal/scanner.cppm index 8a722b4..f1540f2 100644 --- a/ttwhy/terminal/scanner.cppm +++ b/ttwhy/terminal/scanner.cppm @@ -200,23 +200,37 @@ namespace ttwhy::terminal::detail export namespace ttwhy::terminal { + //! State machine tokenizer that decodes terminal escape sequences into input events. + //! + //! @tparam Sink The event sink callable type. + //! @tparam TerminalPolicy The terminal escape policy. template struct scanner { + //! Construct a terminal scanner bound to an event sink. + //! + //! @param sink The event sink to deliver decoded input events to. explicit scanner(Sink & sink) : m_state_machine{sink, m_csi_buffer} {} + //! Process a buffer of incoming raw bytes through the state machine. + //! + //! @param buffer The view of raw bytes to decode. auto process(std::span buffer) -> void { std::ranges::for_each(buffer, [&](auto byte) { m_state_machine.process_event(detail::byte_received{byte}); }); } + //! Notify the state machine that an inter-byte timeout expired. auto timeout() { m_state_machine.process_event(detail::timeout_expired{}); } + //! Check if an escape sequence is currently pending completion. + //! + //! @return True if a sequence is incomplete, false otherwise. [[nodiscard]] auto is_pending() const -> bool { return m_state_machine.is(detail::escape_sequence) || // @@ -230,6 +244,9 @@ export namespace ttwhy::terminal m_state_machine; }; + //! Convenience alias for a terminal scanner configured with the ANSI policy. + //! + //! @tparam Sink The event sink callable type. template using ansi = scanner; diff --git a/ttwhy/terminal/scoped_attributes.cppm b/ttwhy/terminal/scoped_attributes.cppm index 2ca34a3..56fbe58 100644 --- a/ttwhy/terminal/scoped_attributes.cppm +++ b/ttwhy/terminal/scoped_attributes.cppm @@ -52,9 +52,12 @@ namespace ttwhy::terminal } } - //! Store the current TC attributes and restore them on destruction. + //! RAII wrapper storing the current terminal attributes and restoring them on destruction. export struct scoped_attributes { + //! Construct and capture the current attributes of a terminal file descriptor. + //! + //! @param file_descriptor The file descriptor of the terminal. explicit scoped_attributes(int file_descriptor) : m_file_descriptor{-1} , m_attributes{} @@ -91,36 +94,56 @@ namespace ttwhy::terminal return *this; } - //! Check if canonical mode is currently active + //! Check if canonical mode is currently active. + //! + //! @return True if ICANON is set, false otherwise. [[nodiscard]] auto canonical_mode() const noexcept -> bool { return get_line_discipline_flag(m_file_descriptor, ICANON); } - //! Set whether canonical mode is active or not. + //! Set whether canonical mode is active or not on an lvalue reference. + //! + //! @param enabled True to enable canonical mode, false to disable. + //! @return Reference to this object for chaining. auto canonical_mode(bool enabled) & -> scoped_attributes & { set_line_discipline_flag(m_file_descriptor, ICANON, enabled); return *this; } + //! Set whether canonical mode is active or not on an rvalue. + //! + //! @param enabled True to enable canonical mode, false to disable. + //! @return The modified scoped_attributes object by value. auto canonical_mode(bool enabled) && -> scoped_attributes { set_line_discipline_flag(m_file_descriptor, ICANON, enabled); return std::move(*this); } + //! Check if echo is currently active. + //! + //! @return True if ECHO is set, false otherwise. [[nodiscard]] auto echo() const noexcept -> bool { return get_line_discipline_flag(m_file_descriptor, ECHO); } + //! Set whether echo is active or not on an lvalue reference. + //! + //! @param enabled True to enable echo, false to disable. + //! @return Reference to this object for chaining. auto echo(bool enabled) & -> scoped_attributes & { set_line_discipline_flag(m_file_descriptor, ECHO, enabled); return *this; } + //! Set whether echo is active or not on an rvalue. + //! + //! @param enabled True to enable echo, false to disable. + //! @return The modified scoped_attributes object by value. auto echo(bool enabled) && -> scoped_attributes { set_line_discipline_flag(m_file_descriptor, ECHO, enabled); diff --git a/ttwhy/terminal/signal_handling.cppm b/ttwhy/terminal/signal_handling.cppm index a3b5d4d..95b92f1 100644 --- a/ttwhy/terminal/signal_handling.cppm +++ b/ttwhy/terminal/signal_handling.cppm @@ -11,6 +11,17 @@ export module ttwhy.terminal:signal_handling; namespace ttwhy::terminal { + //! Asynchronously handle POSIX process and job-control signals for a terminal application. + //! + //! Intercepts SIGINT and SIGTERM to print an exit notification and terminate gracefully. + //! Intercepts SIGTSTP to restore terminal canonical and echo flags before raising SIGSTOP, + //! and intercepts SIGCONT to restore the terminal attributes when execution resumes. + //! + //! @tparam Stream The output stream type used for writing diagnostic messages. + //! @tparam TerminalAttributes The terminal attributes manager type. + //! @param stream The stream to write diagnostic messages to. + //! @param attributes The scoped attributes managing terminal canonical mode and echo. + //! @return An awaitable task that yields an error code upon termination. export template auto handle_signals(Stream & stream, TerminalAttributes & attributes) -> asio::awaitable { -- cgit v1.2.3