//! `textmode` is a library for terminal interaction built on top of a real //! terminal parsing library. It allows you to do arbitrary drawing operations //! on an in-memory screen, and then update the visible terminal output to //! reflect the in-memory screen via an optimized diff algorithm when you are //! finished. Being built on a real terminal parsing library means that while //! normal curses-like operations are available: //! //! ```no_run //! use textmode::Textmode; //! # #[cfg(feature = "async")] //! # #[tokio::main] //! # fn main() -> textmode::Result<()> { //! let mut tm = textmode::Output::new().await?; //! tm.clear(); //! tm.move_to(5, 5); //! tm.set_fgcolor(textmode::color::RED); //! tm.write_str("foo"); //! tm.refresh().await?; //! # Ok(()) //! # } //! # #[cfg(not(feature = "async"))] //! # fn main() -> textmode::Result<()> { //! # let mut tm = textmode::blocking::Output::new()?; //! # tm.clear(); //! # tm.move_to(5, 5); //! # tm.set_fgcolor(textmode::color::RED); //! # tm.write_str("foo"); //! # tm.refresh()?; //! # Ok(()) //! # } //! ``` //! //! you can also write data containing arbitrary terminal escape codes to the //! output and they will also do the right thing: //! //! ```no_run //! # use textmode::Textmode; //! # #[cfg(feature = "async")] //! # fn main() -> textmode::Result<()> { //! # futures_lite::future::block_on(async { run().await }) //! # } //! # #[cfg(feature = "async")] //! # async fn run() -> textmode::Result<()> { //! # let mut tm = textmode::Output::new().await?; //! tm.write(b"\x1b[34m\x1b[3;9Hbar\x1b[m"); //! tm.refresh().await?; //! # Ok(()) //! # } //! # #[cfg(not(feature = "async"))] //! # fn main() -> textmode::Result<()> { //! # let mut tm = textmode::blocking::Output::new()?; //! # tm.write(b"\x1b[34m\x1b[3;9Hbar\x1b[m"); //! # tm.refresh()?; //! # Ok(()) //! # } //! ``` //! //! This module is split into two main parts: [`Output`](Output) and //! [`Input`](Input). See the documentation for those types for more details. //! Additionally, the [`blocking`] module provides an equivalent interface //! with blocking calls instead of async. #![warn(clippy::cargo)] #![warn(clippy::pedantic)] #![warn(clippy::nursery)] #![warn(clippy::as_conversions)] #![warn(clippy::get_unwrap)] #![allow(clippy::cognitive_complexity)] #![allow(clippy::missing_const_for_fn)] #![allow(clippy::similar_names)] #![allow(clippy::struct_excessive_bools)] #![allow(clippy::too_many_arguments)] #![allow(clippy::too_many_lines)] #![allow(clippy::type_complexity)] /// Blocking interface. pub mod blocking; pub mod color; pub use vt100::Color; mod error; pub use error::{Error, Result}; mod key; pub use key::Key; mod private; #[cfg(feature = "async")] mod output; #[cfg(feature = "async")] pub use output::{Output, ScreenGuard}; #[cfg(feature = "async")] mod input; #[cfg(feature = "async")] pub use input::{Input, RawGuard}; const INIT: &[u8] = b"\x1b7\x1b[?47h\x1b[2J\x1b[H\x1b[?25h"; const DEINIT: &[u8] = b"\x1b[?47l\x1b8\x1b[?25h"; /// Provides the methods used to manipulate the in-memory screen. pub trait Textmode: private::Output { /// Returns the in-memory screen itself. This is the screen that will be /// drawn on the next call to `refresh`. fn screen(&self) -> &vt100::Screen { self.next().screen() } /// Writes a sequence of bytes, potentially containing terminal escape /// sequences, to the in-memory screen. fn write(&mut self, buf: &[u8]) { self.next_mut().process(buf); } /// Sets the terminal size for the in-memory screen. fn set_size(&mut self, rows: u16, cols: u16) { self.cur_mut().set_size(rows, cols); self.next_mut().set_size(rows, cols); } /// Writes a string of printable characters to the in-memory screen. fn write_str(&mut self, text: &str) { self.write(text.as_bytes()); } /// Moves the in-memory screen's cursor. fn move_to(&mut self, row: u16, col: u16) { self.write(b"\x1b["); self.write_u16(row + 1); self.write(b";"); self.write_u16(col + 1); self.write(b"H"); } fn move_relative(&mut self, row_offset: i16, col_offset: i16) { let abs_row_offset = row_offset.unsigned_abs(); let abs_col_offset = col_offset.unsigned_abs(); if row_offset > 0 { self.write(b"\x1b["); self.write_u16(abs_row_offset); self.write(b"B"); } if row_offset < 0 { self.write(b"\x1b["); self.write_u16(abs_row_offset); self.write(b"A"); } if col_offset > 0 { self.write(b"\x1b["); self.write_u16(abs_col_offset); self.write(b"C"); } if col_offset < 0 { self.write(b"\x1b["); self.write_u16(abs_col_offset); self.write(b"D"); } } /// Clears the in-memory screen. fn clear(&mut self) { self.write(b"\x1b[2J"); } /// Clears the line containing the cursor on the in-memory screen. fn clear_line(&mut self) { self.write(b"\x1b[K"); } /// Clears the in-memory screen's currently active drawing attributes. fn reset_attributes(&mut self) { self.write(b"\x1b[m"); } /// Sets the foreground color for subsequent drawing operations to the /// in-memory screen. fn set_fgcolor(&mut self, color: vt100::Color) { match color { vt100::Color::Default => { self.write(b"\x1b[39m"); } vt100::Color::Idx(i) => { if i < 8 { self.write(b"\x1b["); self.write_u8(30 + i); } else if i < 16 { self.write(b"\x1b["); self.write_u8(82 + i); } else { self.write(b"\x1b[38;5;"); self.write_u8(i); } self.write(b"m"); } vt100::Color::Rgb(r, g, b) => { self.write(b"\x1b[38;2;"); self.write_u8(r); self.write(b";"); self.write_u8(g); self.write(b";"); self.write_u8(b); self.write(b"m"); } } } /// Sets the background color for subsequent drawing operations to the /// in-memory screen. fn set_bgcolor(&mut self, color: vt100::Color) { match color { vt100::Color::Default => { self.write(b"\x1b[49m"); } vt100::Color::Idx(i) => { if i < 8 { self.write(b"\x1b["); self.write_u8(40 + i); } else if i < 16 { self.write(b"\x1b["); self.write_u8(92 + i); } else { self.write(b"\x1b[48;5;"); self.write_u8(i); } self.write(b"m"); } vt100::Color::Rgb(r, g, b) => { self.write(b"\x1b[48;2;"); self.write_u8(r); self.write(b";"); self.write_u8(g); self.write(b";"); self.write_u8(b); self.write(b"m"); } } } /// Sets whether subsequent text drawn to the in-memory screen should be /// bold. fn set_bold(&mut self, bold: bool) { if bold { self.write(b"\x1b[1m"); } else { self.write(b"\x1b[22m"); } } /// Sets whether subsequent text drawn to the in-memory screen should be /// italic. fn set_italic(&mut self, italic: bool) { if italic { self.write(b"\x1b[3m"); } else { self.write(b"\x1b[23m"); } } /// Sets whether subsequent text drawn to the in-memory screen should be /// underlined. fn set_underline(&mut self, underline: bool) { if underline { self.write(b"\x1b[4m"); } else { self.write(b"\x1b[24m"); } } /// Sets whether subsequent text drawn to the in-memory screen should have /// its colors inverted. fn set_inverse(&mut self, inverse: bool) { if inverse { self.write(b"\x1b[7m"); } else { self.write(b"\x1b[27m"); } } /// Sets whether the cursor should be visible. fn hide_cursor(&mut self, hide: bool) { if hide { self.write(b"\x1b[?25l"); } else { self.write(b"\x1b[?25h"); } } }