use super::GameSetting;
use slog::Logger;
use sloggers::Build;
use sloggers::file::FileLoggerBuilder;
use sloggers::null::NullLoggerBuilder;
use vte::Perform;
use std::str;
use std::default::Default;
use std::cmp::min;
#[derive(Copy, Clone, Debug, Default)]
struct Cursor {
x: usize,
y: usize,
}
impl Cursor {
fn new(x: usize, y: usize) -> Cursor {
Cursor { x: x, y: y }
}
}
#[derive(Clone, Copy, Debug, Default)]
struct LineRange(usize, usize);
impl LineRange {
fn contains(&self, u: usize) -> bool {
self.0 <= u && u < self.1
}
}
#[derive(Debug)]
pub struct TermData {
buf: Vec<Vec<u8>>,
cur: Cursor,
height: usize,
width: usize,
mode: TermMode,
scroll_range: LineRange,
saved_cur: Cursor,
pub logger: Logger,
preceeding: Option<u8>,
}
impl TermData {
pub fn from_setting(s: &GameSetting) -> TermData {
TermData {
buf: vec![vec![b' '; s.columns]; s.lines],
cur: Cursor::default(),
height: s.lines,
width: s.columns,
mode: TermMode::default(),
scroll_range: LineRange(0, s.lines),
saved_cur: Cursor::default(),
logger: if !s.log_info.fname.is_empty() {
let mut builder = FileLoggerBuilder::new(&s.log_info.fname);
builder.level(s.log_info.sev);
builder.truncate();
builder.build()
} else {
NullLoggerBuilder {}.build()
}.ok()
.unwrap(),
preceeding: None,
}
}
fn from_buf(buf: Vec<Vec<u8>>) -> TermData {
TermData {
cur: Cursor::default(),
height: buf.len(),
width: buf[0].len(),
mode: TermMode::default(),
scroll_range: LineRange(0, buf.len()),
saved_cur: Cursor::default(),
logger: NullLoggerBuilder {}.build().ok().unwrap(),
preceeding: None,
buf: buf,
}
}
pub fn ret_screen(&self) -> Vec<Vec<u8>> {
self.buf.clone()
}
fn is_cursor_valid(&self) -> bool {
self.cur.y < self.height && self.cur.x < self.width
}
fn assert_cursor(&self) {
assert!(
self.is_cursor_valid(),
"Cursor has invalid val!, {:?}",
self.cur
);
}
fn input(&mut self, c: u8) {
trace!(self.logger, "(input) c: {}", c);
while self.cur.x >= self.width {
if !self.mode.contains(TermMode::LINE_WRAP) {
return;
}
self.cur.x -= self.width;
self.linefeed();
}
self.assert_cursor();
self.buf[self.cur.y][self.cur.x] = c;
self.preceeding = Some(c);
self.cur.x += 1;
}
fn carriage_return(&mut self) {
debug!(self.logger, "(carriage_return)");
self.cur.x = 0;
}
fn linefeed(&mut self) {
debug!(
self.logger,
"line_feed, cur: {:?}, range: {:?}", self.cur, self.scroll_range
);
let nxt = self.cur.y + 1;
if nxt == self.scroll_range.1 {
self.scroll_up(1);
} else if nxt < self.height {
self.cur.y += 1;
}
}
fn backspace(&mut self) {
trace!(self.logger, "(backspace)");
if self.cur.x > 0 {
self.cur.x -= 1;
}
}
fn newline(&mut self) {
self.linefeed();
if self.mode.contains(TermMode::LINE_FEED_NEW_LINE) {
self.carriage_return();
}
}
fn add_x(&mut self, num: usize) {
self.cur.x += num;
}
fn add_y(&mut self, num: usize) {
self.cur.y += num;
assert!(self.cur.y < self.height);
}
fn sub_x(&mut self, num: usize) {
assert!(self.cur.x >= num);
self.cur.x -= num;
}
fn sub_y(&mut self, num: usize) {
assert!(self.cur.y >= num);
self.cur.y -= num;
}
fn goto_x(&mut self, num: usize) {
self.cur.x = num;
}
fn goto_y(&mut self, num: usize) {
self.cur.y = num;
assert!(self.cur.y < self.height);
}
fn goto(&mut self, c: Cursor) {
self.cur = c;
}
fn clear_scr(&mut self, mode: ClearMode) {
debug!(self.logger, "(clear_scr): {:?}", mode);
match mode {
ClearMode::All => for i in 0..self.height {
for j in 0..self.width {
self.buf[i][j] = b' ';
}
},
ClearMode::Above => {
for i in 0..self.cur.y {
for j in 0..self.width {
self.buf[i][j] = b' ';
}
}
for j in 0..(self.cur.x + 1) {
self.buf[self.cur.y][j] = b' ';
}
}
ClearMode::Below => {
for i in (self.cur.y + 1)..self.height {
for j in 0..self.width {
self.buf[i][j] = b' ';
}
}
for j in self.cur.x..self.width {
self.buf[self.cur.y][j] = b' ';
}
}
ClearMode::Saved => {}
}
}
fn clear_line(&mut self, mode: LineClearMode) {
debug!(self.logger, "(clear_line): {:?}", mode);
match mode {
LineClearMode::Right => for i in self.cur.x..self.width {
self.buf[self.cur.y][i] = b' ';
},
LineClearMode::Left => for i in 0..self.cur.x + 1 {
self.buf[self.cur.y][i] = b' ';
},
LineClearMode::All => for i in 0..self.width {
self.buf[self.cur.y][i] = b' ';
},
}
}
fn scroll_up(&mut self, num: usize) {
let origin = self.scroll_range.0;
self.scroll_up_relative(origin, num);
}
fn scroll_up_relative(&mut self, origin: usize, num: usize) {
assert!(
self.scroll_range.contains(origin),
"scroll_down_relative: invalid origin! {}, {:?}",
origin,
self.scroll_range
);
debug!(
self.logger,
"scroll_down_relative: origin={}, num={}", origin, num
);
let mut tmp = self.buf.clone();
{
let buf = &self.buf[origin..self.scroll_range.1];
for (i, tmp_v) in tmp[origin..self.scroll_range.1].iter_mut().enumerate() {
if origin + i < self.scroll_range.1 - num {
if let Some(buf_v) = buf.get(i + num) {
*tmp_v = buf_v.clone();
}
} else {
tmp_v.iter_mut().for_each(|x| *x = b' ');
}
}
}
self.buf = tmp;
}
fn scroll_down(&mut self, num: usize) {
let origin = self.scroll_range.0;
self.scroll_down_relative(origin, num);
}
fn scroll_down_relative(&mut self, origin: usize, num: usize) {
assert!(
self.scroll_range.contains(origin),
"scroll_up_relative: invalid origin! {}, {:?}",
origin,
self.scroll_range
);
debug!(
self.logger,
"scroll_up_relative: origin={}, num={}", origin, num
);
let mut tmp = self.buf.clone();
{
let buf = &self.buf[origin..self.scroll_range.1];
for (i, tmp_v) in tmp[origin..self.scroll_range.1].iter_mut().enumerate() {
if i >= num {
if let Some(buf_v) = buf.get(i - num) {
*tmp_v = buf_v.clone();
}
} else {
tmp_v.iter_mut().for_each(|x| *x = b' ');
}
}
}
self.buf = tmp;
}
fn insert_blank_lines(&mut self, num: usize) {
trace!(self.logger, "insert_blank_lines, {}", num);
if self.scroll_range.contains(self.cur.y) {
let origin = self.cur.y;
self.scroll_down_relative(origin, num);
}
}
fn delete_lines(&mut self, num: usize) {
trace!(self.logger, "delete_lines, {}", num);
if self.scroll_range.contains(self.cur.y) {
let origin = self.cur.y;
self.scroll_up_relative(origin, num);
}
}
fn insert_blank_chars(&mut self, num: usize) {
trace!(self.logger, "insert_blank_chars, {}", num);
let mut tmp = vec![b' '; self.width];
for j in 0..self.width {
if j < self.cur.x {
tmp[j] = self.buf[self.cur.y][j];
} else if j >= self.cur.x + num {
tmp[j] = self.buf[self.cur.y][j - num];
}
}
self.buf[self.cur.y] = tmp;
}
fn erase_chars(&mut self, num: usize) {
trace!(self.logger, "erase_chars, {}", num);
for j in self.cur.x..min(self.cur.x + num, self.width) {
self.buf[self.cur.y][j] = b' ';
}
}
fn delete_chars(&mut self, num: usize) {
trace!(self.logger, "delete_chars, {}", num);
let mut tmp = vec![b' '; self.width];
for j in 0..self.width {
if j < self.cur.x {
tmp[j] = self.buf[self.cur.y][j];
} else if j + num < self.width {
tmp[j] = self.buf[self.cur.y][j + num];
}
}
self.buf[self.cur.y] = tmp;
}
fn deccolm(&self) {}
fn unset_mode(&mut self, mode: ModeInt) {
debug!(self.logger, "unset_mode: {:?}", mode);
match mode {
ModeInt::SwapScreenAndSetRestoreCursor => self.restore_cursor(),
ModeInt::ShowCursor => self.mode.remove(TermMode::SHOW_CURSOR),
ModeInt::CursorKeys => self.mode.remove(TermMode::APP_CURSOR),
ModeInt::ReportMouseClicks => self.mode.remove(TermMode::MOUSE_REPORT_CLICK),
ModeInt::ReportMouseMotion => self.mode.remove(TermMode::MOUSE_MOTION),
ModeInt::ReportFocusInOut => self.mode.remove(TermMode::FOCUS_IN_OUT),
ModeInt::BracketedPaste => self.mode.remove(TermMode::BRACKETED_PASTE),
ModeInt::SgrMouse => self.mode.remove(TermMode::SGR_MOUSE),
ModeInt::LineWrap => self.mode.remove(TermMode::LINE_WRAP),
ModeInt::LineFeedNewLine => self.mode.remove(TermMode::LINE_FEED_NEW_LINE),
ModeInt::Origin => self.mode.remove(TermMode::ORIGIN),
ModeInt::DECCOLM => self.deccolm(),
ModeInt::Insert => self.mode.remove(TermMode::INSERT),
_ => trace!(self.logger, "ignoring unset_mode"),
}
}
fn set_mode(&mut self, mode: ModeInt) {
debug!(self.logger, "set_mode: {:?}", mode);
match mode {
ModeInt::SwapScreenAndSetRestoreCursor => self.restore_cursor(),
ModeInt::ShowCursor => self.mode.insert(TermMode::SHOW_CURSOR),
ModeInt::CursorKeys => self.mode.insert(TermMode::APP_CURSOR),
ModeInt::ReportMouseClicks => self.mode.insert(TermMode::MOUSE_REPORT_CLICK),
ModeInt::ReportMouseMotion => self.mode.insert(TermMode::MOUSE_MOTION),
ModeInt::ReportFocusInOut => self.mode.insert(TermMode::FOCUS_IN_OUT),
ModeInt::BracketedPaste => self.mode.insert(TermMode::BRACKETED_PASTE),
ModeInt::SgrMouse => self.mode.insert(TermMode::SGR_MOUSE),
ModeInt::LineWrap => self.mode.insert(TermMode::LINE_WRAP),
ModeInt::LineFeedNewLine => self.mode.insert(TermMode::LINE_FEED_NEW_LINE),
ModeInt::Origin => self.mode.insert(TermMode::ORIGIN),
ModeInt::DECCOLM => self.deccolm(),
ModeInt::Insert => self.mode.insert(TermMode::INSERT),
_ => trace!(self.logger, "ignoring set_mode"),
}
}
fn set_keyboard_app_mode(&mut self) {
self.mode.insert(TermMode::APP_KEYPAD);
}
fn unset_keyboard_app_mode(&mut self) {
self.mode.remove(TermMode::APP_KEYPAD);
}
fn save_cursor(&mut self) {
trace!(self.logger, "save_cursor");
self.saved_cur = self.cur;
}
fn restore_cursor(&mut self) {
trace!(self.logger, "restore_cursor");
self.cur = self.saved_cur;
}
fn reverse_index(&mut self) {
trace!(self.logger, "reverse_index");
if self.cur.y == self.scroll_range.0 {
self.scroll_down(1);
} else if self.cur.y > 0 {
self.sub_y(1);
}
}
fn dectest(&mut self) {
unimplemented!("dectest");
}
}
impl Perform for TermData {
fn print(&mut self, c: char) {
trace!(self.logger, "(print) c: {:?} cursor: {:?}", c, self.cur);
if !c.is_ascii() {
warn!(self.logger, "Non Ascii char Input!");
}
self.input(c as u8);
}
fn execute(&mut self, byte: u8) {
trace!(
self.logger,
"(exectute) byte: {:?}({:x})",
byte as char,
byte
);
match byte {
C0::BS => self.backspace(), C0::CR => self.carriage_return(),
C0::LF | C0::VT | C0::FF => self.linefeed(),
C1::NEL => self.newline(),
_ => warn!(self.logger, "[unhandled!(execute)] byte={:02x}", byte),
}
}
fn csi_dispatch(&mut self, args: &[i64], intermediates: &[u8], _ignore: bool, action: char) {
let private = intermediates.get(0).map(|b| *b == b'?').unwrap_or(false);
macro_rules! unhandled {
() => {{
warn!(self.logger, "[unhandled! (CSI)] action={:?}, args={:?}, intermediates={:?}",
action, args, intermediates);
return;
}}
}
let args_or = |id: usize, default: i64| -> i64 {
if id >= args.len() {
default
} else {
args[id]
}
};
trace!(
self.logger,
"(CSI) private = {:?}, action={:?}, args={:?}, intermediates={:?}",
private,
action,
args,
intermediates
);
match action {
'@' => self.insert_blank_chars(args_or(0, 1) as _),
'A' => self.sub_y(args_or(0, 1) as _),
'b' => match self.preceeding {
Some(c) => for _ in 0..args_or(0, 1) {
self.input(c);
},
None => warn!(self.logger, "Try repeating with No Precceding Char!"),
},
'B' | 'e' => self.add_y(args_or(0, 1) as _), 'C' | 'a' => self.add_x(args_or(0, 1) as _), 'D' => self.sub_x(args_or(0, 1) as _), 'E' => {
self.add_y(args_or(0, 1) as _);
self.carriage_return();
}
'F' => {
self.sub_y(args_or(0, 1) as _);
self.carriage_return();
}
'G' | '`' => self.goto_x(args_or(0, 1) as usize - 1),
'H' | 'f' => {
let y = args_or(0, 1) as usize - 1;
let x = args_or(1, 1) as usize - 1;
self.goto(Cursor::new(x, y));
}
'J' => {
let mode = match args_or(0, 0) {
0 => ClearMode::Below,
1 => ClearMode::Above,
2 => ClearMode::All,
3 => ClearMode::Saved,
_ => unhandled!(),
};
self.clear_scr(mode);
}
'K' => {
let mode = match args_or(0, 0) {
0 => LineClearMode::Right,
1 => LineClearMode::Left,
2 => LineClearMode::All,
_ => unhandled!(),
};
self.clear_line(mode);
}
'S' => self.scroll_up(args_or(0, 1) as _),
'T' => self.scroll_down(args_or(0, 1) as _),
'L' => self.insert_blank_lines(args_or(0, 1) as _),
'l' => {
let mode = ModeInt::from_primitive(private, args_or(0, 0));
trace!(self.logger, "unset mode {:?}", mode);
match mode {
Some(m) => self.unset_mode(m),
None => unhandled!(),
}
}
'M' => self.delete_lines(args_or(0, 1) as _),
'X' => self.erase_chars(args_or(0, 1) as _),
'P' => self.delete_chars(args_or(0, 1) as _),
'd' => self.goto_y(args_or(0, 1) as usize - 1),
'h' => {
let mode = ModeInt::from_primitive(private, args_or(0, 0));
trace!(self.logger, "mode {:?}", mode);
match mode {
Some(m) => self.set_mode(m),
None => unhandled!(),
}
}
'r' => {
if private {
unhandled!();
}
let top = args_or(0, 1) as usize - 1;
let bottom = args_or(1, self.height as _) as usize;
self.scroll_range = LineRange(top, bottom);
}
's' => self.save_cursor(),
'u' => self.restore_cursor(),
_ => {}
}
}
fn esc_dispatch(&mut self, params: &[i64], intermediates: &[u8], _ignore: bool, byte: u8) {
macro_rules! unhandled {
() => {{
warn!(self.logger, "[unhandled! (ESC)] params={:?}, ints={:?}, byte={:?} ({:x})",
params, intermediates, byte as char, byte);
return;
}}
}
trace!(
self.logger,
"(ESC) params={:?}, ints={:?}, byte={:?} ({:02x})",
params,
intermediates,
byte as char,
byte
);
match byte {
b'D' => self.add_y(1),
b'E' => {
self.add_y(1);
self.goto_x(0);
}
b'M' => self.reverse_index(),
b'7' => self.save_cursor(),
b'8' => {
if !intermediates.is_empty() && intermediates[0] == b'#' {
self.dectest();
} else {
self.restore_cursor();
}
}
b'>' => self.set_keyboard_app_mode(),
b'=' => self.unset_keyboard_app_mode(),
b'\\' => {}
_ => unhandled!(),
}
}
fn osc_dispatch(&mut self, params: &[&[u8]]) {
debug!(
self.logger,
"[ignored! (osc_dispatch)]: {}",
str::from_utf8(params[0]).unwrap()
);
}
fn hook(&mut self, params: &[i64], intermediates: &[u8], ignore: bool) {
debug!(
self.logger,
"[unhandled! (hook)] params={:?}, ints: {:?}, ignore: {:?}",
params,
intermediates,
ignore
);
}
fn put(&mut self, byte: u8) {
debug!(self.logger, "[unhandled! (put)] byte={:?}", byte);
}
fn unhook(&mut self) {
debug!(self.logger, "[unhandled! (unhook)]");
}
}
bitflags! {
pub struct TermMode: u16 {
const SHOW_CURSOR = 0b000000000001;
const APP_CURSOR = 0b000000000010;
const APP_KEYPAD = 0b000000000100;
const MOUSE_REPORT_CLICK = 0b000000001000;
const BRACKETED_PASTE = 0b000000010000;
const SGR_MOUSE = 0b000000100000;
const MOUSE_MOTION = 0b000001000000;
const LINE_WRAP = 0b000010000000;
const LINE_FEED_NEW_LINE = 0b000100000000;
const ORIGIN = 0b001000000000;
const INSERT = 0b010000000000;
const FOCUS_IN_OUT = 0b100000000000;
const ANY = 0b111111111111;
const NONE = 0;
}
}
impl Default for TermMode {
fn default() -> TermMode {
TermMode::SHOW_CURSOR | TermMode::LINE_WRAP
}
}
#[derive(Debug, Eq, PartialEq)]
#[allow(dead_code)]
enum ModeInt {
CursorKeys = 1,
DECCOLM = 3,
Insert = 4,
Origin = 6,
LineWrap = 7,
BlinkingCursor = 12,
LineFeedNewLine = 20,
ShowCursor = 25,
ReportMouseClicks = 1000,
ReportMouseMotion = 1002,
ReportFocusInOut = 1004,
SgrMouse = 1006,
SwapScreenAndSetRestoreCursor = 1049,
BracketedPaste = 2004,
}
impl ModeInt {
fn from_primitive(private: bool, num: i64) -> Option<ModeInt> {
if private {
Some(match num {
1 => ModeInt::CursorKeys,
3 => ModeInt::DECCOLM,
6 => ModeInt::Origin,
7 => ModeInt::LineWrap,
12 => ModeInt::BlinkingCursor,
25 => ModeInt::ShowCursor,
1000 => ModeInt::ReportMouseClicks,
1002 => ModeInt::ReportMouseMotion,
1004 => ModeInt::ReportFocusInOut,
1006 => ModeInt::SgrMouse,
1049 => ModeInt::SwapScreenAndSetRestoreCursor,
2004 => ModeInt::BracketedPaste,
_ => return None,
})
} else {
Some(match num {
4 => ModeInt::Insert,
20 => ModeInt::LineFeedNewLine,
_ => return None,
})
}
}
}
#[derive(Debug, Clone, Copy)]
enum LineClearMode {
Right,
Left,
All,
}
#[derive(Debug, Clone, Copy)]
enum ClearMode {
Below,
Above,
All,
Saved,
}
#[allow(non_snake_case, dead_code)]
mod C0 {
pub const NUL: u8 = 0x00;
pub const SOH: u8 = 0x01;
pub const STX: u8 = 0x02;
pub const ETX: u8 = 0x03;
pub const EOT: u8 = 0x04;
pub const ENQ: u8 = 0x05;
pub const ACK: u8 = 0x06;
pub const BEL: u8 = 0x07;
pub const BS: u8 = 0x08;
pub const HT: u8 = 0x09;
pub const LF: u8 = 0x0A;
pub const VT: u8 = 0x0B;
pub const FF: u8 = 0x0C;
pub const CR: u8 = 0x0D;
pub const SO: u8 = 0x0E;
pub const SI: u8 = 0x0F;
pub const DLE: u8 = 0x10;
pub const XON: u8 = 0x11;
pub const DC2: u8 = 0x12;
pub const XOFF: u8 = 0x13;
pub const DC4: u8 = 0x14;
pub const NAK: u8 = 0x15;
pub const SYN: u8 = 0x16;
pub const ETB: u8 = 0x17;
pub const CAN: u8 = 0x18;
pub const EM: u8 = 0x19;
pub const SUB: u8 = 0x1A;
pub const ESC: u8 = 0x1B;
pub const FS: u8 = 0x1C;
pub const GS: u8 = 0x1D;
pub const RS: u8 = 0x1E;
pub const US: u8 = 0x1F;
pub const DEL: u8 = 0x7f;
}
#[allow(non_snake_case, dead_code)]
mod C1 {
pub const PAD: u8 = 0x80;
pub const HOP: u8 = 0x81;
pub const BPH: u8 = 0x82;
pub const NBH: u8 = 0x83;
pub const IND: u8 = 0x84;
pub const NEL: u8 = 0x85;
pub const SSA: u8 = 0x86;
pub const ESA: u8 = 0x87;
pub const HTS: u8 = 0x88;
pub const HTJ: u8 = 0x89;
pub const VTS: u8 = 0x8A;
pub const PLD: u8 = 0x8B;
pub const PLU: u8 = 0x8C;
pub const RI: u8 = 0x8D;
pub const SS2: u8 = 0x8E;
pub const SS3: u8 = 0x8F;
pub const DCS: u8 = 0x90;
pub const PU1: u8 = 0x91;
pub const PU2: u8 = 0x92;
pub const STS: u8 = 0x93;
pub const CCH: u8 = 0x94;
pub const MW: u8 = 0x95;
pub const SPA: u8 = 0x96;
pub const EPA: u8 = 0x97;
pub const SOS: u8 = 0x98;
pub const SGCI: u8 = 0x99;
pub const DECID: u8 = 0x9a;
pub const CSI: u8 = 0x9B;
pub const ST: u8 = 0x9C;
pub const OSC: u8 = 0x9D;
pub const PM: u8 = 0x9E;
pub const APC: u8 = 0x9F;
}
#[cfg(test)]
mod test {
use super::*;
use std::io::{BufRead, BufReader};
use str::from_utf8;
const MAP1: &str = "
----------------
|.....@........|
|........%.....|
|..............|
|..............|
------------+---
";
const MAP2: &str = "
|........%.....|
|..............|
|..............|
------------+---
";
const MAP3: &str = "
----------------
|.....@........|
|........%.....|
|..............|
";
const MAP4: &str = "
----------------
|.....@........|
|........%.....|
|..............|
";
const MAP5: &str = "
----------------
|..............|
|..............|
------------+---
";
const MAP6: &str = "
----------------
|.........|
|........%.....|
|..............|
|..............|
------------+---
";
const MAP7: &str = "
----------------
|..... @....
|........%.....|
|..............|
|..............|
------------+---
";
const MAP8: &str = "
----------------
|..... ....|
|........%.....|
|..............|
|..............|
------------+---
";
#[test]
fn test_scroll_up() {
let mut initial = TermData::from_buf(str_to_buf(MAP1));
initial.scroll_up(2);
println!("\n{}", &buf_to_str(&initial.buf));
assert_eq!(initial.buf, str_to_buf(&MAP2));
}
#[test]
fn test_scroll_down() {
let mut initial = TermData::from_buf(str_to_buf(MAP1));
initial.scroll_down(2);
println!("\n{}", &buf_to_str(&initial.buf));
assert_eq!(initial.buf, str_to_buf(&MAP3));
}
#[test]
fn test_insert_lines() {
let mut initial = TermData::from_buf(str_to_buf(MAP1));
initial.goto_y(3);
initial.insert_blank_lines(2);
println!("\n{}", &buf_to_str(&initial.buf));
assert_eq!(initial.buf, str_to_buf(&MAP4));
}
#[test]
fn test_delete_lines() {
let mut initial = TermData::from_buf(str_to_buf(MAP1));
initial.goto_y(1);
initial.delete_lines(2);
println!("\n{}", &buf_to_str(&initial.buf));
assert_eq!(initial.buf, str_to_buf(&MAP5));
}
#[test]
fn test_delete_chars() {
let mut initial = TermData::from_buf(str_to_buf(MAP1));
initial.goto_y(1);
initial.goto_x(14);
initial.delete_chars(5);
println!("\n{}", &buf_to_str(&initial.buf));
assert_eq!(initial.buf, str_to_buf(&MAP6));
}
#[test]
fn test_insert_chars() {
let mut initial = TermData::from_buf(str_to_buf(MAP1));
initial.goto_y(1);
initial.goto_x(14);
initial.insert_blank_chars(5);
println!("\n{}", &buf_to_str(&initial.buf));
assert_eq!(initial.buf, str_to_buf(&MAP7));
}
#[test]
fn test_erase_chars() {
let mut initial = TermData::from_buf(str_to_buf(MAP1));
initial.goto_y(1);
initial.goto_x(14);
initial.erase_chars(5);
println!("\n{}", &buf_to_str(&initial.buf));
assert_eq!(initial.buf, str_to_buf(&MAP8));
}
fn buf_to_str(buf: &Vec<Vec<u8>>) -> String {
let mut res = String::new();
let len = buf.len();
for (i, v) in buf.iter().enumerate() {
res.push_str(from_utf8(&v).unwrap());
if i < len - 1 {
res.push('\n');
}
}
res
}
fn str_to_buf(s: &str) -> Vec<Vec<u8>> {
let mut res = Vec::new();
let mut buf = String::new();
let mut reader = BufReader::new(s.as_bytes());
while let Ok(n) = reader.read_line(&mut buf) {
if n == 0 || buf.pop() != Some('\n') {
break;
}
if buf.is_empty() {
continue;
}
res.push(buf.as_bytes().to_owned());
buf.clear();
}
res
}
}