diff options
| author | Felix Morgner <felix.morgner@gmail.com> | 2026-09-24 07:59:43 +0200 |
|---|---|---|
| committer | Felix Morgner <felix.morgner@gmail.com> | 2026-09-24 07:59:43 +0200 |
| commit | 79a35574ce6d472f7a7cc4a200a1ed7befe34066 (patch) | |
| tree | 1fb3c1c33645692bc041af8d285bb99754887149 | |
| parent | d05b9343f69a0841043963088e0003368e205bd9 (diff) | |
| download | ttwhy-79a35574ce6d472f7a7cc4a200a1ed7befe34066.tar.xz ttwhy-79a35574ce6d472f7a7cc4a200a1ed7befe34066.zip | |
doc: add API documentation
| -rw-r--r-- | ttwhy/core/concepts.cppm | 11 | ||||
| -rw-r--r-- | ttwhy/terminal/echo.cppm | 16 | ||||
| -rw-r--r-- | ttwhy/terminal/events.cppm | 44 | ||||
| -rw-r--r-- | ttwhy/terminal/policies.cppm | 31 | ||||
| -rw-r--r-- | ttwhy/terminal/readers.cppm | 12 | ||||
| -rw-r--r-- | ttwhy/terminal/scanner.cppm | 17 | ||||
| -rw-r--r-- | ttwhy/terminal/scoped_attributes.cppm | 29 | ||||
| -rw-r--r-- | ttwhy/terminal/signal_handling.cppm | 11 |
8 files changed, 168 insertions, 3 deletions
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<typename Candidate> concept scanner = requires(Candidate a, std::span<char const> buffer) { { a.process(buffer) } -> std::same_as<void>; @@ -17,11 +20,19 @@ namespace ttwhy { a.is_pending() } -> std::same_as<bool>; }; + //! 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<typename Candidate, typename Event> concept router = requires(Candidate candidate, Event event) { { candidate.process(event) } -> std::same_as<asio::awaitable<void>>; }; + //! 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<typename Sink, typename Event> concept event_sink = requires(Sink sink, Event event) { { sink(event) } -> std::same_as<void>; 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<typename StreamType> 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<void> { 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<modifier_set>(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<modifier_set>(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<modifier_set>(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<modifier_set>(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<std::same_as<modifier>... 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<std::same_as<modifier>... 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<character_event, control_event, navigation_event, ctrl_chord_event>; + //! Validates that a sink can receive all terminal input event types. + //! + //! @tparam Sink The sink callable type to validate. template<typename Sink> concept event_sink = ttwhy::event_sink<Sink, character_event> && ttwhy::event_sink<Sink, control_event> && ttwhy::event_sink<Sink, navigation_event>; 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_set>(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<typename TerminalPolicy = xterm_policy, typename InputStream, ttwhy::router<input_event> AppRouter> auto read_events(InputStream & stream, AppRouter & router) -> asio::awaitable<void> { 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<event_sink Sink, typename TerminalPolicy> 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<char const> 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<event_sink Sink> using ansi = scanner<Sink, ansi_policy>; 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<typename Stream, typename TerminalAttributes> auto handle_signals(Stream & stream, TerminalAttributes & attributes) -> asio::awaitable<std::error_code> { |
