mod cursor;
mod dirty_lines;
pub use self::cursor::Cursor;
use self::dirty_lines::DirtyLines;
use crate::buffer::{Buffer, EraseMode};
use crate::cell::{Cell, Occupancy};
use crate::charset::Charset;
use crate::line::Line;
use crate::parser::{
AnsiMode, AnsiModes, CtcOp, DecMode, DecModes, EdScope, ElScope, Function, SgrOp, SgrOps,
TbcScope, XtwinopsOp,
};
use crate::pen::{Intensity, Pen};
use crate::tabs::Tabs;
use std::cmp::Ordering;
use std::mem;
#[derive(Debug)]
pub struct Terminal {
cols: usize,
rows: usize,
buffer: Buffer,
other_buffer: Buffer,
active_buffer_type: BufferType,
scrollback_limit: Option<usize>,
cursor: Cursor,
pen: Pen,
charsets: [Charset; 2],
active_charset: usize,
tabs: Tabs,
insert_mode: bool,
origin_mode: bool,
auto_wrap_mode: bool,
new_line_mode: bool,
cursor_keys_mode: CursorKeysMode,
top_margin: usize,
bottom_margin: usize,
saved_ctx: SavedCtx,
alternate_saved_ctx: SavedCtx,
dirty_lines: DirtyLines,
xtwinops: bool,
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum BufferType {
Primary,
Alternate,
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub(crate) enum CursorKeysMode {
Normal,
Application,
}
#[derive(Debug, PartialEq)]
pub(crate) struct SavedCtx {
pub cursor_col: usize,
pub cursor_row: usize,
pub pen: Pen,
pub origin_mode: bool,
pub auto_wrap_mode: bool,
}
impl Default for SavedCtx {
fn default() -> Self {
SavedCtx {
cursor_col: 0,
cursor_row: 0,
pen: Pen::default(),
origin_mode: false,
auto_wrap_mode: true,
}
}
}
impl SavedCtx {
fn is_default(&self) -> bool {
self.cursor_col == 0
&& self.cursor_row == 0
&& self.pen.is_default()
&& !self.origin_mode
&& self.auto_wrap_mode
}
}
enum PrintResult {
Printed(usize),
NeedsRelocation,
}
impl Terminal {
pub fn new((cols, rows): (usize, usize), scrollback_limit: Option<usize>) -> Self {
let primary_buffer = Buffer::new(cols, rows, scrollback_limit, None);
let alternate_buffer = Buffer::new(cols, rows, Some(0), None);
let dirty_lines = DirtyLines::new(rows);
Terminal {
cols,
rows,
buffer: primary_buffer,
other_buffer: alternate_buffer,
active_buffer_type: BufferType::Primary,
scrollback_limit,
tabs: Tabs::new(cols),
cursor: Cursor::default(),
pen: Pen::default(),
charsets: [Charset::Ascii, Charset::Ascii],
active_charset: 0,
insert_mode: false,
origin_mode: false,
auto_wrap_mode: true,
new_line_mode: false,
cursor_keys_mode: CursorKeysMode::Normal,
top_margin: 0,
bottom_margin: (rows - 1),
saved_ctx: SavedCtx::default(),
alternate_saved_ctx: SavedCtx::default(),
dirty_lines,
xtwinops: false,
}
}
pub fn size(&self) -> (usize, usize) {
(self.cols, self.rows)
}
pub fn active_buffer_type(&self) -> BufferType {
self.active_buffer_type
}
pub fn execute(&mut self, fun: Function) {
use Function::*;
match fun {
Bs => {
self.bs();
}
Cbt(n) => {
self.cbt(n);
}
Cha(n) => {
self.cha(n);
}
Cht(n) => {
self.cht(n);
}
Cnl(n) => {
self.cnl(n);
}
Cpl(n) => {
self.cpl(n);
}
Cr => {
self.cr();
}
Ctc(mode) => {
self.ctc(mode);
}
Cub(n) => {
self.cub(n);
}
Cud(n) => {
self.cud(n);
}
Cuf(n) => {
self.cuf(n);
}
Cup(row, col) => {
self.cup(row, col);
}
Cuu(n) => {
self.cuu(n);
}
Dch(n) => {
self.dch(n);
}
Decaln => {
self.decaln();
}
Decrc => {
self.rc();
}
Decrst(modes) => {
self.decrst(modes);
}
Decsc => {
self.sc();
}
Decset(modes) => {
self.decset(modes);
}
Decstbm(top, bottom) => {
self.decstbm(top, bottom);
}
Decstr => {
self.decstr();
}
Dl(n) => {
self.dl(n);
}
Ech(n) => {
self.ech(n);
}
Ed(mode) => {
self.ed(mode);
}
El(mode) => {
self.el(mode);
}
G1d4(charset) => {
self.g1d4(charset);
}
Gzd4(charset) => {
self.gzd4(charset);
}
Ht => {
self.ht();
}
Hts => {
self.hts();
}
Ich(n) => {
self.ich(n);
}
Il(n) => {
self.il(n);
}
Lf => {
self.lf();
}
Nel => {
self.nel();
}
Print(ch) => {
self.print(ch);
}
Rep(n) => {
self.rep(n);
}
Ri => {
self.ri();
}
Ris => {
self.ris();
}
Rm(modes) => {
self.rm(modes);
}
Scorc => {
self.rc();
}
Scosc => {
self.sc();
}
Sd(n) => {
self.sd(n);
}
Sgr(params) => {
self.sgr(params);
}
Si => {
self.si();
}
Sm(modes) => {
self.sm(modes);
}
So => {
self.so();
}
Su(n) => {
self.su(n);
}
Tbc(mode) => {
self.tbc(mode);
}
Vpa(n) => {
self.vpa(n);
}
Vpr(n) => {
self.vpr(n);
}
Xtwinops(op) => {
self.xtwinops(op);
}
}
}
pub fn cursor(&self) -> Cursor {
self.cursor
}
pub fn gc(&mut self) -> Box<dyn Iterator<Item = Line> + '_> {
let lines = self.buffer.gc();
if self.active_buffer_type == BufferType::Alternate {
return Box::new(std::iter::empty());
}
match lines {
Some(iter) => Box::new(iter),
None => Box::new(std::iter::empty()),
}
}
pub fn changes(&mut self) -> Vec<usize> {
let changes = self.dirty_lines.to_vec();
self.dirty_lines.clear();
changes
}
fn save_cursor(&mut self) {
self.saved_ctx.cursor_col = self.cursor.col.min(self.cols - 1);
self.saved_ctx.cursor_row = self.cursor.row;
self.saved_ctx.pen = self.pen;
self.saved_ctx.origin_mode = self.origin_mode;
self.saved_ctx.auto_wrap_mode = self.auto_wrap_mode;
}
fn restore_cursor(&mut self) {
self.cursor.col = self.saved_ctx.cursor_col;
self.cursor.row = self.saved_ctx.cursor_row;
self.pen = self.saved_ctx.pen;
self.origin_mode = self.saved_ctx.origin_mode;
self.auto_wrap_mode = self.saved_ctx.auto_wrap_mode;
}
fn move_cursor_to_col(&mut self, col: usize) {
if col >= self.cols {
self.cursor.col = self.cols - 1;
} else {
self.cursor.col = col;
}
}
fn move_cursor_to_row(&mut self, mut row: usize) {
let top = self.actual_top_margin();
let bottom = self.actual_bottom_margin();
row = (top + row).max(top).min(bottom);
self.do_move_cursor_to_row(row);
}
fn do_move_cursor_to_row(&mut self, row: usize) {
self.cursor.col = self.cursor.col.min(self.cols - 1);
self.cursor.row = row;
}
fn move_cursor_to_rel_col(&mut self, rel_col: isize) {
let new_col = self.cursor.col as isize + rel_col;
if new_col < 0 {
self.cursor.col = 0;
} else if new_col as usize >= self.cols {
self.cursor.col = self.cols - 1;
} else {
self.cursor.col = new_col as usize;
}
}
fn move_cursor_home(&mut self) {
self.cursor.col = 0;
self.do_move_cursor_to_row(self.actual_top_margin());
}
fn move_cursor_to_next_tab(&mut self, n: usize) {
let next_tab = self.tabs.after(self.cursor.col, n).unwrap_or(self.cols - 1);
self.move_cursor_to_col(next_tab);
}
fn move_cursor_to_prev_tab(&mut self, n: usize) {
let prev_tab = self.tabs.before(self.cursor.col, n).unwrap_or(0);
self.move_cursor_to_col(prev_tab);
}
fn move_cursor_down_with_scroll(&mut self) {
if self.cursor.row == self.bottom_margin {
self.scroll_up_in_region(1);
} else if self.cursor.row < self.rows - 1 {
self.do_move_cursor_to_row(self.cursor.row + 1);
}
}
fn cursor_down(&mut self, n: usize) {
let new_y = if self.cursor.row > self.bottom_margin {
(self.rows - 1).min(self.cursor.row + n)
} else {
self.bottom_margin.min(self.cursor.row + n)
};
self.do_move_cursor_to_row(new_y);
}
fn cursor_up(&mut self, n: usize) {
let mut new_y = (self.cursor.row as isize) - (n as isize);
new_y = if self.cursor.row < self.top_margin {
new_y.max(0)
} else {
new_y.max(self.top_margin as isize)
};
self.do_move_cursor_to_row(new_y as usize);
}
fn actual_top_margin(&self) -> usize {
match self.origin_mode {
false => 0,
true => self.top_margin,
}
}
fn actual_bottom_margin(&self) -> usize {
match self.origin_mode {
false => self.rows - 1,
true => self.bottom_margin,
}
}
fn scroll_up_in_region(&mut self, n: usize) {
let range = self.top_margin..self.bottom_margin + 1;
self.buffer.scroll_up(range.clone(), n, &self.pen);
self.dirty_lines.extend(range);
}
fn scroll_down_in_region(&mut self, n: usize) {
let range = self.top_margin..self.bottom_margin + 1;
self.buffer.scroll_down(range.clone(), n, &self.pen);
self.dirty_lines.extend(range);
}
fn set_tab(&mut self) {
if 0 < self.cursor.col && self.cursor.col < self.cols {
self.tabs.set(self.cursor.col);
}
}
fn clear_tab(&mut self) {
self.tabs.unset(self.cursor.col);
}
fn clear_all_tabs(&mut self) {
self.tabs.clear();
}
fn switch_to_alternate_buffer(&mut self) {
if let BufferType::Primary = self.active_buffer_type {
self.active_buffer_type = BufferType::Alternate;
mem::swap(&mut self.saved_ctx, &mut self.alternate_saved_ctx);
mem::swap(&mut self.buffer, &mut self.other_buffer);
self.buffer = Buffer::new(self.cols, self.rows, Some(0), Some(&self.pen));
self.dirty_lines.extend(0..self.rows);
}
}
fn switch_to_primary_buffer(&mut self) {
if let BufferType::Alternate = self.active_buffer_type {
self.active_buffer_type = BufferType::Primary;
mem::swap(&mut self.saved_ctx, &mut self.alternate_saved_ctx);
mem::swap(&mut self.buffer, &mut self.other_buffer);
self.dirty_lines.extend(0..self.rows);
}
}
pub fn resize(&mut self, cols: usize, rows: usize) -> bool {
let mut resized: bool = false;
match cols.cmp(&self.cols) {
std::cmp::Ordering::Less => {
self.tabs.contract(cols);
resized = true;
}
std::cmp::Ordering::Equal => {}
std::cmp::Ordering::Greater => {
self.tabs.expand(self.cols, cols);
resized = true;
}
}
match rows.cmp(&self.rows) {
std::cmp::Ordering::Less => {
self.top_margin = 0;
self.bottom_margin = rows - 1;
resized = true;
}
std::cmp::Ordering::Equal => {}
std::cmp::Ordering::Greater => {
self.top_margin = 0;
self.bottom_margin = rows - 1;
resized = true;
}
}
self.cols = cols;
self.rows = rows;
self.reflow();
resized
}
fn reflow(&mut self) {
(self.cursor.col, self.cursor.row) =
self.buffer
.resize(self.cols, self.rows, (self.cursor.col, self.cursor.row));
self.dirty_lines.resize(self.rows);
self.dirty_lines.extend(0..self.rows);
if self.saved_ctx.cursor_col >= self.cols {
self.saved_ctx.cursor_col = self.cols - 1;
}
if self.saved_ctx.cursor_row >= self.rows {
self.saved_ctx.cursor_row = self.rows - 1;
}
}
fn soft_reset(&mut self) {
self.cursor.visible = true;
self.top_margin = 0;
self.bottom_margin = self.rows - 1;
self.insert_mode = false;
self.origin_mode = false;
self.pen = Pen::default();
self.charsets = [Charset::Ascii, Charset::Ascii];
self.active_charset = 0;
self.saved_ctx = SavedCtx::default();
}
fn hard_reset(&mut self) {
let primary_buffer = Buffer::new(self.cols, self.rows, self.scrollback_limit, None);
let alternate_buffer = Buffer::new(self.cols, self.rows, Some(0), None);
self.buffer = primary_buffer;
self.other_buffer = alternate_buffer;
self.active_buffer_type = BufferType::Primary;
self.tabs = Tabs::new(self.cols);
self.cursor = Cursor::default();
self.pen = Pen::default();
self.charsets = [Charset::Ascii, Charset::Ascii];
self.active_charset = 0;
self.insert_mode = false;
self.origin_mode = false;
self.auto_wrap_mode = true;
self.new_line_mode = false;
self.cursor_keys_mode = CursorKeysMode::Normal;
self.top_margin = 0;
self.bottom_margin = self.rows - 1;
self.saved_ctx = SavedCtx::default();
self.alternate_saved_ctx = SavedCtx::default();
self.dirty_lines = DirtyLines::new(self.rows);
}
fn primary_buffer(&self) -> &Buffer {
if self.active_buffer_type == BufferType::Primary {
&self.buffer
} else {
&self.other_buffer
}
}
fn alternate_buffer(&self) -> &Buffer {
if self.active_buffer_type == BufferType::Alternate {
&self.buffer
} else {
&self.other_buffer
}
}
pub fn view(&self) -> impl Iterator<Item = &Line> {
self.buffer.view()
}
pub fn lines(&self) -> impl Iterator<Item = &Line> {
self.buffer.lines()
}
pub fn line(&self, n: usize) -> &Line {
&self.buffer[n]
}
pub fn text(&self) -> Vec<String> {
self.primary_buffer().text()
}
pub fn cursor_keys_app_mode(&self) -> bool {
self.cursor_keys_mode == CursorKeysMode::Application
}
#[cfg(test)]
pub fn verify(&self) {
assert!(self.cursor.row < self.rows);
assert!(self.cursor.col <= self.cols);
assert!(self.lines().all(|line| line.len() == self.cols));
assert!(!self.lines().last().unwrap().wrapped);
for line in self.lines() {
for i in 0..self.cols {
let occupancy = line[i].occupancy();
if occupancy == Occupancy::WideTail {
assert!(i > 0);
assert!(line[i - 1].occupancy() == Occupancy::WideHead);
} else if occupancy == Occupancy::WideHead {
assert!(line[i + 1].occupancy() == Occupancy::WideTail, "{:?}", line);
}
}
}
}
#[cfg(test)]
pub fn assert_eq(&self, other: &Terminal) {
assert_eq!(self.active_buffer_type, other.active_buffer_type);
assert_eq!(self.cursor, other.cursor);
assert_eq!(self.pen, other.pen);
assert_eq!(self.charsets, other.charsets);
assert_eq!(self.active_charset, other.active_charset);
assert_eq!(self.tabs, other.tabs);
assert_eq!(self.insert_mode, other.insert_mode);
assert_eq!(self.origin_mode, other.origin_mode);
assert_eq!(self.auto_wrap_mode, other.auto_wrap_mode);
assert_eq!(self.new_line_mode, other.new_line_mode);
assert_eq!(self.cursor_keys_mode, other.cursor_keys_mode);
assert_eq!(self.top_margin, other.top_margin);
assert_eq!(self.bottom_margin, other.bottom_margin);
assert_eq!(self.saved_ctx, other.saved_ctx);
assert_eq!(self.alternate_saved_ctx, other.alternate_saved_ctx);
assert_eq!(
self.primary_buffer().view().collect::<Vec<_>>(),
other.primary_buffer().view().collect::<Vec<_>>()
);
if self.active_buffer_type == BufferType::Alternate {
assert_eq!(
self.alternate_buffer().view().collect::<Vec<_>>(),
other.alternate_buffer().view().collect::<Vec<_>>()
);
}
}
fn print(&mut self, mut ch: char) {
ch = self.charsets[self.active_charset].translate(ch);
match self.try_print(ch) {
PrintResult::Printed(width) => self.advance_cursor_after_print(width),
PrintResult::NeedsRelocation if self.auto_wrap_mode => {
self.wrap_and_print_at_line_start(ch)
}
PrintResult::NeedsRelocation => self.print_at_line_end(ch),
}
self.dirty_lines.add(self.cursor.row);
}
fn try_print(&mut self, ch: char) -> PrintResult {
if self.cursor.col >= self.cols {
return PrintResult::NeedsRelocation;
}
if self.insert_mode {
self.buffer
.shift_right((self.cursor.col, self.cursor.row), 1, self.pen);
}
match self
.buffer
.print((self.cursor.col, self.cursor.row), ch, self.pen)
{
Some(width) => PrintResult::Printed(width),
None => PrintResult::NeedsRelocation,
}
}
fn advance_cursor_after_print(&mut self, width: usize) {
self.cursor.col += width;
if self.cursor.col == self.cols && !self.auto_wrap_mode {
self.cursor.col = self.cols - 1;
}
}
fn wrap_and_print_at_line_start(&mut self, ch: char) {
if self.cursor.row == self.bottom_margin {
self.buffer.wrap(self.cursor.row);
self.scroll_up_in_region(1);
} else if self.cursor.row < self.rows - 1 {
self.buffer.wrap(self.cursor.row);
self.cursor.row += 1;
}
if let Some(width) = self.buffer.print((0, self.cursor.row), ch, self.pen) {
self.cursor.col = width;
} else {
self.cursor.col = 0;
}
}
fn print_at_line_end(&mut self, ch: char) {
let row = self.cursor.row;
if self.cursor.col > 0 {
let col = self.cursor.col - 1;
let printed = self.buffer.print((col, row), ch, self.pen).is_some();
if !printed && col > 0 {
self.buffer.print((col - 1, row), ch, self.pen);
}
}
self.cursor.col = self.cols - 1;
}
fn bs(&mut self) {
if self.cursor.col == self.cols {
self.move_cursor_to_rel_col(-2);
} else {
self.move_cursor_to_rel_col(-1);
}
}
fn ht(&mut self) {
self.move_cursor_to_next_tab(1);
}
fn lf(&mut self) {
self.move_cursor_down_with_scroll();
if self.new_line_mode {
self.cursor.col = 0;
}
}
fn cr(&mut self) {
self.cursor.col = 0;
}
fn so(&mut self) {
self.active_charset = 1;
}
fn si(&mut self) {
self.active_charset = 0;
}
fn nel(&mut self) {
self.move_cursor_down_with_scroll();
self.cursor.col = 0;
}
fn hts(&mut self) {
self.set_tab();
}
fn ri(&mut self) {
if self.cursor.row == self.top_margin {
self.scroll_down_in_region(1);
} else if self.cursor.row > 0 {
self.move_cursor_to_row(self.cursor.row - 1);
}
}
fn sc(&mut self) {
self.save_cursor();
}
fn rc(&mut self) {
self.restore_cursor();
}
fn ris(&mut self) {
self.hard_reset();
}
fn decaln(&mut self) {
for row in 0..self.rows {
for col in 0..self.cols {
self.buffer.print((col, row), '\u{45}', Pen::default());
}
self.dirty_lines.add(row);
}
}
fn gzd4(&mut self, charset: Charset) {
self.charsets[0] = charset;
}
fn g1d4(&mut self, charset: Charset) {
self.charsets[1] = charset;
}
fn ich(&mut self, n: u16) {
if self.cursor.col == self.cols {
self.cursor.col = self.cols - 1;
}
let n = as_usize(n, 1).min(self.cols - self.cursor.col);
self.buffer
.shift_right((self.cursor.col, self.cursor.row), n, self.pen);
for col in self.cursor.col..self.cursor.col + n {
self.buffer.print((col, self.cursor.row), ' ', self.pen);
}
self.dirty_lines.add(self.cursor.row);
}
fn cuu(&mut self, n: u16) {
self.cursor_up(as_usize(n, 1));
}
fn cud(&mut self, n: u16) {
self.cursor_down(as_usize(n, 1));
}
fn cuf(&mut self, n: u16) {
self.move_cursor_to_rel_col(as_usize(n, 1) as isize);
}
fn cub(&mut self, n: u16) {
let mut rel_col = -(as_usize(n, 1) as isize);
if self.cursor.col == self.cols {
rel_col -= 1;
}
self.move_cursor_to_rel_col(rel_col);
}
fn cnl(&mut self, n: u16) {
self.cursor_down(as_usize(n, 1));
self.cursor.col = 0;
}
fn cpl(&mut self, n: u16) {
self.cursor_up(as_usize(n, 1));
self.cursor.col = 0;
}
fn cha(&mut self, n: u16) {
self.move_cursor_to_col(as_usize(n, 1) - 1);
}
fn cup(&mut self, row: u16, col: u16) {
self.move_cursor_to_col(as_usize(col, 1) - 1);
self.move_cursor_to_row(as_usize(row, 1) - 1);
}
fn cht(&mut self, n: u16) {
self.move_cursor_to_next_tab(as_usize(n, 1));
}
fn ed(&mut self, scope: EdScope) {
match scope {
EdScope::Below => {
self.buffer.erase(
(self.cursor.col, self.cursor.row),
EraseMode::FromCursorToEndOfView,
&self.pen,
);
self.dirty_lines.extend(self.cursor.row..self.rows);
}
EdScope::Above => {
self.buffer.erase(
(self.cursor.col, self.cursor.row),
EraseMode::FromStartOfViewToCursor,
&self.pen,
);
self.dirty_lines.extend(0..self.cursor.row + 1);
}
EdScope::All => {
self.buffer.erase(
(self.cursor.col, self.cursor.row),
EraseMode::WholeView,
&self.pen,
);
self.dirty_lines.extend(0..self.rows);
}
_ => {}
}
}
fn el(&mut self, scope: ElScope) {
match scope {
ElScope::ToRight => {
self.buffer.erase(
(self.cursor.col, self.cursor.row),
EraseMode::FromCursorToEndOfLine,
&self.pen,
);
self.dirty_lines.add(self.cursor.row);
}
ElScope::ToLeft => {
self.buffer.erase(
(self.cursor.col, self.cursor.row),
EraseMode::FromStartOfLineToCursor,
&self.pen,
);
self.dirty_lines.add(self.cursor.row);
}
ElScope::All => {
self.buffer.erase(
(self.cursor.col, self.cursor.row),
EraseMode::WholeLine,
&self.pen,
);
self.dirty_lines.add(self.cursor.row);
}
}
}
fn il(&mut self, n: u16) {
let range = if self.cursor.row <= self.bottom_margin {
self.cursor.row..self.bottom_margin + 1
} else {
self.cursor.row..self.rows
};
self.buffer
.scroll_down(range.clone(), as_usize(n, 1), &self.pen);
self.dirty_lines.extend(range);
}
fn dl(&mut self, n: u16) {
let range = if self.cursor.row <= self.bottom_margin {
self.cursor.row..self.bottom_margin + 1
} else {
self.cursor.row..self.rows
};
self.buffer
.scroll_up(range.clone(), as_usize(n, 1), &self.pen);
self.dirty_lines.extend(range);
}
fn dch(&mut self, n: u16) {
if self.cursor.col >= self.cols {
self.move_cursor_to_col(self.cols - 1);
}
self.buffer.delete(
(self.cursor.col, self.cursor.row),
as_usize(n, 1),
&self.pen,
);
self.dirty_lines.add(self.cursor.row);
}
fn su(&mut self, n: u16) {
self.scroll_up_in_region(as_usize(n, 1));
}
fn sd(&mut self, n: u16) {
self.scroll_down_in_region(as_usize(n, 1));
}
fn ctc(&mut self, op: CtcOp) {
match op {
CtcOp::Set => {
self.set_tab();
}
CtcOp::ClearCurrentColumn => {
self.clear_tab();
}
CtcOp::ClearAll => {
self.clear_all_tabs();
}
}
}
fn ech(&mut self, n: u16) {
let n = as_usize(n, 1);
self.buffer.erase(
(self.cursor.col, self.cursor.row),
EraseMode::NextChars(n),
&self.pen,
);
self.dirty_lines.add(self.cursor.row);
}
fn cbt(&mut self, n: u16) {
self.move_cursor_to_prev_tab(as_usize(n, 1));
}
fn rep(&mut self, n: u16) {
if self.cursor.col > 0 {
let n = as_usize(n, 1);
let row = self.cursor.row;
let mut col = self.cursor.col - 1;
while col > 0 && self.buffer[(col, row)].occupancy() == Occupancy::WideTail {
col -= 1;
}
let char = self.buffer[(col, row)].char();
for _n in 0..n {
self.print(char);
}
}
}
fn vpa(&mut self, n: u16) {
self.move_cursor_to_row(as_usize(n, 1) - 1);
}
fn vpr(&mut self, n: u16) {
self.cursor_down(as_usize(n, 1));
}
fn tbc(&mut self, scope: TbcScope) {
match scope {
TbcScope::CurrentColumn => {
self.clear_tab();
}
TbcScope::All => {
self.clear_all_tabs();
}
}
}
fn sm(&mut self, modes: AnsiModes) {
use AnsiMode::*;
for &mode in modes.as_slice() {
match mode {
Insert => {
self.insert_mode = true;
}
NewLine => {
self.new_line_mode = true;
}
}
}
}
fn rm(&mut self, modes: AnsiModes) {
use AnsiMode::*;
for &mode in modes.as_slice() {
match mode {
Insert => {
self.insert_mode = false;
}
NewLine => {
self.new_line_mode = false;
}
}
}
}
fn sgr(&mut self, ops: SgrOps) {
use SgrOp::*;
for &op in ops.as_slice() {
match op {
Reset => {
self.pen = Pen::default();
}
SetBoldIntensity => {
self.pen.intensity = Intensity::Bold;
}
SetFaintIntensity => {
self.pen.intensity = Intensity::Faint;
}
SetItalic => {
self.pen.set_italic();
}
SetUnderline => {
self.pen.set_underline();
}
SetBlink => {
self.pen.set_blink();
}
SetInverse => {
self.pen.set_inverse();
}
SetStrikethrough => {
self.pen.set_strikethrough();
}
ResetIntensity => {
self.pen.intensity = Intensity::Normal;
}
ResetItalic => {
self.pen.unset_italic();
}
ResetUnderline => {
self.pen.unset_underline();
}
ResetBlink => {
self.pen.unset_blink();
}
ResetInverse => {
self.pen.unset_inverse();
}
ResetStrikethrough => {
self.pen.unset_strikethrough();
}
SetForegroundColor(color) => {
self.pen.foreground = Some(color);
}
ResetForegroundColor => {
self.pen.foreground = None;
}
SetBackgroundColor(color) => {
self.pen.background = Some(color);
}
ResetBackgroundColor => {
self.pen.background = None;
}
}
}
}
fn decstbm(&mut self, top: u16, bottom: u16) {
let top = as_usize(top, 1) - 1;
let bottom = as_usize(bottom, self.rows) - 1;
if top < bottom && bottom < self.rows {
self.top_margin = top;
self.bottom_margin = bottom;
}
self.move_cursor_home();
}
fn xtwinops(&mut self, op: XtwinopsOp) {
if self.xtwinops {
let XtwinopsOp::Resize(cols, rows) = op;
let cols = as_usize(cols, self.cols);
let rows = as_usize(rows, self.rows);
self.resize(cols, rows);
}
}
fn decstr(&mut self) {
self.soft_reset();
}
fn decset(&mut self, modes: DecModes) {
use DecMode::*;
for &mode in modes.as_slice() {
match mode {
CursorKeys => {
self.cursor_keys_mode = CursorKeysMode::Application;
}
Origin => {
self.origin_mode = true;
self.move_cursor_home();
}
AutoWrap => {
self.auto_wrap_mode = true;
}
TextCursorEnable => {
self.cursor.visible = true;
}
AltScreenBuffer => {
self.switch_to_alternate_buffer();
self.reflow();
}
SaveCursor => {
self.save_cursor();
}
SaveCursorAltScreenBuffer => {
self.save_cursor();
self.switch_to_alternate_buffer();
self.reflow();
}
}
}
}
fn decrst(&mut self, modes: DecModes) {
use DecMode::*;
for &mode in modes.as_slice() {
match mode {
CursorKeys => {
self.cursor_keys_mode = CursorKeysMode::Normal;
}
Origin => {
self.origin_mode = false;
self.move_cursor_home();
}
AutoWrap => {
self.auto_wrap_mode = false;
}
TextCursorEnable => {
self.cursor.visible = false;
}
AltScreenBuffer => {
self.switch_to_primary_buffer();
self.reflow();
}
SaveCursor => {
self.restore_cursor();
}
SaveCursorAltScreenBuffer => {
self.switch_to_primary_buffer();
self.restore_cursor();
self.reflow();
}
}
}
}
pub fn dump(&self) -> Vec<Function> {
let (primary_ctx, alternate_ctx): (&SavedCtx, &SavedCtx) = match self.active_buffer_type {
BufferType::Primary => (&self.saved_ctx, &self.alternate_saved_ctx),
BufferType::Alternate => (&self.alternate_saved_ctx, &self.saved_ctx),
};
let mut funs = Vec::new();
dump_buffer(self.primary_buffer(), &mut funs);
if self.tabs != Tabs::new(self.cols) {
funs.push(Function::Ctc(CtcOp::ClearAll));
for t in &self.tabs {
funs.push(Function::Cha(as_u16(t + 1)));
funs.push(Function::Ctc(CtcOp::Set));
}
}
if !primary_ctx.is_default() {
if !primary_ctx.auto_wrap_mode {
funs.push(Function::Decrst(DecModes::one(DecMode::AutoWrap)));
}
if primary_ctx.origin_mode {
funs.push(Function::Decset(DecModes::one(DecMode::Origin)));
}
funs.push(Function::Cup(
as_u16(primary_ctx.cursor_row + 1),
as_u16(primary_ctx.cursor_col + 1),
));
funs.push(to_sgr(&primary_ctx.pen));
funs.push(Function::Decsc);
if !primary_ctx.auto_wrap_mode {
funs.push(Function::Decset(DecModes::one(DecMode::AutoWrap)));
}
if primary_ctx.origin_mode {
funs.push(Function::Decrst(DecModes::one(DecMode::Origin)));
}
}
funs.push(to_sgr(&Pen::default()));
if self.active_buffer_type == BufferType::Alternate || !alternate_ctx.is_default() {
funs.push(Function::Decset(DecModes::one(DecMode::AltScreenBuffer)));
}
if self.active_buffer_type == BufferType::Alternate {
funs.push(Function::Cup(1, 1));
dump_buffer(self.alternate_buffer(), &mut funs);
}
if !alternate_ctx.is_default() {
if !alternate_ctx.auto_wrap_mode {
funs.push(Function::Decrst(DecModes::one(DecMode::AutoWrap)));
}
if alternate_ctx.origin_mode {
funs.push(Function::Decset(DecModes::one(DecMode::Origin)));
}
funs.push(Function::Cup(
as_u16(alternate_ctx.cursor_row + 1),
as_u16(alternate_ctx.cursor_col + 1),
));
funs.push(to_sgr(&alternate_ctx.pen));
funs.push(Function::Decsc);
if !alternate_ctx.auto_wrap_mode {
funs.push(Function::Decset(DecModes::one(DecMode::AutoWrap)));
}
if alternate_ctx.origin_mode {
funs.push(Function::Decrst(DecModes::one(DecMode::Origin)));
}
}
if self.active_buffer_type == BufferType::Primary && !alternate_ctx.is_default() {
funs.push(Function::Decrst(DecModes::one(DecMode::AltScreenBuffer)));
}
if self.origin_mode {
funs.push(Function::Decset(DecModes::one(DecMode::Origin)));
}
if self.top_margin > 0 || self.bottom_margin < self.rows - 1 {
funs.push(Function::Decstbm(
as_u16(self.top_margin + 1),
as_u16(self.bottom_margin + 1),
));
}
let col = self.cursor.col;
let mut row = self.cursor.row;
if self.origin_mode {
if row < self.top_margin || row > self.bottom_margin {
funs.push(Function::Scorc);
match col.cmp(&self.saved_ctx.cursor_col) {
Ordering::Less => {
let n = self.saved_ctx.cursor_col - col;
funs.push(Function::Cub(as_u16(n)));
}
Ordering::Greater => {
let n = col - self.saved_ctx.cursor_col;
funs.push(Function::Cuf(as_u16(n)));
}
Ordering::Equal => (),
}
match row.cmp(&self.saved_ctx.cursor_row) {
Ordering::Less => {
let n = self.saved_ctx.cursor_row - row;
funs.push(Function::Cuu(as_u16(n)));
}
Ordering::Greater => {
let n = row - self.saved_ctx.cursor_row;
funs.push(Function::Cud(as_u16(n)));
}
Ordering::Equal => (),
}
} else {
row -= self.top_margin;
funs.push(Function::Cup(as_u16(row + 1), as_u16(col + 1)));
}
} else {
funs.push(Function::Cup(as_u16(row + 1), as_u16(col + 1)));
}
if self.cursor.col >= self.cols {
let last_cell = self.buffer[(self.cols - 1, self.cursor.row)];
let occupancy = last_cell.occupancy();
if occupancy == Occupancy::Single {
funs.push(to_sgr(last_cell.pen()));
funs.push(Function::Print(last_cell.char()));
} else if occupancy == Occupancy::WideTail {
let prev_cell = self.buffer[(self.cols - 2, self.cursor.row)];
funs.push(Function::Cub(1));
funs.push(to_sgr(prev_cell.pen()));
funs.push(Function::Print(prev_cell.char()));
}
}
funs.push(to_sgr(&self.pen));
if !self.cursor.visible {
funs.push(Function::Decrst(DecModes::one(DecMode::TextCursorEnable)));
}
if self.charsets[0] == Charset::Drawing {
funs.push(Function::Gzd4(Charset::Drawing));
}
if self.charsets[1] == Charset::Drawing {
funs.push(Function::G1d4(Charset::Drawing));
}
if self.active_charset == 1 {
funs.push(Function::So);
}
if self.insert_mode {
funs.push(Function::Sm(AnsiModes::one(AnsiMode::Insert)));
}
if !self.auto_wrap_mode {
funs.push(Function::Decrst(DecModes::one(DecMode::AutoWrap)));
}
if self.new_line_mode {
funs.push(Function::Sm(AnsiModes::one(AnsiMode::NewLine)));
}
if self.cursor_keys_mode == CursorKeysMode::Application {
funs.push(Function::Decset(DecModes::one(DecMode::CursorKeys)));
}
funs
}
}
fn dump_buffer(buffer: &Buffer, funs: &mut Vec<Function>) {
let mut cutoff = 0;
let mut wrapped = false;
for (i, line) in buffer.view().enumerate() {
if wrapped || line.wrapped || !line.is_blank() {
cutoff = i + 1;
}
wrapped = line.wrapped;
}
let last = buffer.rows - 1;
let mut pen = Pen::default();
for (i, line) in buffer.view().take(cutoff).enumerate() {
for cells in line.chunks(|c1, c2| c1.pen() != c2.pen()) {
if cells[0].pen() != &pen {
if let Some(sgr) = to_sgr_diff(&pen, cells[0].pen()) {
funs.push(sgr);
}
pen = *cells[0].pen();
}
dump_cells(&cells, funs);
}
if i < last && !line.wrapped {
funs.push(Function::Cr);
funs.push(Function::Lf);
}
}
}
fn to_sgr_diff(from: &Pen, to: &Pen) -> Option<Function> {
if from == to {
return None;
}
let full = to_sgr_ops(to);
let diff = to_sgr_diff_ops(from, to);
let ops = if diff.len() <= full.len() { diff } else { full };
Some(Function::Sgr(ops))
}
fn to_sgr_diff_ops(from: &Pen, to: &Pen) -> SgrOps {
let mut ops = SgrOps::new();
if from.intensity != to.intensity {
match to.intensity {
Intensity::Normal => ops.push(SgrOp::ResetIntensity),
Intensity::Bold => ops.push(SgrOp::SetBoldIntensity),
Intensity::Faint => ops.push(SgrOp::SetFaintIntensity),
}
}
if from.foreground() != to.foreground() {
match to.foreground() {
Some(color) => ops.push(SgrOp::SetForegroundColor(color)),
None => ops.push(SgrOp::ResetForegroundColor),
}
}
if from.background() != to.background() {
match to.background() {
Some(color) => ops.push(SgrOp::SetBackgroundColor(color)),
None => ops.push(SgrOp::ResetBackgroundColor),
}
}
push_attr_diff(
&mut ops,
from.is_italic(),
to.is_italic(),
SgrOp::SetItalic,
SgrOp::ResetItalic,
);
push_attr_diff(
&mut ops,
from.is_underline(),
to.is_underline(),
SgrOp::SetUnderline,
SgrOp::ResetUnderline,
);
push_attr_diff(
&mut ops,
from.is_blink(),
to.is_blink(),
SgrOp::SetBlink,
SgrOp::ResetBlink,
);
push_attr_diff(
&mut ops,
from.is_inverse(),
to.is_inverse(),
SgrOp::SetInverse,
SgrOp::ResetInverse,
);
push_attr_diff(
&mut ops,
from.is_strikethrough(),
to.is_strikethrough(),
SgrOp::SetStrikethrough,
SgrOp::ResetStrikethrough,
);
ops
}
fn push_attr_diff(ops: &mut SgrOps, from: bool, to: bool, set: SgrOp, reset: SgrOp) {
if from != to {
ops.push(if to { set } else { reset });
}
}
fn dump_cells(cells: &[Cell], funs: &mut Vec<Function>) {
let mut i = 0;
while i < cells.len() {
let ch = cells[i].char();
let mut run_len = 1;
while i + run_len < cells.len() && cells[i + run_len].char() == ch {
run_len += 1;
}
if run_len > 5 {
funs.push(Function::Print(ch));
funs.push(Function::Rep(as_u16(run_len - 1)));
} else {
for _ in 0..run_len {
funs.push(Function::Print(ch));
}
}
i += run_len;
}
}
fn to_sgr(pen: &Pen) -> Function {
Function::Sgr(to_sgr_ops(pen))
}
fn to_sgr_ops(pen: &Pen) -> SgrOps {
let mut ops = SgrOps::new();
ops.push(SgrOp::Reset);
if let Some(color) = pen.foreground() {
ops.push(SgrOp::SetForegroundColor(color));
}
if let Some(color) = pen.background() {
ops.push(SgrOp::SetBackgroundColor(color));
}
match pen.intensity {
Intensity::Normal => {}
Intensity::Bold => ops.push(SgrOp::SetBoldIntensity),
Intensity::Faint => ops.push(SgrOp::SetFaintIntensity),
}
if pen.is_italic() {
ops.push(SgrOp::SetItalic);
}
if pen.is_underline() {
ops.push(SgrOp::SetUnderline);
}
if pen.is_blink() {
ops.push(SgrOp::SetBlink);
}
if pen.is_inverse() {
ops.push(SgrOp::SetInverse);
}
if pen.is_strikethrough() {
ops.push(SgrOp::SetStrikethrough);
}
ops
}
fn as_u16(value: usize) -> u16 {
value
.try_into()
.expect("terminal dump parameter exceeds u16 range")
}
fn as_usize(value: u16, default: usize) -> usize {
if value == 0 {
default
} else {
value as usize
}
}
impl Default for Terminal {
fn default() -> Self {
Self::new((80, 24), None)
}
}
#[cfg(test)]
mod tests {
use super::{BufferType, Occupancy, Terminal};
use crate::charset::Charset;
use crate::color::Color;
use crate::line::Line;
use crate::parser::{
AnsiMode, AnsiModes, DecMode, DecModes, EdScope, ElScope, Function, SgrOp, SgrOps,
};
use crate::pen::Intensity;
use crate::pen::Pen;
use Function::*;
use SgrOp::*;
fn feed(term: &mut Terminal, input: &str) {
for ch in input.chars() {
let fun = match ch {
'\n' => Lf,
'\r' => Cr,
_ => Print(ch),
};
term.execute(fun);
}
}
fn build_term(cols: usize, rows: usize, cx: usize, cy: usize, init: &str) -> Terminal {
let mut term = Terminal::new((cols, rows), None);
feed(&mut term, init);
term.execute(Cup((cy + 1) as u16, (cx + 1) as u16));
term
}
fn text(term: &Terminal) -> String {
let cursor = term.cursor();
buffer_text(term.view(), cursor.col, cursor.row)
}
fn buffer_text<'a, I: Iterator<Item = &'a Line> + 'a>(
mut view: I,
cursor_col: usize,
cursor_row: usize,
) -> String {
let mut lines = Vec::new();
lines.extend(view.by_ref().take(cursor_row).map(|l| l.text()));
let cursor_line = view.next().unwrap();
let mut offset = 0;
let mut line = String::new();
let mut cells = cursor_line
.cells()
.iter()
.filter(|c| c.occupancy() != Occupancy::WideTail);
for cell in cells.by_ref() {
let width = cell.width() as usize;
if offset + width <= cursor_col {
line.push(cell.char());
offset += width;
} else {
line.push('|');
line.push(cell.char());
offset += width;
break;
}
}
if offset == cursor_col {
line.push('|');
}
line.extend(cells.map(|c| c.char()));
lines.push(line);
lines.extend(view.map(|l| l.text()));
lines
.into_iter()
.map(|line| line.trim_end().to_owned())
.collect::<Vec<_>>()
.join("\n")
}
fn wrapped(term: &Terminal) -> Vec<bool> {
term.view().map(|l| l.wrapped).collect()
}
fn ansi_modes<const N: usize>(modes: [AnsiMode; N]) -> AnsiModes {
AnsiModes::from(&modes[..])
}
fn dec_modes<const N: usize>(modes: [DecMode; N]) -> DecModes {
DecModes::from(&modes[..])
}
fn sgr(op: SgrOp) -> Function {
Sgr(SgrOps::from(vec![op]))
}
fn pen(f: impl FnOnce(&mut Pen)) -> Pen {
let mut pen = Pen::default();
f(&mut pen);
pen
}
#[test]
fn execute_sgr() {
let mut term = Terminal::default();
term.execute(sgr(SetBoldIntensity));
assert!(term.pen.intensity == Intensity::Bold);
term.execute(sgr(SetFaintIntensity));
assert_eq!(term.pen.intensity, Intensity::Faint);
term.execute(sgr(SetItalic));
assert!(term.pen.is_italic());
term.execute(sgr(SetUnderline));
assert!(term.pen.is_underline());
term.execute(sgr(SetBlink));
assert!(term.pen.is_blink());
term.execute(sgr(SetInverse));
assert!(term.pen.is_inverse());
term.execute(sgr(SetStrikethrough));
assert!(term.pen.is_strikethrough());
term.execute(sgr(SetForegroundColor(Color::Indexed(1))));
assert_eq!(term.pen.foreground, Some(Color::Indexed(1)));
term.execute(sgr(SetBackgroundColor(Color::Indexed(2))));
assert_eq!(term.pen.background, Some(Color::Indexed(2)));
term.execute(sgr(ResetForegroundColor));
assert_eq!(term.pen.foreground, None);
term.execute(sgr(ResetBackgroundColor));
assert_eq!(term.pen.background, None);
term.execute(Sgr(SgrOps::from(vec![
SetBoldIntensity,
SetForegroundColor(Color::Indexed(1)),
SetBackgroundColor(Color::Indexed(2)),
SetBlink,
ResetIntensity,
])));
assert_eq!(term.pen.intensity, Intensity::Normal);
assert!(term.pen.is_blink());
assert_eq!(term.pen.foreground, Some(Color::Indexed(1)));
assert_eq!(term.pen.background, Some(Color::Indexed(2)));
}
#[test]
fn execute_lf() {
let mut term = build_term(8, 2, 3, 0, "abc");
term.execute(Lf);
assert_eq!(term.cursor(), (3, 1));
assert_eq!(text(&term), "abc\n |");
term.execute(Print('d'));
term.execute(Lf);
assert_eq!(term.cursor(), (4, 1));
assert_eq!(text(&term), " d\n |");
}
#[test]
fn execute_cr() {
let mut term = Terminal::new((4, 2), None);
feed(&mut term, "abc");
term.execute(Cr);
term.execute(Print('d'));
assert_eq!(term.cursor(), (1, 0));
assert_eq!(text(&term), "d|bc\n");
}
#[test]
fn execute_nel() {
let mut term = build_term(8, 2, 3, 0, "abc");
term.execute(Nel);
assert_eq!(term.cursor(), (0, 1));
assert_eq!(text(&term), "abc\n|");
term.execute(Print('d'));
assert_eq!(text(&term), "abc\nd|");
}
#[test]
fn execute_bs() {
let mut term = Terminal::new((4, 2), None);
feed(&mut term, "a");
term.execute(Bs);
assert_eq!(text(&term), "|a\n");
term.execute(Bs);
assert_eq!(text(&term), "|a\n");
feed(&mut term, "abcd");
term.execute(Bs);
assert_eq!(text(&term), "ab|cd\n");
feed(&mut term, "cdef");
term.execute(Bs);
assert_eq!(text(&term), "abcd\ne|f");
term.execute(Bs);
assert_eq!(text(&term), "abcd\n|ef");
term.execute(Bs);
assert_eq!(text(&term), "abcd\n|ef");
}
#[test]
fn execute_cup() {
let mut term = Terminal::new((4, 2), None);
feed(&mut term, "abc\r\ndef");
term.execute(Cup(1, 1));
assert_eq!(term.cursor(), (0, 0));
term.execute(Cup(10, 10));
assert_eq!(term.cursor(), (3, 1));
}
#[test]
fn execute_cuu() {
let mut term = Terminal::new((8, 4), None);
feed(&mut term, "abcd\n\n\n");
term.execute(Cuu(0));
assert_eq!(term.cursor(), (4, 2));
term.execute(Cuu(2));
assert_eq!(term.cursor(), (4, 0));
}
#[test]
fn execute_cpl() {
let mut term = Terminal::new((8, 4), None);
feed(&mut term, "abcd\r\n\r\n\r\nef");
assert_eq!(term.cursor(), (2, 3));
term.execute(Cpl(0));
assert_eq!(term.cursor(), (0, 2));
term.execute(Cpl(2));
assert_eq!(term.cursor(), (0, 0));
}
#[test]
fn execute_cnl() {
let mut term = Terminal::new((4, 4), None);
feed(&mut term, "ab");
term.execute(Cnl(0));
assert_eq!(term.cursor(), (0, 1));
term.execute(Cnl(3));
assert_eq!(term.cursor(), (0, 3));
}
#[test]
fn execute_vpa() {
let mut term = Terminal::new((4, 4), None);
feed(&mut term, "\r\n\r\naaa\r\nbbb");
term.execute(Vpa(0));
assert_eq!(term.cursor(), (3, 0));
term.execute(Vpa(10));
assert_eq!(term.cursor(), (3, 3));
}
#[test]
fn execute_cud() {
let mut term = Terminal::new((8, 4), None);
feed(&mut term, "abcd");
term.execute(Cud(0));
assert_eq!(text(&term), "abcd\n |\n\n");
term.execute(Cud(2));
assert_eq!(text(&term), "abcd\n\n\n |");
}
#[test]
fn execute_cuf() {
let mut term = Terminal::new((4, 1), None);
term.execute(Cuf(2));
assert_eq!(text(&term), " |");
term.execute(Cuf(2));
assert_eq!(text(&term), " |");
term.execute(Print('a'));
assert_eq!(text(&term), " a|");
term.execute(Cuf(5));
assert_eq!(text(&term), " |a");
feed(&mut term, "ab");
term.execute(Cuf(10));
assert_eq!(text(&term), "b |");
}
#[test]
fn execute_cha() {
let mut term = Terminal::new((8, 2), None);
feed(&mut term, "abc");
term.execute(Cha(0));
assert_eq!(term.cursor(), (0, 0));
term.execute(Cha(3));
assert_eq!(term.cursor(), (2, 0));
term.execute(Cha(20));
assert_eq!(term.cursor(), (7, 0));
}
#[test]
fn execute_cub() {
let mut term = Terminal::new((8, 2), None);
feed(&mut term, "abcd");
term.execute(Cub(2));
assert_eq!(text(&term), "ab|cd\n");
feed(&mut term, "cdef");
term.execute(Cub(2));
assert_eq!(text(&term), "abcd|ef\n");
term.execute(Cub(10));
assert_eq!(text(&term), "|abcdef\n");
let mut term = Terminal::new((4, 2), None);
feed(&mut term, "abcd");
term.execute(Cub(0));
assert_eq!(text(&term), "ab|cd\n");
}
#[test]
fn execute_ht() {
let mut term = Terminal::new((20, 1), None);
term.execute(Ht);
assert_eq!(term.cursor(), (8, 0));
term.execute(Ht);
assert_eq!(term.cursor(), (16, 0));
term.execute(Ht);
assert_eq!(term.cursor(), (19, 0));
}
#[test]
fn execute_hts() {
let mut term = Terminal::new((20, 1), None);
term.execute(Cuf(5));
term.execute(Hts);
assert_eq!(term.tabs, vec![5, 8, 16]);
term.execute(Cup(1, 1));
term.execute(Ht);
assert_eq!(term.cursor(), (5, 0));
}
#[test]
fn execute_cht() {
let mut term = build_term(28, 1, 3, 0, "abcdefghijklmnopqrstuwxyzabc");
term.execute(Cht(0));
assert_eq!(term.cursor(), (8, 0));
term.execute(Cht(2));
assert_eq!(term.cursor(), (24, 0));
term.execute(Cht(0));
assert_eq!(term.cursor(), (27, 0));
}
#[test]
fn execute_cbt() {
let mut term = build_term(28, 1, 26, 0, "abcdefghijklmnopqrstuwxyzabc");
term.execute(Cbt(0));
assert_eq!(term.cursor(), (24, 0));
term.execute(Cbt(2));
assert_eq!(term.cursor(), (8, 0));
term.execute(Cbt(0));
assert_eq!(term.cursor(), (0, 0));
}
#[test]
fn execute_rep() {
let mut term = build_term(20, 2, 0, 0, "");
term.execute(Rep(0));
assert_eq!(text(&term), "|\n");
term.execute(Print('A'));
term.execute(Rep(0));
assert_eq!(text(&term), "AA|\n");
term.execute(Rep(3));
assert_eq!(text(&term), "AAAAA|\n");
term.execute(Cuf(5));
term.execute(Rep(0));
assert_eq!(text(&term), "AAAAA |\n");
}
#[test]
fn execute_rep_after_wide_char() {
let mut term = build_term(20, 2, 0, 0, "");
term.execute(Print('ハ'));
term.execute(Rep(3));
assert_eq!(text(&term), "ハハハハ|\n");
}
#[test]
fn execute_rep_after_moving_into_wide_char_tail() {
let mut term = build_term(20, 2, 0, 0, "");
term.execute(Print('ハ'));
term.execute(Cub(1));
term.execute(Rep(3));
assert_eq!(text(&term), " ハハハ|\n");
}
#[test]
fn dump_uses_rep_for_wide_char_runs() {
let mut term = build_term(20, 2, 0, 0, "");
feed(&mut term, "ハハハハハハ");
let funs = term.dump();
assert_eq!(funs[..2], [Print('ハ'), Rep(5)]);
}
#[test]
fn sgr_diff_prefers_targeted_ops_on_tie() {
let from = pen(|pen| pen.intensity = Intensity::Bold);
assert_eq!(
super::to_sgr_diff(&from, &Pen::default()),
Some(sgr(ResetIntensity))
);
}
#[test]
fn sgr_diff_uses_full_reset_when_shorter() {
let from = pen(|pen| {
pen.intensity = Intensity::Bold;
pen.set_underline();
});
assert_eq!(super::to_sgr_diff(&from, &Pen::default()), Some(sgr(Reset)));
}
#[test]
fn sgr_diff_uses_targeted_sets_for_first_styled_chunk() {
let to = pen(|pen| {
pen.intensity = Intensity::Bold;
pen.foreground = Some(Color::Indexed(1));
});
assert_eq!(
super::to_sgr_diff(&Pen::default(), &to),
Some(Sgr(SgrOps::from(vec![
SetBoldIntensity,
SetForegroundColor(Color::Indexed(1))
])))
);
}
#[test]
fn execute_vpr() {
let mut term = Terminal::new((4, 4), None);
feed(&mut term, "ab");
term.execute(Vpr(0));
assert_eq!(term.cursor(), (2, 1));
assert_eq!(text(&term), "ab\n |\n\n");
term.execute(Vpr(10));
assert_eq!(term.cursor(), (2, 3));
assert_eq!(text(&term), "ab\n\n\n |");
}
#[test]
fn execute_ich() {
let mut term = build_term(8, 2, 3, 0, "abcdefghijklmn");
term.execute(Ich(0));
assert_eq!(text(&term), "abc| defg\nijklmn");
assert_eq!(wrapped(&term), vec![true, false]);
term.execute(Ich(2));
assert_eq!(text(&term), "abc| de\nijklmn");
term.execute(Ich(10));
assert_eq!(text(&term), "abc|\nijklmn");
let mut term = build_term(8, 2, 7, 0, "abcdefghijklmn");
term.execute(Ich(10));
assert_eq!(text(&term), "abcdefg|\nijklmn");
}
#[test]
fn execute_il() {
let mut term = build_term(4, 4, 2, 1, "abcdefghij");
term.execute(Il(0));
assert_eq!(text(&term), "abcd\n |\nefgh\nij");
assert_eq!(wrapped(&term), vec![false, false, true, false]);
term.execute(Cuu(1));
term.execute(Il(0));
assert_eq!(text(&term), " |\nabcd\n\nefgh");
assert_eq!(wrapped(&term), vec![false, false, false, false]);
term.execute(Cud(3));
term.execute(Il(100));
assert_eq!(text(&term), "\nabcd\n\n |");
}
#[test]
fn execute_dl() {
let mut term = Terminal::new((4, 4), None);
feed(&mut term, "abcdefghijklmn");
term.execute(Cuu(2));
term.execute(Dl(0));
assert_eq!(text(&term), "abcd\nij|kl\nmn\n");
assert_eq!(wrapped(&term), vec![false, true, false, false]);
let mut term = Terminal::new((4, 4), None);
feed(&mut term, "abcdefghijklmn");
term.execute(Decstbm(1, 3));
term.execute(Cup(2, 1));
term.execute(Dl(0));
assert_eq!(text(&term), "abcd\n|ijkl\n\nmn");
assert_eq!(wrapped(&term), vec![false, false, false, false]);
let mut term = Terminal::new((4, 4), None);
feed(&mut term, "abcdefghijklmn");
term.execute(Decstbm(1, 2));
term.execute(Cup(4, 1));
term.execute(Dl(0));
assert_eq!(text(&term), "abcd\nefgh\nijkl\n|");
assert_eq!(wrapped(&term), vec![true, true, false, false]);
}
#[test]
fn execute_el() {
let mut term = build_term(4, 2, 2, 0, "abcd");
term.execute(El(ElScope::ToRight));
assert_eq!(text(&term), "ab|\n");
let mut term = build_term(4, 2, 2, 0, "a");
term.execute(El(ElScope::ToRight));
assert_eq!(text(&term), "a |\n");
let mut term = build_term(4, 2, 2, 0, "abcd");
term.execute(El(ElScope::ToLeft));
assert_eq!(text(&term), " | d\n");
let mut term = build_term(4, 2, 2, 0, "abcd");
term.execute(El(ElScope::All));
assert_eq!(text(&term), " |\n");
let mut term = Terminal::new((4, 3), None);
feed(&mut term, "abcdefghij");
term.execute(Cuu(1));
term.execute(El(ElScope::ToRight));
assert_eq!(text(&term), "abcd\nef|\nij");
assert_eq!(wrapped(&term), vec![true, false, false]);
let mut term = Terminal::new((4, 3), None);
feed(&mut term, "abcdefghij");
term.execute(Cuu(1));
term.execute(El(ElScope::ToLeft));
assert_eq!(text(&term), "abcd\n | h\nij");
assert_eq!(wrapped(&term), vec![true, true, false]);
let mut term = Terminal::new((4, 3), None);
feed(&mut term, "abcdefghij");
term.execute(Cuu(1));
term.execute(El(ElScope::All));
assert_eq!(text(&term), "abcd\n |\nij");
assert_eq!(wrapped(&term), vec![true, false, false]);
}
#[test]
fn execute_ed() {
let mut term = build_term(4, 3, 1, 1, "abc\r\ndef\r\nghi");
term.execute(Ed(EdScope::Below));
assert_eq!(text(&term), "abc\nd|\n");
let mut term = build_term(4, 3, 1, 1, "abc\r\n\r\nghi");
term.execute(Ed(EdScope::Below));
assert_eq!(text(&term), "abc\n |\n");
let mut term = build_term(4, 3, 1, 1, "abc\r\ndef\r\nghi");
term.execute(Ed(EdScope::Above));
assert_eq!(text(&term), "\n | f\nghi");
let mut term = build_term(4, 3, 1, 1, "abc\r\ndef\r\nghi");
term.execute(Ed(EdScope::All));
assert_eq!(text(&term), "\n |\n");
let mut term = build_term(4, 3, 1, 1, "abcdefghij");
term.execute(Ed(EdScope::Below));
assert_eq!(text(&term), "abcd\ne|\n");
assert_eq!(wrapped(&term), vec![true, false, false]);
let mut term = build_term(4, 3, 1, 1, "abcdefghij");
term.execute(Ed(EdScope::Above));
assert_eq!(text(&term), "\n | gh\nij");
assert_eq!(wrapped(&term), vec![false, true, false]);
let mut term = build_term(4, 3, 1, 1, "abcdefghij");
term.execute(Ed(EdScope::All));
assert_eq!(text(&term), "\n |\n");
assert_eq!(wrapped(&term), vec![false, false, false]);
}
#[test]
fn execute_dch() {
let mut term = build_term(8, 2, 3, 0, "abcdefghijkl");
term.execute(Dch(0));
assert_eq!(text(&term), "abc|efgh\nijkl");
assert_eq!(wrapped(&term), vec![false, false]);
term.execute(Dch(2));
assert_eq!(text(&term), "abc|gh\nijkl");
term.execute(Dch(10));
assert_eq!(text(&term), "abc|\nijkl");
term.execute(Cuf(10));
term.execute(Dch(10));
assert_eq!(text(&term), "abc |\nijkl");
}
#[test]
fn execute_ech() {
let mut term = build_term(8, 2, 3, 0, "abcdefghijkl");
term.execute(Ech(0));
assert_eq!(text(&term), "abc| efgh\nijkl");
assert_eq!(wrapped(&term), vec![true, false]);
term.execute(Ech(2));
assert_eq!(text(&term), "abc| fgh\nijkl");
assert_eq!(wrapped(&term), vec![true, false]);
term.execute(Ech(10));
assert_eq!(text(&term), "abc|\nijkl");
assert_eq!(wrapped(&term), vec![false, false]);
term.execute(Cuf(3));
term.execute(Ech(0));
assert_eq!(text(&term), "abc |\nijkl");
}
#[test]
fn execute_ri() {
let mut term = build_term(8, 5, 0, 0, "abcd\r\nefgh\r\nijkl\r\nmnop\r\nqrst");
term.execute(Ri);
assert_eq!(text(&term), "|\nabcd\nefgh\nijkl\nmnop");
term.execute(Decstbm(3, 4));
term.execute(Cup(3, 1));
term.execute(Ri);
assert_eq!(text(&term), "\nabcd\n|\nefgh\nmnop");
}
#[test]
fn execute_sc_rc() {
let mut term = build_term(4, 3, 0, 0, "");
feed(&mut term, " \n");
term.execute(Decsc);
feed(&mut term, " \n");
term.execute(Decrc);
assert_eq!(term.cursor(), (2, 1));
let mut term = build_term(4, 3, 0, 0, "");
feed(&mut term, " \n");
term.execute(Scosc);
feed(&mut term, " \n");
term.execute(Scorc);
assert_eq!(term.cursor(), (2, 1));
}
#[test]
fn execute_sc_rc_restores_pen_and_modes() {
fn assert_save_restore(save: Function, restore: Function) {
let mut term = Terminal::new((4, 4), None);
term.execute(Decrst(dec_modes([DecMode::AutoWrap])));
term.execute(Decstbm(2, 4));
term.execute(Decset(dec_modes([DecMode::Origin])));
term.execute(Cup(2, 3));
term.execute(sgr(SetBoldIntensity));
term.execute(save);
term.execute(Decset(dec_modes([DecMode::AutoWrap])));
term.execute(Decrst(dec_modes([DecMode::Origin])));
term.execute(Cup(4, 4));
term.execute(sgr(Reset));
term.execute(restore);
assert_eq!(term.cursor(), (2, 2));
assert_eq!(term.pen.intensity, Intensity::Bold);
assert!(term.origin_mode);
assert!(!term.auto_wrap_mode);
}
assert_save_restore(Decsc, Decrc);
assert_save_restore(Scosc, Scorc);
}
#[test]
fn auto_wrap_mode() {
let mut term = Terminal::new((4, 4), None);
term.execute(Decset(dec_modes([DecMode::AutoWrap])));
feed(&mut term, "abcdef");
assert_eq!(text(&term), "abcd\nef|\n\n");
let mut term = Terminal::new((4, 4), None);
term.execute(Decrst(dec_modes([DecMode::AutoWrap])));
feed(&mut term, "abcdef");
assert_eq!(text(&term), "abc|f\n\n\n");
}
#[test]
fn insert_mode() {
let mut term = Terminal::new((4, 4), None);
feed(&mut term, "abcd");
term.execute(Cub(2));
term.execute(Sm(ansi_modes([AnsiMode::Insert])));
feed(&mut term, "ef");
assert_eq!(text(&term), "aef|b\n\n\n");
feed(&mut term, "ghij");
assert_eq!(text(&term), "aefg\nhij|\n\n");
}
#[test]
fn print_at_the_end_of_the_screen() {
let mut term = Terminal::new((4, 6), None);
feed(&mut term, "xxxxxxxxxx");
term.execute(Cup(50, 1));
feed(&mut term, "yyy");
term.execute(Cuf(50));
feed(&mut term, "zzz");
assert_eq!(text(&term), "xxxx\nxx\n\n\nyyyz\nzz|");
let mut term = Terminal::new((4, 6), None);
feed(&mut term, "\nxxxxxxxxxx");
term.execute(Decstbm(2, 4));
term.execute(Cup(1, 1));
feed(&mut term, "yyy");
term.execute(Cuf(50));
feed(&mut term, "zzz");
assert_eq!(text(&term), "yyyz\nzz|xx\nxxxx\nxx\n\n");
let mut term = Terminal::new((4, 6), None);
term.execute(Decstbm(0, 3));
feed(&mut term, "xxxxxxxxxx");
term.execute(Cup(50, 1));
feed(&mut term, "yyy");
term.execute(Cuf(50));
feed(&mut term, "zzz");
assert_eq!(text(&term), "xxxx\nxxxx\nxx\n\n\nzz|yz");
}
#[test]
fn wide_chars() {
let mut term = Terminal::new((20, 2), None);
feed(&mut term, "ハローワールド");
assert_eq!(text(&term), "ハローワールド|\n");
term.execute(Cub(5));
assert_eq!(term.cursor().col, 9);
assert_eq!(text(&term), "ハローワ|ールド\n");
}
#[test]
fn print_wide_char_on_wide_tail_with_one_col_right_and_autowrap() {
let mut term = Terminal::new((4, 2), None);
feed(&mut term, "Aハz");
term.execute(Cub(1));
term.execute(Print('界'));
assert_eq!(term.cursor(), (4, 0));
assert_eq!(text(&term), "A 界|\n");
assert_eq!(wrapped(&term), vec![false, false]);
let row0 = term.view().next().unwrap();
assert_eq!(row0.text(), "A 界");
assert_eq!(
row0.cells()
.iter()
.map(|cell| (cell.char(), cell.occupancy()))
.collect::<Vec<_>>(),
vec![
('A', Occupancy::Single),
(' ', Occupancy::Single),
('界', Occupancy::WideHead),
(' ', Occupancy::WideTail),
]
);
}
#[test]
fn print_wide_char_on_wide_tail_with_one_col_right_and_no_autowrap() {
let mut term = Terminal::new((4, 2), None);
term.execute(Decrst(dec_modes([DecMode::AutoWrap])));
feed(&mut term, "Aハz");
term.execute(Cub(1));
term.execute(Print('界'));
assert_eq!(term.cursor(), (3, 0));
assert_eq!(text(&term), "A |界\n");
assert_eq!(wrapped(&term), vec![false, false]);
let row0 = term.view().next().unwrap();
assert_eq!(row0.text(), "A 界");
assert_eq!(
row0.cells()
.iter()
.map(|cell| (cell.char(), cell.occupancy()))
.collect::<Vec<_>>(),
vec![
('A', Occupancy::Single),
(' ', Occupancy::Single),
('界', Occupancy::WideHead),
(' ', Occupancy::WideTail),
]
);
}
#[test]
fn print_wide_char_in_occupied_last_column_preserves_cell_before_wrap() {
let mut term = Terminal::new((4, 2), None);
feed(&mut term, "abcz");
term.execute(Cup(1, 4));
term.execute(Print('界'));
assert_eq!(term.cursor(), (2, 1));
assert_eq!(text(&term), "abcz\n界|");
assert_eq!(wrapped(&term), vec![true, false]);
let mut view = term.view();
let row0 = view.next().unwrap();
let row1 = view.next().unwrap();
assert_eq!(row0.text(), "abcz");
assert_eq!(
row0.cells()
.iter()
.map(|cell| (cell.char(), cell.occupancy()))
.collect::<Vec<_>>(),
vec![
('a', Occupancy::Single),
('b', Occupancy::Single),
('c', Occupancy::Single),
('z', Occupancy::Single),
]
);
assert_eq!(row1.text(), "界 ");
assert_eq!(
row1.cells()
.iter()
.map(|cell| (cell.char(), cell.occupancy()))
.collect::<Vec<_>>(),
vec![
('界', Occupancy::WideHead),
(' ', Occupancy::WideTail),
(' ', Occupancy::Single),
(' ', Occupancy::Single),
]
);
}
#[test]
fn execute_su() {
let mut term = Terminal::new((4, 6), None);
feed(&mut term, "aa\r\nbb\r\ncc\r\ndd\r\nee\r\nff");
term.execute(Su(2));
assert_eq!(text(&term), "cc\ndd\nee\nff\n\n |");
let mut term = Terminal::new((4, 6), None);
feed(&mut term, "aa\r\nbb\r\ncc\r\ndd\r\nee\r\nff");
term.execute(sgr(SetBoldIntensity));
term.execute(Su(2));
assert_eq!(text(&term), "cc\ndd\nee\nff\n\n |");
assert!(term.view().last().unwrap()[0].pen().is_bold());
let mut term = Terminal::new((4, 6), None);
feed(&mut term, "aa\r\nbb\r\ncc\r\ndd\r\nee\r\nff");
term.execute(Decstbm(2, 5));
term.execute(Cup(1, 1));
term.execute(Su(2));
assert_eq!(text(&term), "|aa\ndd\nee\n\n\nff");
let mut term = Terminal::new((4, 6), None);
feed(&mut term, "aaaaaa\r\nbbbbbb\r\ncccccc");
term.execute(Su(2));
assert_eq!(text(&term), "bbbb\nbb\ncccc\ncc\n\n |");
assert_eq!(wrapped(&term), vec![true, false, true, false, false, false]);
let mut term = Terminal::new((4, 6), None);
feed(&mut term, "aaaaaa\r\nbbbbbb\r\ncccccc");
term.execute(Decstbm(2, 5));
term.execute(Cup(1, 1));
term.execute(Su(2));
assert_eq!(text(&term), "|aaaa\nbb\ncccc\n\n\ncc");
assert_eq!(
wrapped(&term),
vec![false, false, false, false, false, false]
);
}
#[test]
fn execute_sd() {
let mut term = Terminal::new((4, 6), None);
feed(&mut term, "aa\r\nbb\r\ncc\r\ndd\r\nee\r\nff");
term.execute(Sd(2));
assert_eq!(text(&term), "\n\naa\nbb\ncc\ndd|");
let mut term = Terminal::new((4, 6), None);
feed(&mut term, "aa\r\nbb\r\ncc\r\ndd\r\nee\r\nff");
term.execute(Decstbm(2, 5));
term.execute(Cup(1, 1));
term.execute(Sd(2));
assert_eq!(text(&term), "|aa\n\n\nbb\ncc\nff");
let mut term = Terminal::new((4, 6), None);
feed(&mut term, "aaaaaa\r\nbbbbbb\r\ncccccc");
term.execute(Sd(2));
assert_eq!(text(&term), "\n\naaaa\naa\nbbbb\nbb|");
assert_eq!(wrapped(&term), vec![false, false, true, false, true, false]);
let mut term = Terminal::new((4, 6), None);
feed(&mut term, "aaaaaa\r\nbbbbbb\r\ncccccc");
term.execute(Decstbm(2, 5));
term.execute(Cup(1, 1));
term.execute(Sd(2));
assert_eq!(text(&term), "|aaaa\n\n\naa\nbbbb\ncc");
assert_eq!(
wrapped(&term),
vec![false, false, false, false, false, false]
);
}
#[test]
fn charsets() {
let mut term = build_term(6, 7, 0, 0, "");
feed(&mut term, "alpty\r\n");
term.execute(Gzd4(Charset::Drawing));
feed(&mut term, "alpty\r\n");
term.execute(So);
feed(&mut term, "alpty\r\n");
term.execute(G1d4(Charset::Drawing));
feed(&mut term, "alpty\r\n");
term.execute(G1d4(Charset::Ascii));
feed(&mut term, "alpty\r\n");
term.execute(Gzd4(Charset::Ascii));
term.execute(Si);
feed(&mut term, "alpty");
assert_eq!(text(&term), "alpty\n▒┌⎻├≤\nalpty\n▒┌⎻├≤\nalpty\nalpty|\n");
}
#[test]
fn execute_decaln() {
let mut term = Terminal::new((4, 2), None);
feed(&mut term, "ab\r\nc");
term.execute(Cup(2, 3));
term.execute(Decaln);
assert_eq!(term.cursor(), (2, 1));
assert_eq!(text(&term), "EEEE\nEE|EE");
}
#[test]
fn resize_wider() {
let mut term = Terminal::new((6, 6), None);
term.resize(7, 6);
assert_eq!(text(&term), "|\n\n\n\n\n");
assert!(!term.view().any(|l| l.wrapped));
term.resize(15, 6);
assert_eq!(text(&term), "|\n\n\n\n\n");
assert!(!term.view().any(|l| l.wrapped));
let mut term = Terminal::new((6, 6), None);
feed(&mut term, "000000111111222222333333444444555");
assert_eq!(text(&term), "000000\n111111\n222222\n333333\n444444\n555|");
assert_eq!(wrapped(&term), vec![true, true, true, true, true, false]);
term.resize(7, 6);
assert_eq!(text(&term), "0000001\n1111122\n2222333\n3334444\n44555|\n");
assert_eq!(wrapped(&term), vec![true, true, true, true, false, false]);
term.resize(15, 6);
assert_eq!(text(&term), "000000111111222\n222333333444444\n555|\n\n\n");
assert_eq!(wrapped(&term), vec![true, true, false, false, false, false]);
let mut term = Terminal::new((4, 3), None);
feed(&mut term, "000011\r\n22");
assert_eq!(text(&term), "0000\n11\n22|");
assert_eq!(wrapped(&term), vec![true, false, false]);
term.resize(8, 3);
assert_eq!(text(&term), "000011\n22|\n");
assert_eq!(wrapped(&term), vec![false, false, false]);
}
#[test]
fn resize_narrower() {
let mut term = Terminal::new((15, 6), None);
term.resize(7, 6);
assert_eq!(text(&term), "|\n\n\n\n\n");
assert!(!term.view().any(|l| l.wrapped));
term.resize(6, 6);
assert_eq!(text(&term), "|\n\n\n\n\n");
assert!(!term.view().any(|l| l.wrapped));
let mut term = Terminal::new((8, 2), None);
feed(&mut term, "\nabcdef");
assert_eq!(wrapped(&term), vec![false, false]);
term.resize(4, 2);
assert_eq!(text(&term), "abcd\nef|");
assert_eq!(wrapped(&term), vec![true, false]);
let mut term = Terminal::new((15, 6), None);
feed(&mut term, "000000111111222222333333444444555");
assert_eq!(text(&term), "000000111111222\n222333333444444\n555|\n\n\n");
assert_eq!(wrapped(&term), vec![true, true, false, false, false, false]);
term.resize(7, 6);
assert_eq!(text(&term), "2222333\n3334444\n44555|\n\n\n");
assert_eq!(wrapped(&term), vec![true, true, false, false, false, false]);
term.resize(6, 6);
assert_eq!(text(&term), "333333\n444444\n555|\n\n\n");
assert_eq!(wrapped(&term), vec![true, true, false, false, false, false]);
}
#[test]
fn resize() {
let mut term = Terminal::new((8, 4), None);
feed(&mut term, "abcdefgh\r\nijklmnop\r\nqrstuw");
term.execute(Cup(4, 1));
feed(&mut term, "AAA");
term.resize(8, 5);
assert_eq!(text(&term), "abcdefgh\nijklmnop\nqrstuw\nAAA|\n");
feed(&mut term, "BBBBB");
assert_eq!(term.cursor(), (8, 3));
term.resize(4, 5);
assert_eq!(text(&term), "qrst\nuw\nAAAB\nBBB|B\n");
feed(&mut term, "\rCCC");
assert_eq!(text(&term), "qrst\nuw\nAAAB\nCCC|B\n");
assert_eq!(wrapped(&term), vec![true, false, true, false, false]);
term.resize(3, 5);
assert_eq!(text(&term), "tuw\nAAA\nBCC\nC|B\n");
term.resize(5, 5);
assert_eq!(text(&term), "qrstu\nw\nAAABC\nCC|B\n");
feed(&mut term, "DDD");
term.resize(6, 5);
assert_eq!(text(&term), "op\nqrstuw\nAAABCC\nCDDD|\n");
}
#[test]
fn resize_taller() {
let mut term = Terminal::new((6, 4), None);
feed(&mut term, "AAA\n\rBBB\n\r");
term.resize(6, 5);
assert_eq!(text(&term), "AAA\nBBB\n|\n\n");
}
#[test]
fn resize_shorter() {
let mut term = Terminal::new((6, 6), None);
feed(&mut term, "AAA\n\rBBB\n\rCCC\n\r");
term.resize(6, 5);
assert_eq!(text(&term), "AAA\nBBB\nCCC\n|\n");
term.resize(6, 3);
assert_eq!(text(&term), "BBB\nCCC\n|");
term.resize(6, 2);
assert_eq!(text(&term), "CCC\n|");
}
#[test]
fn resize_vs_buffer_switching() {
let mut term = Terminal::new((4, 4), None);
feed(&mut term, "aaa\n\rbbb\n\rc\n\rddd");
assert_eq!(term.cursor(), (3, 3));
term.resize(4, 5);
assert_eq!(text(&term), "aaa\nbbb\nc\nddd|\n");
term.execute(Decset(dec_modes([DecMode::SaveCursorAltScreenBuffer])));
assert_eq!(term.cursor(), (3, 3));
term.resize(4, 2);
assert_eq!(term.cursor(), (3, 1));
term.resize(2, 3);
term.resize(3, 3);
term.execute(Decrst(dec_modes([DecMode::SaveCursorAltScreenBuffer])));
assert_eq!(text(&term), "bbb\nc\ndd|d");
}
#[test]
fn execute_new_line_mode() {
let mut term = build_term(8, 2, 3, 0, "abc");
term.execute(Sm(ansi_modes([AnsiMode::NewLine])));
term.execute(Lf);
assert_eq!(term.cursor(), (0, 1));
term.execute(Rm(ansi_modes([AnsiMode::NewLine])));
term.execute(Cup(1, 4));
term.execute(Lf);
assert_eq!(term.cursor(), (3, 1));
}
#[test]
fn execute_cursor_keys_mode() {
let mut term = Terminal::new((4, 2), None);
assert!(!term.cursor_keys_app_mode());
term.execute(Decset(dec_modes([DecMode::CursorKeys])));
assert!(term.cursor_keys_app_mode());
term.execute(Decrst(dec_modes([DecMode::CursorKeys])));
assert!(!term.cursor_keys_app_mode());
}
#[test]
fn execute_text_cursor_visibility_mode() {
let mut term = Terminal::new((4, 2), None);
assert!(term.cursor.visible);
term.execute(Decrst(dec_modes([DecMode::TextCursorEnable])));
assert!(!term.cursor.visible);
term.execute(Decset(dec_modes([DecMode::TextCursorEnable])));
assert!(term.cursor.visible);
}
#[test]
fn execute_origin_mode() {
let mut term = Terminal::new((4, 4), None);
term.execute(Decstbm(2, 4));
term.execute(Decset(dec_modes([DecMode::Origin])));
assert_eq!(term.cursor(), (0, 1));
term.execute(Cup(2, 1));
assert_eq!(term.cursor(), (0, 2));
term.execute(Decrst(dec_modes([DecMode::Origin])));
assert_eq!(term.cursor(), (0, 0));
}
#[test]
fn execute_alt_screen_buffer_mode() {
let mut term = Terminal::new((4, 3), None);
feed(&mut term, "ab\r\ncd");
assert_eq!(term.active_buffer_type(), BufferType::Primary);
assert_eq!(text(&term), "ab\ncd|\n");
term.execute(Decset(dec_modes([DecMode::AltScreenBuffer])));
assert_eq!(term.active_buffer_type(), BufferType::Alternate);
assert_eq!(text(&term), "\n |\n");
feed(&mut term, "xy");
assert_eq!(text(&term), "\n xy|\n");
term.execute(Decrst(dec_modes([DecMode::AltScreenBuffer])));
assert_eq!(term.active_buffer_type(), BufferType::Primary);
assert_eq!(text(&term), "ab\ncd |\n");
}
#[test]
fn execute_save_cursor_mode() {
let mut term = Terminal::new((4, 4), None);
term.execute(Decrst(dec_modes([DecMode::AutoWrap])));
term.execute(Decstbm(2, 4));
term.execute(Decset(dec_modes([DecMode::Origin])));
term.execute(Cup(2, 3));
term.execute(sgr(SetBoldIntensity));
term.execute(Decset(dec_modes([DecMode::SaveCursor])));
term.execute(Decset(dec_modes([DecMode::AutoWrap])));
term.execute(Decrst(dec_modes([DecMode::Origin])));
term.execute(Cup(4, 4));
term.execute(sgr(Reset));
term.execute(Decrst(dec_modes([DecMode::SaveCursor])));
assert_eq!(term.cursor(), (2, 2));
assert_eq!(term.pen.intensity, Intensity::Bold);
assert!(term.origin_mode);
assert!(!term.auto_wrap_mode);
}
#[test]
fn execute_decstr() {
let mut term = Terminal::new((4, 3), None);
feed(&mut term, "ab");
term.execute(Decset(dec_modes([DecMode::AltScreenBuffer])));
feed(&mut term, "xy");
term.execute(Decrst(dec_modes([DecMode::TextCursorEnable])));
term.execute(Sm(ansi_modes([AnsiMode::Insert, AnsiMode::NewLine])));
term.execute(Decrst(dec_modes([DecMode::AutoWrap])));
term.execute(Decset(dec_modes([DecMode::CursorKeys])));
term.execute(Decstbm(2, 3));
term.execute(Decset(dec_modes([DecMode::Origin])));
term.execute(Cup(2, 3));
term.execute(sgr(SetBoldIntensity));
term.execute(G1d4(Charset::Drawing));
term.execute(So);
term.execute(Decsc);
assert_eq!(term.active_buffer_type(), BufferType::Alternate);
assert_eq!(term.cursor(), (2, 2));
assert_eq!(text(&term), " xy\n\n |");
assert!(!term.cursor.visible);
assert!(term.insert_mode);
assert!(term.origin_mode);
assert!(!term.auto_wrap_mode);
assert!(term.new_line_mode);
assert!(term.cursor_keys_app_mode());
assert_eq!(term.top_margin, 1);
assert_eq!(term.bottom_margin, 2);
assert_eq!(term.charsets, [Charset::Ascii, Charset::Drawing]);
assert_eq!(term.active_charset, 1);
assert_eq!(term.saved_ctx.cursor_col, 2);
assert_eq!(term.saved_ctx.cursor_row, 2);
assert_eq!(term.pen.intensity, Intensity::Bold);
term.execute(Decstr);
assert_eq!(term.active_buffer_type(), BufferType::Alternate);
assert_eq!(term.cursor(), (2, 2));
assert_eq!(text(&term), " xy\n\n |");
assert!(term.cursor.visible);
assert!(!term.insert_mode);
assert!(!term.origin_mode);
assert!(!term.auto_wrap_mode);
assert!(term.new_line_mode);
assert!(term.cursor_keys_app_mode());
assert_eq!(term.top_margin, 0);
assert_eq!(term.bottom_margin, 2);
assert_eq!(term.charsets, [Charset::Ascii, Charset::Ascii]);
assert_eq!(term.active_charset, 0);
assert_eq!(term.saved_ctx, super::SavedCtx::default());
assert_eq!(term.pen, crate::pen::Pen::default());
}
#[test]
fn execute_ris() {
let mut term = Terminal::new((4, 3), None);
feed(&mut term, "ab\r\ncd");
term.execute(sgr(SetBoldIntensity));
term.execute(Decset(dec_modes([
DecMode::CursorKeys,
DecMode::SaveCursorAltScreenBuffer,
])));
feed(&mut term, "zz");
term.execute(Ris);
assert_eq!(term.active_buffer_type(), BufferType::Primary);
assert_eq!(term.cursor(), (0, 0));
assert_eq!(text(&term), "|\n\n");
assert_eq!(term.pen, crate::pen::Pen::default());
assert!(!term.cursor_keys_app_mode());
}
#[test]
fn resize_vs_tabs() {
let mut term = Terminal::new((6, 2), None);
assert_eq!(term.tabs, vec![]);
term.resize(10, 2);
assert_eq!(term.tabs, vec![8]);
term.resize(30, 2);
assert_eq!(term.tabs, vec![8, 16, 24]);
term.resize(20, 2);
assert_eq!(term.tabs, vec![8, 16]);
}
#[test]
fn resize_vs_saved_ctx() {
use DecMode::*;
let mut term = Terminal::new((20, 5), None);
term.execute(Cuf(15));
assert_eq!(term.cursor.col, 15);
term.execute(Decsc);
assert_eq!(term.saved_ctx.cursor_col, 15);
term.execute(Decset(dec_modes([AltScreenBuffer])));
term.execute(Decsc);
assert_eq!(term.saved_ctx.cursor_col, 15);
term.resize(10, 5);
assert_eq!(term.saved_ctx.cursor_col, 9);
}
}