blob: 4fb8cd7ea77a018ec4a2128fe80bb82c7253e003 [file]
//! Terminal handles and platform-neutral terminal operations.
//!
//! [`PlatformTerminal`] opens the process terminal for the current target. It implements
//! [`Terminal`], which combines byte output, raw/cooked mode switching, terminal dimensions,
//! synchronous event reads, polling, and panic-hook cleanup. Termina does not enter alternate
//! screen with [`DecPrivateModeCode::ClearAndEnableAlternateScreen`], enable
//! [`DecPrivateModeCode::BracketedPaste`], or enable mouse tracking modes such as
//! [`DecPrivateModeCode::MouseTracking`] for you. Those are protocol choices the application
//! writes explicitly with [`crate::escape`].
//!
//! # Examples
//!
//! ```no_run
//! use std::{io, time::Duration};
//!
//! use termina::{
//! event::{Event, KeyEventKind},
//! PlatformTerminal, Terminal,
//! };
//!
//! fn main() -> io::Result<()> {
//! let mut terminal = PlatformTerminal::new()?;
//! terminal.enter_raw_mode()?;
//!
//! if terminal.poll(|_| true, Some(Duration::from_millis(250)))? {
//! let event = terminal.read(|event| {
//! matches!(event, Event::Key(key) if key.kind == KeyEventKind::Press)
//! })?;
//! if let Event::Key(key) = event {
//! println!("received {:?}", key.code);
//! }
//! }
//!
//! terminal.enter_cooked_mode()
//! }
//! ```
#[cfg(unix)]
mod unix;
#[cfg(windows)]
mod windows;
use std::{io, time::Duration};
#[cfg(unix)]
pub use unix::*;
#[cfg(windows)]
pub use windows::*;
use crate::{Event, EventReader, WindowSize};
#[cfg(doc)]
use crate::escape::csi::{DecPrivateModeCode, Keyboard};
/// The terminal implementation for the current platform.
///
/// On Unix this aliases `UnixTerminal`. On Windows this aliases `WindowsTerminal`.
#[cfg(unix)]
pub type PlatformTerminal = UnixTerminal;
#[cfg(windows)]
pub type PlatformTerminal = WindowsTerminal;
/// The output handle type passed to panic hooks on the current platform.
///
/// The hook receives this lower-level handle instead of `PlatformTerminal` so cleanup code can
/// write terminal reset sequences even while the higher-level terminal value is unwinding.
#[cfg(unix)]
pub type PlatformHandle = FileDescriptor;
#[cfg(windows)]
pub type PlatformHandle = OutputHandle;
/// Platform-agnostic terminal I/O surface.
///
/// The trait is implemented by the Unix and Windows backends and also requires [`io::Write`], so a
/// terminal value is both an output sink and an input/event source. `enter_raw_mode` and
/// `enter_cooked_mode` only manage the platform terminal mode. Application-level terminal features
/// such as alternate screen, bracketed paste, focus tracking, mouse tracking, and keyboard
/// protocol flags are CSI/OSC writes and remain the caller's responsibility. See
/// [`DecPrivateModeCode::ClearAndEnableAlternateScreen`],
/// [`DecPrivateModeCode::BracketedPaste`], [`DecPrivateModeCode::FocusTracking`],
/// [`DecPrivateModeCode::MouseTracking`], and [`Keyboard`] for the typed escape values.
///
/// `poll` and `read` use filters because the terminal stream may include responses that the caller
/// is not currently waiting for. Rejected events stay buffered in [`EventReader`] so later reads
/// can still observe them.
///
/// # Implementation Notes
///
/// This trait is based on [termwiz's terminal API], but Termina keeps feature setup outside the
/// terminal type and mirrors crossterm's synchronous event-reader shape for `poll` and `read`.
///
/// [termwiz's terminal API]: https://docs.rs/termwiz/latest/termwiz/terminal/index.html
pub trait Terminal: io::Write {
/// Enters raw mode for the platform terminal.
///
/// Raw mode disables line buffering and other terminal-driver processing, so key presses and
/// escape sequences can reach the application without waiting for Enter. Use
/// [`Self::enter_cooked_mode`] before returning control to a normal shell.
fn enter_raw_mode(&mut self) -> io::Result<()>;
/// Enters cooked mode for the platform terminal.
///
/// Cooked mode is the normal shell-facing mode for a terminal device. The terminal driver
/// handles echo, line editing, and Enter-delimited input before passing data to the
/// application. On Unix, this restores the termios state captured when the terminal was opened.
/// On Windows, this switches the console input flags back to cooked behavior, but leaves other
/// captured state, such as code pages and virtual-terminal flags, for drop-time cleanup.
fn enter_cooked_mode(&mut self) -> io::Result<()>;
/// Reads the current terminal window dimensions.
fn get_dimensions(&self) -> io::Result<WindowSize>;
/// Returns a cloneable event reader backed by the terminal input handle.
fn event_reader(&self) -> EventReader;
/// Checks if there is an [`Event`] available.
///
/// Returns `Ok(true)` if an [`Event`] is available or `Ok(false)` if one is not available.
/// If `timeout` is `None`, this blocks until a matching event is available.
fn poll<F: Fn(&Event) -> bool>(&self, filter: F, timeout: Option<Duration>)
-> io::Result<bool>;
/// Reads a single [`Event`] from the terminal.
///
/// This function blocks until an [`Event`] is available. Use [`Self::poll`] first to guarantee
/// that the read won't block.
fn read<F: Fn(&Event) -> bool>(&self, filter: F) -> io::Result<Event>;
/// Installs a panic hook that can write terminal cleanup sequences.
///
/// Depending on how your application handles panics, you may want to eagerly reset
/// application-level terminal state before the process exits. Use this hook for cleanup such as
/// disabling [`DecPrivateModeCode::BracketedPaste`] or returning from
/// [`DecPrivateModeCode::ClearAndEnableAlternateScreen`] to the main screen.
///
/// The hook receives a [`PlatformHandle`] for stdout or the platform console output. After the
/// hook runs, Termina restores the platform mode as if [`Self::enter_cooked_mode`] had run.
fn set_panic_hook(&mut self, f: impl Fn(&mut PlatformHandle) + Send + Sync + 'static);
}