use ansi::{Cell, Cursor, Parser, Screen, Style, Token};
pub struct Term {
primary: Screen,
alt: Screen,
in_alt: bool,
style: Style,
scroll_top: usize,
scroll_bottom: usize,
saved: Option<(Cursor, Style)>,
tabs: Vec<bool>,
wrap_pending: bool,
autowrap: bool,
cursor_visible: bool,
parser: Parser,
dirty: bool,
in_sync: bool,
sync_frame: Option<Screen>,
}
impl Term {
pub fn new(rows: usize, cols: usize) -> Self {
let rows = rows.max(1);
let cols = cols.max(1);
Term {
primary: Screen::new(rows, cols),
alt: Screen::new(rows, cols),
in_alt: false,
style: Style::default(),
scroll_top: 0,
scroll_bottom: rows - 1,
saved: None,
tabs: default_tabs(cols),
wrap_pending: false,
autowrap: true,
cursor_visible: true,
parser: Parser::new(),
dirty: false,
in_sync: false,
sync_frame: None,
}
}
pub fn feed(&mut self, bytes: &[u8]) {
if bytes.is_empty() {
return;
}
self.dirty = true;
let tokens = self.parser.feed(bytes);
for tok in tokens {
self.apply(&tok);
}
}
pub fn take_dirty(&mut self) -> bool {
std::mem::replace(&mut self.dirty, false)
}
pub fn screen(&self) -> &Screen {
match &self.sync_frame {
Some(frame) => frame,
None => self.active(),
}
}
pub fn in_sync(&self) -> bool {
self.in_sync
}
pub fn resize(&mut self, rows: usize, cols: usize) {
let rows = rows.max(1);
let cols = cols.max(1);
let cursor = self.cursor();
let old_primary = std::mem::replace(&mut self.primary, Screen::new(rows, cols));
let old_alt = std::mem::replace(&mut self.alt, Screen::new(rows, cols));
copy_top_left(&old_primary, &mut self.primary);
copy_top_left(&old_alt, &mut self.alt);
self.scroll_top = self.scroll_top.min(rows - 1);
self.scroll_bottom = rows - 1;
self.active_mut().set_cursor(cursor);
self.tabs = default_tabs(cols);
self.wrap_pending = false;
self.in_sync = false;
self.sync_frame = None;
self.dirty = true;
}
fn active(&self) -> &Screen {
if self.in_alt {
&self.alt
} else {
&self.primary
}
}
fn active_mut(&mut self) -> &mut Screen {
if self.in_alt {
&mut self.alt
} else {
&mut self.primary
}
}
fn rows(&self) -> usize {
self.active().rows()
}
fn cols(&self) -> usize {
self.active().cols()
}
fn cursor(&self) -> Cursor {
self.active().cursor()
}
fn set_row(&mut self, row: usize) {
let col = self.cursor().col;
self.active_mut().set_cursor(Cursor { row, col });
}
fn set_col(&mut self, col: usize) {
let row = self.cursor().row;
self.active_mut().set_cursor(Cursor { row, col });
}
fn apply(&mut self, tok: &Token) {
match tok {
Token::Text(s) => {
for ch in s.chars() {
self.print(ch);
}
}
Token::Control(b) => self.control(*b),
Token::Csi {
private,
params,
final_byte,
..
} => self.csi(*private, params, *final_byte),
Token::Esc { final_byte, .. } => self.esc(*final_byte),
Token::Osc(_) | Token::Other { .. } => {}
}
}
fn print(&mut self, ch: char) {
let w = uwidth::char_width(ch) as usize;
if w == 0 {
return;
}
if self.wrap_pending && self.autowrap {
self.set_col(0);
self.line_feed();
self.wrap_pending = false;
}
if w == 2 && self.cursor().col + 1 >= self.cols() {
if !self.autowrap {
return;
}
self.set_col(0);
self.line_feed();
self.wrap_pending = false;
}
let (autowrap, style) = (self.autowrap, self.style);
let screen = if self.in_alt {
&mut self.alt
} else {
&mut self.primary
};
let cols = screen.cols();
let Cursor { row: r, col: c } = screen.cursor();
if w == 2 {
screen.set(r, c, Cell::wide(ch, style));
screen.set(r, c + 1, Cell::continuation(style));
} else {
screen.set(r, c, Cell::new(ch, style));
}
let at_edge = c + w >= cols;
let next = if at_edge { cols - 1 } else { c + w };
screen.set_cursor(Cursor { row: r, col: next });
if at_edge && autowrap {
self.wrap_pending = true;
}
}
fn control(&mut self, b: u8) {
match b {
b'\r' => {
self.set_col(0);
self.wrap_pending = false;
}
b'\n' | 0x0b | 0x0c => {
self.line_feed();
self.wrap_pending = false;
}
0x08 => {
if self.cursor().col > 0 {
self.set_col(self.cursor().col - 1);
}
self.wrap_pending = false;
}
b'\t' => {
self.set_col(self.next_tab_stop());
self.wrap_pending = false;
}
_ => {} }
}
fn line_feed(&mut self) {
if self.cursor().row == self.scroll_bottom {
self.scroll_up(1);
} else if self.cursor().row + 1 < self.rows() {
self.set_row(self.cursor().row + 1);
}
}
fn next_tab_stop(&self) -> usize {
let cols = self.cols();
let mut c = self.cursor().col + 1;
while c < cols {
if self.tabs.get(c).copied().unwrap_or(false) {
return c;
}
c += 1;
}
cols - 1
}
fn csi(&mut self, private: Option<char>, params: &[u16], final_byte: char) {
if private == Some('?') {
match final_byte {
'h' => self.set_mode(params, true),
'l' => self.set_mode(params, false),
_ => {} }
return;
}
if private.is_some() {
return; }
let n1 = |i: usize| -> usize { at1(params, i) };
match final_byte {
'H' | 'f' => {
let r = n1(0).saturating_sub(1);
let c = n1(1).saturating_sub(1);
self.move_to(r, c);
}
'A' => self.move_up(n1(0)),
'B' => self.move_down(n1(0)),
'C' => self.move_right(n1(0)),
'D' => self.move_left(n1(0)),
'E' => {
self.move_down(n1(0));
self.set_col(0);
self.wrap_pending = false;
}
'F' => {
self.move_up(n1(0));
self.set_col(0);
self.wrap_pending = false;
}
'G' => {
let c = n1(0).saturating_sub(1);
self.set_col(c.min(self.cols() - 1));
self.wrap_pending = false;
}
'd' => {
let r = n1(0).saturating_sub(1);
self.set_row(r.min(self.rows() - 1));
self.wrap_pending = false;
}
'J' => self.erase_display(at0(params, 0)),
'K' => self.erase_line(at0(params, 0)),
'X' => self.erase_chars(n1(0)),
'm' => self.style.apply_sgr(params),
'r' => self.set_scroll_region(params),
'L' => self.insert_lines(n1(0)),
'M' => self.delete_lines(n1(0)),
'S' => self.scroll_up(n1(0)),
'T' => self.scroll_down(n1(0)),
'@' => self.insert_chars(n1(0)),
'P' => self.delete_chars(n1(0)),
's' => self.save_cursor(),
'u' => self.restore_cursor(),
_ => {} }
}
fn set_mode(&mut self, params: &[u16], set: bool) {
for &p in params {
match p {
25 => self.cursor_visible = set,
7 => self.autowrap = set, 47 | 1047 | 1049 => self.set_alt(set),
2026 => self.set_sync(set), _ => {} }
}
}
fn set_alt(&mut self, enter: bool) {
let cursor = self.cursor();
if enter && !self.in_alt {
self.alt = Screen::new(self.rows(), self.cols());
self.in_alt = true;
} else if !enter && self.in_alt {
self.in_alt = false;
}
self.active_mut().set_cursor(cursor);
self.wrap_pending = false;
}
fn set_sync(&mut self, open: bool) {
if open {
if !self.in_sync {
self.sync_frame = Some(self.active().clone());
self.in_sync = true;
}
} else {
self.in_sync = false;
self.sync_frame = None;
}
}
fn move_to(&mut self, r: usize, c: usize) {
self.active_mut().set_cursor(Cursor { row: r, col: c });
self.wrap_pending = false;
}
fn move_up(&mut self, n: usize) {
self.set_row(self.cursor().row.saturating_sub(n));
self.wrap_pending = false;
}
fn move_down(&mut self, n: usize) {
self.set_row((self.cursor().row + n).min(self.rows() - 1));
self.wrap_pending = false;
}
fn move_right(&mut self, n: usize) {
self.set_col((self.cursor().col + n).min(self.cols() - 1));
self.wrap_pending = false;
}
fn move_left(&mut self, n: usize) {
self.set_col(self.cursor().col.saturating_sub(n));
self.wrap_pending = false;
}
fn erase_display(&mut self, mode: u16) {
let (rows, cols) = (self.rows(), self.cols());
let Cursor { row: cr, col: cc } = self.cursor();
let blank = Cell::default();
match mode {
0 => {
for c in cc..cols {
self.active_mut().set(cr, c, blank);
}
for r in (cr + 1)..rows {
for c in 0..cols {
self.active_mut().set(r, c, blank);
}
}
}
1 => {
for r in 0..cr {
for c in 0..cols {
self.active_mut().set(r, c, blank);
}
}
for c in 0..=cc {
self.active_mut().set(cr, c, blank);
}
}
2 => {
for r in 0..rows {
for c in 0..cols {
self.active_mut().set(r, c, blank);
}
}
}
_ => {}
}
}
fn erase_line(&mut self, mode: u16) {
let cols = self.cols();
let Cursor { row: cr, col: cc } = self.cursor();
let blank = Cell::default();
let range = match mode {
0 => cc..cols,
1 => 0..(cc + 1).min(cols),
2 => 0..cols,
_ => return,
};
for c in range {
self.active_mut().set(cr, c, blank);
}
}
fn erase_chars(&mut self, n: usize) {
let cols = self.cols();
let Cursor { row: cr, col: cc } = self.cursor();
let end = (cc + n.max(1)).min(cols);
let blank = Cell::default();
for c in cc..end {
self.active_mut().set(cr, c, blank);
}
self.wrap_pending = false;
}
fn set_scroll_region(&mut self, params: &[u16]) {
let rows = self.rows();
let top = at1(params, 0).saturating_sub(1);
let bottom = match at0(params, 1) {
0 => rows - 1,
b => (b as usize - 1).min(rows - 1),
};
if top < bottom {
self.scroll_top = top;
self.scroll_bottom = bottom;
}
self.active_mut().set_cursor(Cursor::default());
self.wrap_pending = false;
}
fn scroll_up(&mut self, n: usize) {
let (top, bottom) = (self.scroll_top, self.scroll_bottom);
self.active_mut()
.scroll_rows_up(top, bottom, n, Cell::default());
}
fn scroll_down(&mut self, n: usize) {
let (top, bottom) = (self.scroll_top, self.scroll_bottom);
self.active_mut()
.scroll_rows_down(top, bottom, n, Cell::default());
}
fn insert_lines(&mut self, n: usize) {
if self.cursor().row < self.scroll_top || self.cursor().row > self.scroll_bottom {
return;
}
let saved_top = self.scroll_top;
self.scroll_top = self.cursor().row;
self.scroll_down(n);
self.scroll_top = saved_top;
self.set_col(0);
self.wrap_pending = false;
}
fn delete_lines(&mut self, n: usize) {
if self.cursor().row < self.scroll_top || self.cursor().row > self.scroll_bottom {
return;
}
let saved_top = self.scroll_top;
self.scroll_top = self.cursor().row;
self.scroll_up(n);
self.scroll_top = saved_top;
self.set_col(0);
self.wrap_pending = false;
}
fn insert_chars(&mut self, n: usize) {
let cols = self.cols();
let Cursor { row: r, col: start } = self.cursor();
let n = n.min(cols - start);
for c in (start..cols).rev() {
let cell = if c >= start + n {
self.active().cell(r, c - n)
} else {
Cell::default()
};
self.active_mut().set(r, c, cell);
}
self.wrap_pending = false;
}
fn delete_chars(&mut self, n: usize) {
let cols = self.cols();
let Cursor { row: r, col: start } = self.cursor();
let n = n.min(cols - start);
for c in start..cols {
let cell = if c + n < cols {
self.active().cell(r, c + n)
} else {
Cell::default()
};
self.active_mut().set(r, c, cell);
}
self.wrap_pending = false;
}
fn save_cursor(&mut self) {
self.saved = Some((self.cursor(), self.style));
}
fn restore_cursor(&mut self) {
if let Some((cursor, style)) = self.saved {
self.active_mut().set_cursor(cursor);
self.style = style;
}
self.wrap_pending = false;
}
fn esc(&mut self, final_byte: u8) {
match final_byte {
b'7' => self.save_cursor(), b'8' => self.restore_cursor(), _ => {} }
}
}
fn default_tabs(cols: usize) -> Vec<bool> {
(0..cols).map(|c| c % 8 == 0).collect()
}
fn at1(params: &[u16], i: usize) -> usize {
match params.get(i).copied().unwrap_or(0) {
0 => 1,
v => v as usize,
}
}
fn at0(params: &[u16], i: usize) -> u16 {
params.get(i).copied().unwrap_or(0)
}
fn copy_top_left(src: &Screen, dst: &mut Screen) {
let rows = src.rows().min(dst.rows());
let cols = src.cols().min(dst.cols());
for r in 0..rows {
for c in 0..cols {
dst.set(r, c, src.cell(r, c));
}
}
}