aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFelix Morgner <felix.morgner@gmail.com>2026-09-24 07:59:43 +0200
committerFelix Morgner <felix.morgner@gmail.com>2026-09-24 07:59:43 +0200
commit79a35574ce6d472f7a7cc4a200a1ed7befe34066 (patch)
tree1fb3c1c33645692bc041af8d285bb99754887149
parentd05b9343f69a0841043963088e0003368e205bd9 (diff)
downloadttwhy-79a35574ce6d472f7a7cc4a200a1ed7befe34066.tar.xz
ttwhy-79a35574ce6d472f7a7cc4a200a1ed7befe34066.zip
doc: add API documentation
-rw-r--r--ttwhy/core/concepts.cppm11
-rw-r--r--ttwhy/terminal/echo.cppm16
-rw-r--r--ttwhy/terminal/events.cppm44
-rw-r--r--ttwhy/terminal/policies.cppm31
-rw-r--r--ttwhy/terminal/readers.cppm12
-rw-r--r--ttwhy/terminal/scanner.cppm17
-rw-r--r--ttwhy/terminal/scoped_attributes.cppm29
-rw-r--r--ttwhy/terminal/signal_handling.cppm11
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>
{