stevenkhan's picture
Upload spectral-core/src/term.rs
05879f6 verified
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 &param 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 &param 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
}
}