| use crate::ansi::Perform; |
| use crate::cell::CellFlags; |
| use crate::color::ANSI_COLORS; |
| use crate::config::{Config, CursorStyle}; |
| use crate::damage::Damage; |
| use crate::grid::Grid; |
|
|
| pub struct Terminal { |
| pub grid: Grid, |
| pub config: Config, |
|
|
| pub cursor_style: CursorStyle, |
| pub cursor_visible: bool, |
| pub auto_wrap: bool, |
| pub insert_mode: bool, |
| pub reverse_video: bool, |
| pub origin_mode: bool, |
| pub bracketed_paste: bool, |
| pub focus_tracking: bool, |
| pub mouse_tracking: bool, |
|
|
| pub fg_color: u32, |
| pub bg_color: u32, |
| pub sgr_flags: CellFlags, |
|
|
| pub damage: Damage, |
| pub cursor_blink_phase: bool, |
| pub dirty_cursor: bool, |
| } |
|
|
| impl Terminal { |
| pub fn new(config: Config) -> Self { |
| let mut grid = Grid::new(config.cols, config.rows, config.scrollback_limit); |
| grid.clear_screen(); |
| let cfg = config.clone(); |
| Self { |
| grid, |
| config, |
| cursor_style: cfg.cursor_style, |
| cursor_visible: true, |
| auto_wrap: true, |
| insert_mode: false, |
| reverse_video: false, |
| origin_mode: false, |
| bracketed_paste: false, |
| focus_tracking: false, |
| mouse_tracking: false, |
| fg_color: cfg.foreground_color, |
| bg_color: cfg.background_color, |
| sgr_flags: CellFlags::empty(), |
| damage: Damage::Full, |
| cursor_blink_phase: true, |
| dirty_cursor: true, |
| } |
| } |
|
|
| pub fn feed(&mut self, bytes: &[u8]) { |
| let mut parser = crate::ansi::Parser::new(); |
| parser.advance(self, bytes); |
| } |
|
|
| pub fn resize(&mut self, cols: usize, rows: usize) { |
| self.grid.resize(cols, rows); |
| self.damage = Damage::Full; |
| } |
|
|
| pub fn tick_blink(&mut self) { |
| if self.config.cursor_blink { |
| self.cursor_blink_phase = !self.cursor_blink_phase; |
| self.dirty_cursor = true; |
| } |
| } |
|
|
| fn write_char(&mut self, ch: char) { |
| let row = self.grid.cursor_row; |
| let col = self.grid.cursor_col; |
|
|
| if let Some(line) = self.grid.line_mut(row) { |
| if let Some(cell) = line.cells.get_mut(col) { |
| cell.ch = ch; |
| cell.fg = self.fg_color; |
| cell.bg = self.bg_color; |
| cell.flags = self.sgr_flags; |
| line.dirty = true; |
| self.damage.add_line(row, col, col + 1); |
| } |
| } |
|
|
| if self.grid.cursor_col + 1 < self.grid.cols { |
| self.grid.cursor_col += 1; |
| } else if self.auto_wrap { |
| self.grid.cursor_col = 0; |
| if self.grid.cursor_row + 1 >= self.grid.bottom_margin + 1 { |
| self.grid.scroll_up(1); |
| } else { |
| self.grid.cursor_row += 1; |
| } |
| } |
| } |
| } |
|
|
| impl Perform for Terminal { |
| fn print(&mut self, ch: char) { |
| self.write_char(ch); |
| } |
|
|
| fn execute(&mut self, byte: u8) { |
| match byte { |
| 0x07 => { } |
| 0x08 => { |
| if self.grid.cursor_col > 0 { |
| self.grid.cursor_col -= 1; |
| } |
| } |
| 0x09 => { |
| let next_tab = ((self.grid.cursor_col / 8) + 1) * 8; |
| self.grid.cursor_col = next_tab.min(self.grid.cols - 1); |
| } |
| 0x0A | 0x0B | 0x0C => { |
| if self.grid.cursor_row + 1 >= self.grid.bottom_margin + 1 { |
| self.grid.scroll_up(1); |
| } else { |
| self.grid.cursor_row += 1; |
| } |
| } |
| 0x0D => { |
| self.grid.cursor_col = 0; |
| } |
| 0x0E => { } |
| 0x0F => { } |
| _ => {} |
| } |
| } |
|
|
| fn csi_dispatch(&mut self, params: &[u16], _intermediates: &[u8], _ignore: bool, action: char) { |
| match action { |
| 'm' => self.handle_sgr(params), |
| 'H' | 'f' => self.handle_cup(params), |
| 'J' => self.handle_ed(params), |
| 'K' => self.handle_el(params), |
| 'A' => self.handle_cuu(params), |
| 'B' => self.handle_cud(params), |
| 'C' => self.handle_cuf(params), |
| 'D' => self.handle_cub(params), |
| 'E' => self.handle_cnl(params), |
| 'F' => self.handle_cpl(params), |
| 'G' => self.handle_cha(params), |
| 'd' => self.handle_vpa(params), |
| 'n' => self.handle_dsr(params), |
| 'h' => self.handle_sm(params), |
| 'l' => self.handle_rm(params), |
| 'r' => self.handle_decstbm(params), |
| 'L' => self.handle_il(params), |
| 'M' => self.handle_dl(params), |
| 'P' => self.handle_dch(params), |
| '@' => self.handle_ich(params), |
| 'X' => self.handle_ech(params), |
| 'c' => self.handle_da(params), |
| 'g' => self.handle_tbc(params), |
| _ => {} |
| } |
| } |
|
|
| fn esc_dispatch(&mut self, _intermediates: &[u8], _ignore: bool, byte: u8) { |
| match byte { |
| b'c' => { |
| *self = Terminal::new(self.config.clone()); |
| } |
| b'7' => { |
| self.grid.saved_cursor_row = self.grid.cursor_row; |
| self.grid.saved_cursor_col = self.grid.cursor_col; |
| } |
| b'8' => { |
| self.grid.cursor_row = self.grid.saved_cursor_row; |
| self.grid.cursor_col = self.grid.saved_cursor_col; |
| } |
| b'M' => { |
| if self.grid.cursor_row == self.grid.top_margin { |
| self.grid.scroll_down(1); |
| } else if self.grid.cursor_row > 0 { |
| self.grid.cursor_row -= 1; |
| } |
| } |
| _ => {} |
| } |
| } |
|
|
| fn osc_dispatch(&mut self, params: &[&[u8]]) { |
| if params.is_empty() { |
| return; |
| } |
| let param0 = std::str::from_utf8(params[0]).unwrap_or(""); |
| match param0.parse::<u32>() { |
| Ok(0) | Ok(2) => { } |
| Ok(7) => { } |
| Ok(8) => { } |
| Ok(52) => { } |
| _ => { } |
| } |
| } |
|
|
| fn dcs_hook(&mut self, _params: &[u16], _intermediates: &[u8], _ignore: bool, _action: char) {} |
| fn dcs_put(&mut self, _byte: u8) {} |
| fn dcs_unhook(&mut self) {} |
| } |
|
|
| macro_rules! param_or { |
| ($params:expr, $idx:expr, $default:expr) => { |
| $params.get($idx).copied().filter(|&v| v != 0).unwrap_or($default) |
| }; |
| } |
|
|
| impl Terminal { |
| fn handle_sgr(&mut self, params: &[u16]) { |
| if params.is_empty() { |
| self.fg_color = self.config.foreground_color; |
| self.bg_color = self.config.background_color; |
| self.sgr_flags = CellFlags::empty(); |
| return; |
| } |
|
|
| let mut i = 0; |
| while i < params.len() { |
| match params[i] { |
| 0 => { |
| self.fg_color = self.config.foreground_color; |
| self.bg_color = self.config.background_color; |
| self.sgr_flags = CellFlags::empty(); |
| } |
| 1 => self.sgr_flags.insert(CellFlags::BOLD), |
| 3 => self.sgr_flags.insert(CellFlags::ITALIC), |
| 4 => self.sgr_flags.insert(CellFlags::UNDERLINE), |
| 5 | 6 => self.sgr_flags.insert(CellFlags::BLINK), |
| 7 => self.sgr_flags.insert(CellFlags::REVERSE), |
| 8 => self.sgr_flags.insert(CellFlags::HIDDEN), |
| 9 => self.sgr_flags.insert(CellFlags::STRIKETHROUGH), |
| 21 => self.sgr_flags.remove(CellFlags::BOLD), |
| 22 => self.sgr_flags.remove(CellFlags::BOLD), |
| 23 => self.sgr_flags.remove(CellFlags::ITALIC), |
| 24 => self.sgr_flags.remove(CellFlags::UNDERLINE), |
| 25 => self.sgr_flags.remove(CellFlags::BLINK), |
| 27 => self.sgr_flags.remove(CellFlags::REVERSE), |
| 28 => self.sgr_flags.remove(CellFlags::HIDDEN), |
| 29 => self.sgr_flags.remove(CellFlags::STRIKETHROUGH), |
| 30..=37 => { |
| let idx = (params[i] - 30) as usize; |
| self.fg_color = ANSI_COLORS[idx]; |
| } |
| 38 => { |
| if let Some(color) = self.parse_extended_color(params, &mut i) { |
| self.fg_color = color; |
| } |
| } |
| 39 => self.fg_color = self.config.foreground_color, |
| 40..=47 => { |
| let idx = (params[i] - 40) as usize; |
| self.bg_color = ANSI_COLORS[idx]; |
| } |
| 48 => { |
| if let Some(color) = self.parse_extended_color(params, &mut i) { |
| self.bg_color = color; |
| } |
| } |
| 49 => self.bg_color = self.config.background_color, |
| 90..=97 => { |
| let idx = (params[i] - 90 + 8) as usize; |
| self.fg_color = ANSI_COLORS[idx]; |
| } |
| 100..=107 => { |
| let idx = (params[i] - 100 + 8) as usize; |
| self.bg_color = ANSI_COLORS[idx]; |
| } |
| _ => {} |
| } |
| i += 1; |
| } |
| } |
|
|
| fn parse_extended_color(&self, params: &[u16], i: &mut usize) -> Option<u32> { |
| *i += 1; |
| match params.get(*i)? { |
| 2 => { |
| *i += 1; |
| let r = params.get(*i).copied().unwrap_or(0) as u32; |
| *i += 1; |
| let g = params.get(*i).copied().unwrap_or(0) as u32; |
| *i += 1; |
| let b = params.get(*i).copied().unwrap_or(0) as u32; |
| Some(0xFF_000000 | (r << 16) | (g << 8) | b) |
| } |
| 5 => { |
| *i += 1; |
| let idx = params.get(*i).copied().unwrap_or(0) as usize; |
| Some(extended_256_color(idx)) |
| } |
| _ => None, |
| } |
| } |
|
|
| fn handle_cup(&mut self, params: &[u16]) { |
| let row = param_or!(params, 0, 1).saturating_sub(1) as usize; |
| let col = param_or!(params, 1, 1).saturating_sub(1) as usize; |
| let base = if self.origin_mode { self.grid.top_margin } else { 0 }; |
| self.grid.cursor_row = (base + row).min(self.grid.rows.saturating_sub(1)); |
| self.grid.cursor_col = col.min(self.grid.cols.saturating_sub(1)); |
| } |
|
|
| fn handle_ed(&mut self, params: &[u16]) { |
| let mode = param_or!(params, 0, 0); |
| let row = self.grid.cursor_row; |
| let col = self.grid.cursor_col; |
|
|
| match mode { |
| 0 => { |
| self.grid.clear_line_right(row, col); |
| for r in (row + 1)..self.grid.rows { |
| self.grid.clear_line(r); |
| } |
| self.damage = Damage::Full; |
| } |
| 1 => { |
| self.grid.clear_line_left(row, col); |
| for r in 0..row { |
| self.grid.clear_line(r); |
| } |
| self.damage = Damage::Full; |
| } |
| 2 | 3 => { |
| self.grid.clear_screen(); |
| if mode == 3 { |
| self.grid.scrollback.clear(); |
| } |
| self.damage = Damage::Full; |
| } |
| _ => {} |
| } |
| } |
|
|
| fn handle_el(&mut self, params: &[u16]) { |
| let mode = param_or!(params, 0, 0); |
| let row = self.grid.cursor_row; |
| let col = self.grid.cursor_col; |
|
|
| match mode { |
| 0 => self.grid.clear_line_right(row, col), |
| 1 => self.grid.clear_line_left(row, col), |
| 2 => self.grid.clear_line(row), |
| _ => {} |
| } |
| } |
|
|
| fn handle_cuu(&mut self, params: &[u16]) { |
| let n = param_or!(params, 0, 1) as usize; |
| let top = if self.origin_mode { self.grid.top_margin } else { 0 }; |
| self.grid.cursor_row = self.grid.cursor_row.saturating_sub(n).max(top); |
| } |
|
|
| fn handle_cud(&mut self, params: &[u16]) { |
| let n = param_or!(params, 0, 1) as usize; |
| let bottom = if self.origin_mode { |
| self.grid.bottom_margin |
| } else { |
| self.grid.rows.saturating_sub(1) |
| }; |
| self.grid.cursor_row = (self.grid.cursor_row + n).min(bottom); |
| } |
|
|
| fn handle_cuf(&mut self, params: &[u16]) { |
| let n = param_or!(params, 0, 1) as usize; |
| self.grid.cursor_col = (self.grid.cursor_col + n).min(self.grid.cols.saturating_sub(1)); |
| } |
|
|
| fn handle_cub(&mut self, params: &[u16]) { |
| let n = param_or!(params, 0, 1) as usize; |
| self.grid.cursor_col = self.grid.cursor_col.saturating_sub(n); |
| } |
|
|
| fn handle_cnl(&mut self, params: &[u16]) { |
| let n = param_or!(params, 0, 1) as usize; |
| self.grid.cursor_col = 0; |
| self.handle_cud(&[n as u16]); |
| } |
|
|
| fn handle_cpl(&mut self, params: &[u16]) { |
| let n = param_or!(params, 0, 1) as usize; |
| self.grid.cursor_col = 0; |
| self.handle_cuu(&[n as u16]); |
| } |
|
|
| fn handle_cha(&mut self, params: &[u16]) { |
| let col = param_or!(params, 0, 1).saturating_sub(1) as usize; |
| self.grid.cursor_col = col.min(self.grid.cols.saturating_sub(1)); |
| } |
|
|
| fn handle_vpa(&mut self, params: &[u16]) { |
| let row = param_or!(params, 0, 1).saturating_sub(1) as usize; |
| let base = if self.origin_mode { self.grid.top_margin } else { 0 }; |
| self.grid.cursor_row = (base + row).min(self.grid.rows.saturating_sub(1)); |
| } |
|
|
| fn handle_dsr(&mut self, _params: &[u16]) { } |
|
|
| fn handle_sm(&mut self, params: &[u16]) { |
| for ¶m in params { |
| match param { |
| 1 => { } |
| 7 => self.auto_wrap = true, |
| 25 => self.cursor_visible = true, |
| 1049 => { } |
| 1000 => self.mouse_tracking = true, |
| 1002 => self.mouse_tracking = true, |
| 1004 => self.focus_tracking = true, |
| 2004 => self.bracketed_paste = true, |
| _ => {} |
| } |
| } |
| } |
|
|
| fn handle_rm(&mut self, params: &[u16]) { |
| for ¶m in params { |
| match param { |
| 7 => self.auto_wrap = false, |
| 25 => self.cursor_visible = false, |
| 1049 => { } |
| 1000 => self.mouse_tracking = false, |
| 1002 => self.mouse_tracking = false, |
| 1004 => self.focus_tracking = false, |
| 2004 => self.bracketed_paste = false, |
| _ => {} |
| } |
| } |
| } |
|
|
| fn handle_decstbm(&mut self, params: &[u16]) { |
| let top = param_or!(params, 0, 1).saturating_sub(1) as usize; |
| let bottom = param_or!(params, 1, self.grid.rows as u16) as usize; |
| if top < bottom && bottom <= self.grid.rows { |
| self.grid.top_margin = top; |
| self.grid.bottom_margin = bottom.saturating_sub(1); |
| self.grid.cursor_row = top; |
| self.grid.cursor_col = 0; |
| } |
| } |
|
|
| fn handle_il(&mut self, params: &[u16]) { |
| let n = param_or!(params, 0, 1) as usize; |
| let row = self.grid.cursor_row; |
| let top = self.grid.top_margin; |
| let bottom = self.grid.bottom_margin; |
| let cols = self.grid.cols; |
|
|
| for _ in 0..n { |
| if row <= bottom { |
| self.grid.lines.remove(bottom); |
| let mut new_line = crate::grid::Line::new(cols); |
| new_line.dirty = true; |
| self.grid.lines.insert(row, new_line); |
| } |
| } |
| for i in top..=bottom { |
| if let Some(line) = self.grid.lines.get_mut(i) { |
| line.dirty = true; |
| } |
| } |
| self.damage = Damage::Full; |
| } |
|
|
| fn handle_dl(&mut self, params: &[u16]) { |
| let n = param_or!(params, 0, 1) as usize; |
| let row = self.grid.cursor_row; |
| let top = self.grid.top_margin; |
| let bottom = self.grid.bottom_margin; |
| let cols = self.grid.cols; |
|
|
| for _ in 0..n { |
| if row <= bottom { |
| self.grid.lines.remove(row); |
| let mut new_line = crate::grid::Line::new(cols); |
| new_line.dirty = true; |
| self.grid.lines.insert(bottom, new_line); |
| } |
| } |
| for i in top..=bottom { |
| if let Some(line) = self.grid.lines.get_mut(i) { |
| line.dirty = true; |
| } |
| } |
| self.damage = Damage::Full; |
| } |
|
|
| fn handle_dch(&mut self, params: &[u16]) { |
| let n = param_or!(params, 0, 1) as usize; |
| let row = self.grid.cursor_row; |
| let col = self.grid.cursor_col; |
| let cols = self.grid.cols; |
| if let Some(line) = self.grid.line_mut(row) { |
| for i in col..cols.saturating_sub(n) { |
| line.cells[i] = line.cells[i + n].clone(); |
| } |
| for i in (cols.saturating_sub(n))..cols { |
| line.cells[i].reset(); |
| } |
| line.dirty = true; |
| self.damage.add_line(row, col, cols); |
| } |
| } |
|
|
| fn handle_ich(&mut self, params: &[u16]) { |
| let n = param_or!(params, 0, 1) as usize; |
| let row = self.grid.cursor_row; |
| let col = self.grid.cursor_col; |
| let cols = self.grid.cols; |
| if let Some(line) = self.grid.line_mut(row) { |
| let shift_count = (cols - col).saturating_sub(n); |
| for i in (0..shift_count).rev() { |
| line.cells[col + i + n] = line.cells[col + i].clone(); |
| } |
| let clear_end = col + n.min(cols - col); |
| for i in col..clear_end { |
| line.cells[i].reset(); |
| } |
| line.dirty = true; |
| self.damage.add_line(row, col, cols); |
| } |
| } |
|
|
| fn handle_ech(&mut self, params: &[u16]) { |
| let n = param_or!(params, 0, 1) as usize; |
| let row = self.grid.cursor_row; |
| let col = self.grid.cursor_col; |
| let end = (col + n).min(self.grid.cols); |
| if let Some(line) = self.grid.line_mut(row) { |
| for i in col..end { |
| line.cells[i].reset(); |
| } |
| line.dirty = true; |
| self.damage.add_line(row, col, end); |
| } |
| } |
|
|
| fn handle_da(&mut self, _params: &[u16]) { } |
|
|
| fn handle_tbc(&mut self, _params: &[u16]) { } |
| } |
|
|
| fn extended_256_color(idx: usize) -> u32 { |
| const TABLE: [u32; 216] = { |
| let mut table = [0u32; 216]; |
| let mut i = 0usize; |
| while i < 216 { |
| let r = (i / 36) % 6; |
| let g = (i / 6) % 6; |
| let b = i % 6; |
| let rv = if r > 0 { 55 + r * 40 } else { 0 }; |
| let gv = if g > 0 { 55 + g * 40 } else { 0 }; |
| let bv = if b > 0 { 55 + b * 40 } else { 0 }; |
| table[i] = 0xFF_000000 | ((rv as u32) << 16) | ((gv as u32) << 8) | (bv as u32); |
| i += 1; |
| } |
| table |
| }; |
|
|
| if idx < 16 { |
| ANSI_COLORS[idx] |
| } else if idx < 232 { |
| TABLE[idx - 16] |
| } else { |
| let v = 8 + (idx - 232) * 10; |
| let v = v as u32; |
| 0xFF_000000 | (v << 16) | (v << 8) | v |
| } |
| } |
|
|