tuinix/
lib.rs

1//! A library for building terminal user interface (TUI) applications on Unix systems with minimum dependencies.
2//!
3//! `tuinix` provides a lightweight foundation for building terminal-based user interfaces with minimal
4//! dependencies (only `libc` is required). The library offers a clean API for:
5//!
6//! - Managing terminal state (raw mode, alternate screen)
7//! - Capturing and processing keyboard input
8//! - Drawing styled text with ANSI colors
9//! - Handling terminal resize events
10//! - Creating efficient terminal frames with differential updates
11//!
12//! ## Basic Example
13//!
14//! This example demonstrates basic terminal UI functionality including initializing the terminal,
15//! drawing styled text, processing keyboard events, and handling terminal resizing.
16//!
17//! ```no_run
18//! use std::{fmt::Write, time::Duration};
19//!
20//! use tuinix::{Terminal, TerminalColor, TerminalEvent, TerminalFrame, TerminalInput, TerminalStyle};
21//!
22//! fn main() -> Result<(), Box<dyn std::error::Error>> {
23//!     // Initialize terminal
24//!     let mut terminal = Terminal::new()?;
25//!
26//!     // Create a frame with the terminal's dimensions
27//!     let mut frame: TerminalFrame = TerminalFrame::new(terminal.size());
28//!
29//!     // Add styled content to the frame
30//!     let title_style = TerminalStyle::new().bold().fg_color(TerminalColor::GREEN);
31//!
32//!     writeln!(
33//!         frame,
34//!         "{}Welcome to tuinix!{}",
35//!         title_style,
36//!         TerminalStyle::RESET
37//!     )?;
38//!     writeln!(frame, "\nPress any key ('q' to quit)")?;
39//!
40//!     // Draw the frame to the terminal
41//!     terminal.draw(frame)?;
42//!
43//!     // Process input events with a timeout
44//!     loop {
45//!         match terminal.poll_event(&[], &[], Some(Duration::from_millis(100)))? {
46//!             Some(TerminalEvent::Input(input)) => {
47//!                 let TerminalInput::Key(input) = input else {
48//!                     continue;  // Skip mouse events
49//!                 };
50//!
51//!                 // Check if 'q' was pressed
52//!                 if let tuinix::KeyCode::Char('q') = input.code {
53//!                     break;
54//!                 }
55//!
56//!                 // Display the input
57//!                 let mut frame: TerminalFrame = TerminalFrame::new(terminal.size());
58//!                 writeln!(frame, "Key pressed: {:?}", input)?;
59//!                 writeln!(frame, "\nPress any key ('q' to quit)")?;
60//!                 terminal.draw(frame)?;
61//!             }
62//!             Some(TerminalEvent::Resize(size)) => {
63//!                 // Terminal was resized, update UI if needed
64//!                 let mut frame: TerminalFrame = TerminalFrame::new(size);
65//!                 writeln!(frame, "Terminal resized to {}x{}", size.cols, size.rows)?;
66//!                 writeln!(frame, "\nPress any key ('q' to quit)")?;
67//!                 terminal.draw(frame)?;
68//!             }
69//!             Some(TerminalEvent::FdReady { .. }) => unreachable!(),
70//!             None => {
71//!                 // Timeout elapsed, no events to process
72//!             }
73//!         }
74//!     }
75//!
76//!     Ok(())
77//! }
78//! ```
79//!
80//! For integration with external event loop libraries like `mio`, see the [nonblocking.rs] example.
81//!
82//! [nonblocking.rs]: https://github.com/sile/tuinix/blob/main/examples/nonblocking.rs
83#![warn(missing_docs)]
84use std::{io::ErrorKind, os::fd::RawFd};
85
86mod frame;
87mod geometry;
88mod input;
89mod style;
90mod terminal;
91
92pub use frame::{EstimateCharWidth, FixedCharWidthEstimator, TerminalFrame};
93pub use geometry::{TerminalPosition, TerminalRegion, TerminalSize};
94pub use input::{KeyCode, KeyInput, MouseEvent, MouseInput, TerminalInput};
95pub use style::{TerminalColor, TerminalStyle};
96pub use terminal::{Terminal, TerminalEvent};
97
98/// Sets a file descriptor to non-blocking mode.
99///
100/// This function modifies the flags of the given file descriptor (`fd`) to
101/// include the `O_NONBLOCK` flag, which makes operations on the file descriptor
102/// non-blocking.
103///
104/// When a file descriptor is in non-blocking mode, operations that would normally
105/// block until data is available (such as `read`) or until resources are ready
106/// (such as `write`) will instead immediately return with [`std::io::ErrorKind::WouldBlock`]
107/// if the operation cannot be completed without blocking. This allows the calling
108/// thread to continue execution and check for availability later, which is
109/// particularly useful in asynchronous I/O patterns.
110pub fn set_nonblocking(fd: RawFd) -> std::io::Result<()> {
111    unsafe {
112        let flags = libc::fcntl(fd, libc::F_GETFL, 0);
113        if flags < 0 {
114            return Err(std::io::Error::last_os_error());
115        }
116        if libc::fcntl(fd, libc::F_SETFL, flags | libc::O_NONBLOCK) < 0 {
117            return Err(std::io::Error::last_os_error());
118        }
119        Ok(())
120    }
121}
122
123/// Handles the result of a non-blocking I/O operation by converting [`ErrorKind::WouldBlock`] errors to `Ok(None)`.
124///
125/// This utility function is designed to work with non-blocking I/O operations (typically used after
126/// calling [`set_nonblocking()`] on [`Terminal::input_fd()`] and [`Terminal::signal_fd()`]). When a non-blocking operation returns a
127/// [`ErrorKind::WouldBlock`] error, indicating that the operation would need to block to complete, this function
128/// converts it to `Ok(None)` for easier handling in caller code.
129pub fn try_nonblocking<T>(result: std::io::Result<T>) -> std::io::Result<Option<T>> {
130    match result {
131        Err(e) if e.kind() == ErrorKind::WouldBlock => Ok(None),
132        Err(e) => Err(e),
133        Ok(v) => Ok(Some(v)),
134    }
135}
136
137/// Handles the result of an I/O operation that might be interrupted by converting [`ErrorKind::Interrupted`] errors to `Ok(None)`.
138///
139/// This utility function manages system calls that can be interrupted by signals. When an I/O operation
140/// returns an [`ErrorKind::Interrupted`] error, indicating that a system call was interrupted by a signal
141/// before it could complete, this function converts it to `Ok(None)` for easier handling in caller code.
142///
143/// This is particularly useful in scenarios where you want to retry operations that were interrupted,
144/// rather than propagating the error.
145pub fn try_uninterrupted<T>(result: std::io::Result<T>) -> std::io::Result<Option<T>> {
146    match result {
147        Err(e) if e.kind() == ErrorKind::Interrupted => Ok(None),
148        Err(e) => Err(e),
149        Ok(v) => Ok(Some(v)),
150    }
151}