/// Exports the `Term` type which is a high-level API for the Grid use std::ops::Range; use ansi::{self, Attr}; use grid::{self, Grid, CellFlags}; use tty; use ::Rgb; /// tomorrow night bright /// /// because contrast pub static COLORS: &'static [Rgb] = &[ Rgb {r: 0x00, g: 0x00, b: 0x00}, // Black Rgb {r: 0xd5, g: 0x4e, b: 0x53}, // Red Rgb {r: 0xb9, g: 0xca, b: 0x4a}, // Green Rgb {r: 0xe6, g: 0xc5, b: 0x47}, // Yellow Rgb {r: 0x7a, g: 0xa6, b: 0xda}, // Blue Rgb {r: 0xc3, g: 0x97, b: 0xd8}, // Magenta Rgb {r: 0x70, g: 0xc0, b: 0xba}, // Cyan Rgb {r: 0x42, g: 0x42, b: 0x42}, // White Rgb {r: 0x66, g: 0x66, b: 0x66}, // Bright black Rgb {r: 0xff, g: 0x33, b: 0x34}, // Bright red Rgb {r: 0x9e, g: 0xc4, b: 0x00}, // Bright green Rgb {r: 0xe7, g: 0xc5, b: 0x47}, // Bright yellow Rgb {r: 0x7a, g: 0xa6, b: 0xda}, // Bright blue Rgb {r: 0xb7, g: 0x7e, b: 0xe0}, // Bright magenta Rgb {r: 0x54, g: 0xce, b: 0xd6}, // Bright cyan Rgb {r: 0x2a, g: 0x2a, b: 0x2a}, // Bright white ]; pub mod mode { bitflags! { pub flags TermMode: u32 { const TEXT_CURSOR = 0b00000001, } } } pub use self::mode::TermMode; pub const CURSOR_SHAPE: char = '█'; pub const DEFAULT_FG: Rgb = Rgb { r: 0xea, g: 0xea, b: 0xea}; pub const DEFAULT_BG: Rgb = Rgb { r: 0, g: 0, b: 0}; pub const TAB_SPACES: usize = 8; /// State for cursor #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub struct Cursor { pub x: u16, pub y: u16, } impl Default for Cursor { fn default() -> Cursor { Cursor { x: 0, y: 0 } } } impl Cursor { pub fn goto(&mut self, x: u16, y: u16) { self.x = x; self.y = y; } pub fn advance(&mut self, rows: i64, cols: i64) { self.x = (self.x as i64 + cols) as u16; self.y = (self.y as i64 + rows) as u16; } } pub struct Term { /// The grid grid: Grid, /// Alternate grid alt_grid: Grid, /// Alt is active alt: bool, /// Reference to the underlying tty _tty: tty::Tty, /// The cursor cursor: Cursor, /// Alt cursor alt_cursor: Cursor, /// Active foreground color fg: Rgb, /// Active background color bg: Rgb, /// Tabstops tabs: Vec, /// Cell attributes attr: grid::CellFlags, /// Mode flags mode: TermMode, /// Scroll region scroll_region: Range, } impl Term { pub fn new(tty: tty::Tty, grid: Grid) -> Term { let mut tabs = (0..grid.num_cols()).map(|i| i % TAB_SPACES == 0) .collect::>(); tabs[0] = false; let alt = grid.clone(); let scroll_region = 0..grid.num_rows(); Term { grid: grid, alt_grid: alt, alt: false, cursor: Cursor::default(), alt_cursor: Cursor::default(), fg: DEFAULT_FG, bg: DEFAULT_BG, _tty: tty, tabs: tabs, attr: CellFlags::empty(), mode: TermMode::empty(), scroll_region: scroll_region, } } pub fn grid(&self) -> &Grid { &self.grid } #[inline] pub fn mode(&self) -> &TermMode { &self.mode } pub fn swap_alt(&mut self) { self.alt = !self.alt; ::std::mem::swap(&mut self.grid, &mut self.alt_grid); ::std::mem::swap(&mut self.cursor, &mut self.alt_cursor); if self.alt { self.grid.clear(); } } #[inline] pub fn cursor_x(&self) -> u16 { self.cursor.x } #[inline] pub fn cursor_y(&self) -> u16 { self.cursor.y } #[inline] pub fn cursor(&self) -> Cursor { self.cursor } /// Set character in current cursor position fn set_char(&mut self, c: char) { if self.cursor.x == self.grid.num_cols() as u16 { println!("wrapping"); self.cursor.y += 1; self.cursor.x = 0; } if self.cursor.y == self.grid.num_rows() as u16 { panic!("cursor fell off grid"); } let cell = &mut self.grid[self.cursor]; cell.c = c; cell.fg = self.fg; cell.bg = self.bg; cell.flags = self.attr; } /// Convenience function for scrolling fn scroll(&mut self, count: isize) { println!("[TERM] scrolling {} lines", count); self.grid.scroll(self.scroll_region.clone(), count); if count > 0 { // Scrolled down, so need to clear from bottom let start = self.scroll_region.end - (count as usize); self.grid.clear_region(start..self.scroll_region.end); } else { // Scrolled up, clear from top let end = self.scroll_region.start + ((-count) as usize); self.grid.clear_region(self.scroll_region.start..end); } } } impl ansi::TermInfo for Term { #[inline] fn rows(&self) -> usize { self.grid.num_rows() } #[inline] fn cols(&self) -> usize { self.grid.num_cols() } } impl ansi::Handler for Term { /// A character to be displayed #[inline] fn input(&mut self, c: char) { self.set_char(c); self.cursor.x += 1; } fn goto(&mut self, x: i64, y: i64) { println!("goto: x={}, y={}", x, y); self.cursor.goto(x as u16, y as u16); } fn goto_row(&mut self, y: i64) { println!("goto_row: {}", y); let x = self.cursor_x(); self.cursor.goto(x, y as u16); } fn goto_col(&mut self, x: i64) { println!("goto_col: {}", x); let y = self.cursor_y(); self.cursor.goto(x as u16, y); } fn insert_blank(&mut self, num: i64) { println!("insert_blank: {}", num); } fn move_up(&mut self, rows: i64) { println!("move_up: {}", rows); self.cursor.advance(-rows, 0); } fn move_down(&mut self, rows: i64) { println!("move_down: {}", rows); self.cursor.advance(rows, 0); } fn move_forward(&mut self, cols: i64) { println!("move_forward: {}", cols); self.cursor.advance(0, cols); } fn move_backward(&mut self, spaces: i64) { println!("move_backward: {}", spaces); self.cursor.advance(0, -spaces); } fn identify_terminal(&mut self) { println!("identify_terminal"); } fn move_down_and_cr(&mut self, rows: i64) { println!("move_down_and_cr: {}", rows); } fn move_up_and_cr(&mut self, rows: i64) { println!("move_up_and_cr: {}", rows); } fn put_tab(&mut self, mut count: i64) { println!("put_tab: {}", count); let mut x = self.cursor_x(); while x < self.grid.num_cols() as u16 && count != 0 { count -= 1; loop { if x == self.grid.num_cols() as u16 || self.tabs[x as usize] { break; } x += 1; } } self.cursor.x = x; } /// Backspace `count` characters #[inline] fn backspace(&mut self) { println!("backspace"); self.cursor.x -= 1; } /// Carriage return #[inline] fn carriage_return(&mut self) { println!("carriage_return"); self.cursor.x = 0; } /// Linefeed #[inline] fn linefeed(&mut self) { println!("linefeed"); // TODO handle scroll? not clear what parts of this the pty handle if self.cursor_y() + 1 >= self.scroll_region.end as u16 { self.scroll(1); self.clear_line(ansi::LineClearMode::Right); } else { self.cursor.y += 1; } } /// Set current position as a tabstop fn bell(&mut self) { println!("bell"); } fn substitute(&mut self) { println!("substitute"); } fn newline(&mut self) { println!("newline"); } fn set_horizontal_tabstop(&mut self) { println!("set_horizontal_tabstop"); } fn scroll_up(&mut self, rows: i64) { println!("scroll_up: {}", rows); self.scroll(-rows as isize); } fn scroll_down(&mut self, rows: i64) { println!("scroll_down: {}", rows); self.scroll(rows as isize); } fn insert_blank_lines(&mut self, count: i64) { println!("insert_blank_lines: {}", count); if self.scroll_region.contains(self.cursor_y() as usize) { self.scroll(-count as isize); } } fn delete_lines(&mut self, count: i64) { if self.scroll_region.contains(self.cursor_y() as usize) { self.scroll(count as isize); } } fn erase_chars(&mut self, count: i64) { println!("erase_chars: {}", count); } fn delete_chars(&mut self, count: i64) { println!("delete_chars: {}", count); } fn move_backward_tabs(&mut self, count: i64) { println!("move_backward_tabs: {}", count); } fn move_forward_tabs(&mut self, count: i64) { println!("move_forward_tabs: {}", count); } fn save_cursor_position(&mut self) { println!("save_cursor_position"); } fn restore_cursor_position(&mut self) { println!("restore_cursor_position"); } fn clear_line(&mut self, mode: ansi::LineClearMode) { println!("clear_line: {:?}", mode); match mode { ansi::LineClearMode::Right => { let row = &mut self.grid[self.cursor.y as usize]; let start = self.cursor.x as usize; for cell in row[start..].iter_mut() { cell.reset(); } }, _ => (), } } fn clear_screen(&mut self, mode: ansi::ClearMode) { println!("clear_screen: {:?}", mode); match mode { ansi::ClearMode::Below => { let start = self.cursor_y() as usize; let end = self.grid.num_rows(); for i in start..end { let row = &mut self.grid[i]; for cell in row.iter_mut() { cell.c = ' '; } } }, ansi::ClearMode::All => { self.grid.clear(); }, _ => { panic!("ansi::ClearMode::Above not implemented"); } } } fn clear_tabs(&mut self, mode: ansi::TabulationClearMode) { println!("clear_tabs: {:?}", mode); } fn reset_state(&mut self) { println!("reset_state"); } fn reverse_index(&mut self) { println!("reverse_index"); // if cursor is at the top if self.cursor.y == 0 { self.scroll(-1); } else { // can't wait for nonlexical lifetimes.. omg borrowck let x = self.cursor.x; let y = self.cursor.y; self.cursor.goto(x, y - 1); } } /// set a terminal attribute fn terminal_attribute(&mut self, attr: Attr) { match attr { Attr::DefaultForeground => { self.fg = DEFAULT_FG; }, Attr::DefaultBackground => { self.bg = DEFAULT_BG; }, Attr::Foreground(named_color) => { self.fg = COLORS[named_color as usize]; }, Attr::Background(named_color) => { self.bg = COLORS[named_color as usize]; }, Attr::ForegroundSpec(rgb) => { self.fg = rgb; }, Attr::BackgroundSpec(rgb) => { self.bg = rgb; }, Attr::Reset => { self.fg = DEFAULT_FG; self.bg = DEFAULT_BG; self.attr = CellFlags::empty(); }, Attr::Reverse => self.attr.insert(grid::INVERSE), Attr::CancelReverse => self.attr.remove(grid::INVERSE), Attr::Bold => self.attr.insert(grid::BOLD), Attr::CancelBoldDim => self.attr.remove(grid::BOLD), Attr::Italic => self.attr.insert(grid::ITALIC), Attr::CancelItalic => self.attr.remove(grid::ITALIC), Attr::Underscore => self.attr.insert(grid::UNDERLINE), Attr::CancelUnderline => self.attr.remove(grid::UNDERLINE), _ => { println!("Term got unhandled attr: {:?}", attr); } } } fn set_mode(&mut self, mode: ansi::Mode) { println!("set_mode: {:?}", mode); match mode { ansi::Mode::SwapScreenAndSetRestoreCursor => self.swap_alt(), ansi::Mode::TextCursor => self.mode.insert(mode::TEXT_CURSOR), _ => { println!(".. ignoring set_mode"); } } } fn unset_mode(&mut self,mode: ansi::Mode) { println!("unset_mode: {:?}", mode); match mode { ansi::Mode::SwapScreenAndSetRestoreCursor => self.swap_alt(), ansi::Mode::TextCursor => self.mode.remove(mode::TEXT_CURSOR), _ => { println!(".. ignoring unset_mode"); } } } fn set_scrolling_region(&mut self, top: i64, bot: i64) { println!("set scroll region: {:?} - {:?}", top, bot); // 1 is added to bottom for inclusive range self.scroll_region = (top as usize)..((bot as usize) + 1); } }