aboutsummaryrefslogblamecommitdiffstats
path: root/src/lib.rs
blob: 3bfb9b1fc715c3e6811b4e7983934e1967e6ea0b (plain) (tree)
1
2
3
4
5
6
7
8
9
10









                                                                              
                    
                                         

















































                                                                             


                          
                                
                            

                                       
                                


                                         
                                  
 
                       
                 

              
                     
          
                               

                 
            
 
                         
           
                         
                                      


                         
                                 
 

                                                            
 
                                                                 
                                     

                                                                            

                                        

     

                                                                          

                                     

     
                                                        


                                                  

     
                                                                        
                                         


                                    
                                            
                                               
                             
                                
                         
                                


                         





                                                                   
                             



                                           
                             



                                           
                             



                                           
                             


         
                                    
                         


                               
                                                                      



                              
                                                                          



                                    

                                                                          
                                                    







                                          


                                          


                                              
                 
                                 












                                           

                                                                          
                                                    







                                          


                                          


                                              
                 
                                 











                                           
 

                                                                            







                                        

                                                                            







                                            

                                                                            







                                                  

                                                                              






                                              








                                                  
 
//! `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");
        }
    }
}