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::() { 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 { *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 } }