aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJesse Luehrs <doy@tozt.net>2019-11-09 02:23:54 -0500
committerJesse Luehrs <doy@tozt.net>2019-11-09 14:58:57 -0500
commit5970f3f473b915f61f8306263c15c70e2f962f7c (patch)
tree024f74a8629b475c68244ce0c4e38b5997cf532d
parentdc09dd29d0fe6741b17de3767cfc675c366fb1a4 (diff)
downloadvt100-rust-5970f3f473b915f61f8306263c15c70e2f962f7c.tar.gz
vt100-rust-5970f3f473b915f61f8306263c15c70e2f962f7c.zip
refactor terminal writing
-rw-r--r--src/attrs.rs120
-rw-r--r--src/grid.rs86
-rw-r--r--src/lib.rs1
-rw-r--r--src/row.rs107
-rw-r--r--src/screen.rs68
-rw-r--r--src/term.rs277
-rw-r--r--tests/text.rs12
-rw-r--r--tests/window_contents.rs17
8 files changed, 499 insertions, 189 deletions
diff --git a/src/attrs.rs b/src/attrs.rs
index 95a5e76..dc46646 100644
--- a/src/attrs.rs
+++ b/src/attrs.rs
@@ -1,3 +1,5 @@
+use std::io::Write as _;
+
/// Represents a foreground or background color for cells.
#[derive(Eq, PartialEq, Debug, Copy, Clone)]
pub enum Color {
@@ -87,89 +89,49 @@ impl Attrs {
}
}
- pub fn escape_code_diff(&self, other: &Self) -> Vec<u8> {
- let mut opts = vec![];
+ pub fn write_escape_code_diff(
+ &self,
+ contents: &mut Vec<u8>,
+ other: &Self,
+ ) {
+ let attrs = crate::term::Attrs::default();
if self != other && self == &Self::default() {
- return b"\x1b[m".to_vec();
- }
-
- if self.fgcolor != other.fgcolor {
- match self.fgcolor {
- Color::Default => {
- opts.push(39);
- }
- Color::Idx(i) => {
- if i < 8 {
- opts.push(i + 30);
- } else if i < 16 {
- opts.push(i + 82);
- } else {
- opts.push(38);
- opts.push(5);
- opts.push(i);
- }
- }
- Color::Rgb(r, g, b) => {
- opts.push(38);
- opts.push(2);
- opts.push(r);
- opts.push(g);
- opts.push(b);
- }
- }
- }
-
- if self.bgcolor != other.bgcolor {
- match self.bgcolor {
- Color::Default => {
- opts.push(49);
- }
- Color::Idx(i) => {
- if i < 8 {
- opts.push(i + 40);
- } else if i < 16 {
- opts.push(i + 92);
- } else {
- opts.push(48);
- opts.push(5);
- opts.push(i);
- }
- }
- Color::Rgb(r, g, b) => {
- opts.push(48);
- opts.push(2);
- opts.push(r);
- opts.push(g);
- opts.push(b);
- }
- }
+ write!(contents, "{}", attrs).unwrap();
+ return;
}
- if self.bold() != other.bold() {
- opts.push(if self.bold() { 1 } else { 22 });
- }
- if self.italic() != other.italic() {
- opts.push(if self.italic() { 3 } else { 23 });
- }
- if self.underline() != other.underline() {
- opts.push(if self.underline() { 4 } else { 24 });
- }
- if self.inverse() != other.inverse() {
- opts.push(if self.inverse() { 7 } else { 27 });
- }
+ let attrs = if self.fgcolor == other.fgcolor {
+ attrs
+ } else {
+ attrs.fgcolor(self.fgcolor)
+ };
+ let attrs = if self.bgcolor == other.bgcolor {
+ attrs
+ } else {
+ attrs.bgcolor(self.bgcolor)
+ };
+ let attrs = if self.bold() == other.bold() {
+ attrs
+ } else {
+ attrs.bold(self.bold())
+ };
+ let attrs = if self.italic() == other.italic() {
+ attrs
+ } else {
+ attrs.italic(self.italic())
+ };
+ let attrs = if self.underline() == other.underline() {
+ attrs
+ } else {
+ attrs.underline(self.underline())
+ };
+ let attrs = if self.inverse() == other.inverse() {
+ attrs
+ } else {
+ attrs.inverse(self.inverse())
+ };
- let mut bytes = b"\x1b[".to_vec();
- let mut first = true;
- for opt in opts {
- if first {
- first = false;
- } else {
- bytes.push(b';');
- }
- bytes.extend(opt.to_string().as_bytes());
- }
- bytes.push(b'm');
- bytes
+ write!(contents, "{}", attrs).unwrap();
}
}
diff --git a/src/grid.rs b/src/grid.rs
index fa5d437..287e5e4 100644
--- a/src/grid.rs
+++ b/src/grid.rs
@@ -1,4 +1,6 @@
use std::convert::TryInto as _;
+use std::fmt::Write as _;
+use std::io::Write as _;
#[derive(Clone, Debug)]
pub struct Grid {
@@ -125,30 +127,42 @@ impl Grid {
.expect("cursor not pointing to a cell")
}
- pub fn contents(&self) -> String {
- let mut contents = String::new();
+ pub fn write_contents(&self, contents: &mut String) {
for row in self.rows() {
- contents += &row.contents(0, self.size.cols);
+ row.write_contents(contents, 0, self.size.cols);
if !row.wrapped() {
- contents += "\n";
+ writeln!(contents).unwrap();
}
}
- contents.trim_end().to_string()
+
+ while contents.ends_with('\n') {
+ contents.truncate(contents.len() - 1);
+ }
}
- pub fn contents_formatted(&self) -> Vec<u8> {
- let mut contents = b"\x1b[m\x1b[H\x1b[J".to_vec();
+ pub fn write_contents_formatted(&self, contents: &mut Vec<u8>) {
+ write!(
+ contents,
+ "{}{}",
+ crate::term::Attrs::new(),
+ crate::term::ClearScreen::new()
+ )
+ .unwrap();
+
let mut prev_attrs = crate::attrs::Attrs::default();
let mut final_col = 0;
for row in self.rows() {
- let (mut new_contents, new_attrs, new_col) =
- row.contents_formatted(0, self.size.cols, prev_attrs);
- if !new_contents.is_empty() {
- final_col = new_col;
+ let (new_attrs, new_col) = row.write_contents_formatted(
+ contents,
+ 0,
+ self.size.cols,
+ prev_attrs,
+ );
+ if let Some(col) = new_col {
+ final_col = col;
}
- contents.append(&mut new_contents);
if !row.wrapped() {
- contents.extend(b"\r\n");
+ write!(contents, "{}", crate::term::CRLF::new()).unwrap();
}
prev_attrs = new_attrs;
}
@@ -160,41 +174,45 @@ impl Grid {
}
if final_row != self.pos.row || final_col != self.pos.col {
- contents.extend(
- format!("\x1b[{};{}H", self.pos.row + 1, self.pos.col + 1)
- .as_bytes(),
- );
+ write!(contents, "{}", crate::term::MoveTo::new(self.pos))
+ .unwrap();
}
-
- contents
}
- pub fn contents_diff(&self, prev: &Self) -> Vec<u8> {
- let mut contents = b"\x1b[m".to_vec();
+ pub fn write_contents_diff(&self, contents: &mut Vec<u8>, prev: &Self) {
+ write!(contents, "{}", crate::term::Attrs::default()).unwrap();
let mut prev_attrs = crate::attrs::Attrs::default();
let mut final_row = prev.pos.row;
let mut final_col = prev.pos.col;
for (idx, (row, prev_row)) in self.rows().zip(prev.rows()).enumerate()
{
- let (mut new_contents, new_attrs, new_col) =
- row.contents_diff(prev_row, 0, self.size.cols, prev_attrs);
- if !new_contents.is_empty() {
- contents.extend(format!("\x1b[{};1H", idx + 1).as_bytes());
- final_row = idx.try_into().unwrap();
- final_col = new_col;
+ let idx = idx.try_into().unwrap();
+ let (new_attrs, new_col) = row.write_contents_diff(
+ contents,
+ prev_row,
+ |contents| {
+ write!(
+ contents,
+ "{}",
+ crate::term::MoveTo::new(Pos { row: idx, col: 0 })
+ )
+ .unwrap();
+ },
+ 0,
+ self.size.cols,
+ prev_attrs,
+ );
+ if let Some(col) = new_col {
+ final_row = idx;
+ final_col = col;
}
- contents.append(&mut new_contents);
prev_attrs = new_attrs;
}
if self.pos.row != final_row || self.pos.col != final_col {
- contents.extend(
- format!("\x1b[{};{}H", self.pos.row + 1, self.pos.col + 1)
- .as_bytes(),
- );
+ write!(contents, "{}", crate::term::MoveTo::new(self.pos))
+ .unwrap();
}
-
- contents
}
pub fn erase_all(&mut self, bgcolor: crate::attrs::Color) {
diff --git a/src/lib.rs b/src/lib.rs
index 48dee07..9424fb9 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -33,6 +33,7 @@ mod grid;
mod parser;
mod row;
mod screen;
+mod term;
pub use attrs::Color;
pub use cell::Cell;
diff --git a/src/row.rs b/src/row.rs
index 0b834ce..4655f48 100644
--- a/src/row.rs
+++ b/src/row.rs
@@ -1,4 +1,5 @@
use std::convert::TryInto as _;
+use std::io::Write as _;
#[derive(Clone, Debug)]
pub struct Row {
@@ -63,46 +64,51 @@ impl Row {
self.wrapped
}
- pub fn contents(&self, start: u16, width: u16) -> String {
+ pub fn write_contents(
+ &self,
+ contents: &mut String,
+ start: u16,
+ width: u16,
+ ) {
let mut prev_was_wide = false;
- let mut contents = String::new();
for cell in self
.cells()
.skip(start as usize)
- .take(width.min(self.content_width(start)) as usize)
+ .take(width.min(self.content_width(start, false)) as usize)
{
if prev_was_wide {
prev_was_wide = false;
continue;
}
- contents += if cell.has_contents() {
- cell.contents()
+ if cell.has_contents() {
+ // using write! here is significantly slower, for some reason
+ // write!(contents, "{}", cell.contents()).unwrap();
+ contents.push_str(cell.contents());
} else {
- " "
- };
+ contents.push(' ');
+ }
+
prev_was_wide = cell.is_wide();
}
-
- contents.trim_end().to_string()
}
- pub fn contents_formatted(
+ pub fn write_contents_formatted(
&self,
+ contents: &mut Vec<u8>,
start: u16,
width: u16,
attrs: crate::attrs::Attrs,
- ) -> (Vec<u8>, crate::attrs::Attrs, u16) {
+ ) -> (crate::attrs::Attrs, Option<u16>) {
let mut prev_was_wide = false;
- let mut contents = vec![];
let mut prev_attrs = attrs;
- let mut cols = 0;
+ let mut cols = None;
for cell in self
.cells()
.skip(start as usize)
- .take(width.min(self.content_width(start)) as usize)
+ .take(width.min(self.content_width(start, true)) as usize)
{
if prev_was_wide {
prev_was_wide = false;
@@ -111,37 +117,49 @@ impl Row {
let attrs = cell.attrs();
if &prev_attrs != attrs {
- contents.append(&mut attrs.escape_code_diff(&prev_attrs));
+ attrs.write_escape_code_diff(contents, &prev_attrs);
prev_attrs = *attrs;
}
- contents.extend(if cell.has_contents() {
- cell.contents().as_bytes()
+ if cell.has_contents() {
+ // using write! here is significantly slower, for some reason
+ // write!(contents, "{}", cell.contents()).unwrap();
+ contents.extend(cell.contents().as_bytes());
} else if cell.bgcolor() == crate::attrs::Color::Default {
- &b"\x1b[C"[..]
+ write!(contents, "{}", crate::term::MoveRight::default())
+ .unwrap();
} else {
- &b"\x1b[X\x1b[C"[..]
- });
+ write!(
+ contents,
+ "{}{}",
+ crate::term::EraseChar::default(),
+ crate::term::MoveRight::default()
+ )
+ .unwrap();
+ }
prev_was_wide = cell.is_wide();
- cols += if prev_was_wide { 2 } else { 1 };
+ cols =
+ Some(cols.unwrap_or(0) + if prev_was_wide { 2 } else { 1 });
}
- (contents, prev_attrs, cols)
+ (prev_attrs, cols)
}
- pub fn contents_diff(
+ pub fn write_contents_diff<F: for<'a> Fn(&'a mut Vec<u8>)>(
&self,
+ contents: &mut Vec<u8>,
prev: &Self,
+ initial_pos: F,
start: u16,
width: u16,
attrs: crate::attrs::Attrs,
- ) -> (Vec<u8>, crate::attrs::Attrs, u16) {
+ ) -> (crate::attrs::Attrs, Option<u16>) {
let mut prev_was_wide = false;
- let mut skip = 0;
- let mut contents = vec![];
let mut prev_attrs = attrs;
- let mut cols = 0;
+ let mut skip = 0;
+ let mut cols = None;
+ let mut initial_pos = Some(initial_pos);
for (cell, prev_cell) in self
.cells()
@@ -158,38 +176,51 @@ impl Row {
prev_was_wide = cell.is_wide();
skip += if prev_was_wide { 2 } else { 1 };
} else {
+ if let Some(f) = initial_pos.take() {
+ f(contents)
+ }
if skip > 0 {
- contents.extend(format!("\x1b[{}C", skip).as_bytes());
- cols += skip;
+ write!(contents, "{}", crate::term::MoveRight::new(skip))
+ .unwrap();
+ cols = Some(cols.unwrap_or(0) + skip);
skip = 0;
}
let attrs = cell.attrs();
if &prev_attrs != attrs {
- contents.append(&mut attrs.escape_code_diff(&prev_attrs));
+ attrs.write_escape_code_diff(contents, &prev_attrs);
prev_attrs = *attrs;
}
- contents.extend(if cell.has_contents() {
- cell.contents().as_bytes()
+ if cell.has_contents() {
+ write!(contents, "{}", cell.contents()).unwrap();
} else {
- b"\x1b[X\x1b[C"
- });
+ write!(
+ contents,
+ "{}{}",
+ crate::term::EraseChar::default(),
+ crate::term::MoveRight::default()
+ )
+ .unwrap();
+ }
prev_was_wide = cell.is_wide();
- cols += if prev_was_wide { 2 } else { 1 };
+ cols = Some(
+ cols.unwrap_or(0) + if prev_was_wide { 2 } else { 1 },
+ );
}
}
- (contents, prev_attrs, cols)
+ (prev_attrs, cols)
}
- fn content_width(&self, start: u16) -> u16 {
+ fn content_width(&self, start: u16, formatting: bool) -> u16 {
for (col, cell) in
self.cells.iter().skip(start as usize).enumerate().rev()
{
if cell.has_contents()
- || cell.bgcolor() != crate::attrs::Color::Default
+ || (formatting
+ && cell.bgcolor() != crate::attrs::Color::Default)
{
let width: u16 = col.try_into().unwrap();
return width + 1;
diff --git a/src/screen.rs b/src/screen.rs
index 88e5b44..4ed065b 100644
--- a/src/screen.rs
+++ b/src/screen.rs
@@ -1,4 +1,5 @@
use std::convert::TryInto as _;
+use std::io::Write as _;
use unicode_width::UnicodeWidthChar as _;
const DEFAULT_MULTI_PARAMS: &[i64] = &[0];
@@ -128,7 +129,13 @@ impl Screen {
/// This will not include any formatting information, and will be in plain
/// text format.
pub fn contents(&self) -> String {
- self.grid().contents()
+ let mut contents = String::new();
+ self.write_contents(&mut contents);
+ contents
+ }
+
+ fn write_contents(&self, contents: &mut String) {
+ self.grid().write_contents(contents);
}
/// Returns the text contents of the terminal by row, restricted to the
@@ -143,9 +150,11 @@ impl Screen {
start: u16,
width: u16,
) -> impl Iterator<Item = String> + '_ {
- self.grid()
- .rows()
- .map(move |row| row.contents(start, width))
+ self.grid().rows().map(move |row| {
+ let mut contents = String::new();
+ row.write_contents(&mut contents, start, width);
+ contents
+ })
}
/// Returns the formatted contents of the terminal.
@@ -157,14 +166,19 @@ impl Screen {
/// mode) will not be included here, but modes that affect the visible
/// output (such as hidden cursor mode) will.
pub fn contents_formatted(&self) -> Vec<u8> {
- let mut grid_contents = if self.hide_cursor() {
- b"\x1b[?25l"
- } else {
- b"\x1b[?25h"
- }
- .to_vec();
- grid_contents.append(&mut self.grid().contents_formatted());
- grid_contents
+ let mut contents = vec![];
+ self.write_contents_formatted(&mut contents);
+ contents
+ }
+
+ fn write_contents_formatted(&self, contents: &mut Vec<u8>) {
+ write!(
+ contents,
+ "{}",
+ crate::term::HideCursor::new(self.hide_cursor())
+ )
+ .unwrap();
+ self.grid().write_contents_formatted(contents);
}
/// Returns the formatted contents of the terminal by row, restricted to
@@ -183,7 +197,9 @@ impl Screen {
width: u16,
) -> impl Iterator<Item = Vec<u8>> + '_ {
self.grid().rows().map(move |row| {
- let (contents, ..) = row.contents_formatted(
+ let mut contents = vec![];
+ row.write_contents_formatted(
+ &mut contents,
start,
width,
crate::attrs::Attrs::default(),
@@ -202,16 +218,21 @@ impl Screen {
/// since the diff will likely require less memory and cause less
/// flickering than redrawing the entire screen contents.
pub fn contents_diff(&self, prev: &Self) -> Vec<u8> {
- let mut grid_contents = vec![];
+ let mut contents = vec![];
+ self.write_contents_diff(&mut contents, prev);
+ contents
+ }
+
+ fn write_contents_diff(&self, contents: &mut Vec<u8>, prev: &Self) {
if self.hide_cursor() != prev.hide_cursor() {
- grid_contents.extend(if self.hide_cursor() {
- b"\x1b[?25l"
- } else {
- b"\x1b[?25h"
- });
+ write!(
+ contents,
+ "{}",
+ crate::term::HideCursor::new(self.hide_cursor())
+ )
+ .unwrap();
}
- grid_contents.append(&mut self.grid().contents_diff(prev.grid()));
- grid_contents
+ self.grid().write_contents_diff(contents, prev.grid());
}
/// Returns a sequence of terminal byte streams sufficient to turn the
@@ -230,8 +251,11 @@ impl Screen {
) -> impl Iterator<Item = Vec<u8>> + 'a {
self.grid().rows().zip(prev.grid().rows()).map(
move |(row, prev_row)| {
- let (contents, ..) = row.contents_diff(
+ let mut contents = vec![];
+ row.write_contents_diff(
+ &mut contents,
prev_row,
+ |_| (),
start,
width,
crate::attrs::Attrs::default(),
diff --git a/src/term.rs b/src/term.rs
new file mode 100644
index 0000000..ef6c546
--- /dev/null
+++ b/src/term.rs
@@ -0,0 +1,277 @@
+// TODO: read all of this from terminfo
+
+#[derive(Default, Debug)]
+pub struct ClearScreen;
+
+impl ClearScreen {
+ pub fn new() -> Self {
+ Self::default()
+ }
+}
+
+impl std::fmt::Display for ClearScreen {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.write_str("\x1b[H\x1b[J")
+ }
+}
+
+#[derive(Default, Debug)]
+pub struct CRLF;
+
+impl CRLF {
+ pub fn new() -> Self {
+ Self::default()
+ }
+}
+
+impl std::fmt::Display for CRLF {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.write_str("\r\n")
+ }
+}
+
+#[derive(Default, Debug)]
+pub struct MoveTo {
+ row: u16,
+ col: u16,
+}
+
+impl MoveTo {
+ pub fn new(pos: crate::grid::Pos) -> Self {
+ Self {
+ row: pos.row,
+ col: pos.col,
+ }
+ }
+}
+
+impl std::fmt::Display for MoveTo {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ if self.row == 0 && self.col == 0 {
+ f.write_str("\x1b[H")
+ } else {
+ write!(f, "\x1b[{};{}H", self.row + 1, self.col + 1)
+ }
+ }
+}
+
+#[derive(Default, Debug)]
+pub struct Attrs {
+ fgcolor: Option<crate::attrs::Color>,
+ bgcolor: Option<crate::attrs::Color>,
+ bold: Option<bool>,
+ italic: Option<bool>,
+ underline: Option<bool>,
+ inverse: Option<bool>,
+}
+
+impl Attrs {
+ pub fn new() -> Self {
+ Self::default()
+ }
+
+ pub fn fgcolor(mut self, fgcolor: crate::attrs::Color) -> Self {
+ self.fgcolor = Some(fgcolor);
+ self
+ }
+
+ pub fn bgcolor(mut self, bgcolor: crate::attrs::Color) -> Self {
+ self.bgcolor = Some(bgcolor);
+ self
+ }
+
+ pub fn bold(mut self, bold: bool) -> Self {
+ self.bold = Some(bold);
+ self
+ }
+
+ pub fn italic(mut self, italic: bool) -> Self {
+ self.italic = Some(italic);
+ self
+ }
+
+ pub fn underline(mut self, underline: bool) -> Self {
+ self.underline = Some(underline);
+ self
+ }
+
+ pub fn inverse(mut self, inverse: bool) -> Self {
+ self.inverse = Some(inverse);
+ self
+ }
+}
+
+impl std::fmt::Display for Attrs {
+ #[allow(unused_assignments, clippy::cognitive_complexity)]
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.write_str("\x1b[")?;
+ let mut first = true;
+
+ macro_rules! write_param {
+ ($i:expr) => {
+ if first {
+ first = false;
+ } else {
+ f.write_str(";")?;
+ }
+ write!(f, "{}", $i)?;
+ };
+ }
+
+ if let Some(fgcolor) = self.fgcolor {
+ match fgcolor {
+ crate::attrs::Color::Default => {
+ write_param!(39);
+ }
+ crate::attrs::Color::Idx(i) => {
+ if i < 8 {
+ write_param!(i + 30);
+ } else if i < 16 {
+ write_param!(i + 82);
+ } else {
+ write_param!(38);
+ write_param!(5);
+ write_param!(i);
+ }
+ }
+ crate::attrs::Color::Rgb(r, g, b) => {
+ write_param!(38);
+ write_param!(2);
+ write_param!(r);
+ write_param!(g);
+ write_param!(b);
+ }
+ }
+ }
+
+ if let Some(bgcolor) = self.bgcolor {
+ match bgcolor {
+ crate::attrs::Color::Default => {
+ write_param!(49);
+ }
+ crate::attrs::Color::Idx(i) => {
+ if i < 8 {
+ write_param!(i + 40);
+ } else if i < 16 {
+ write_param!(i + 92);
+ } else {
+ write_param!(48);
+ write_param!(5);
+ write_param!(i);
+ }
+ }
+ crate::attrs::Color::Rgb(r, g, b) => {
+ write_param!(48);
+ write_param!(2);
+ write_param!(r);
+ write_param!(g);
+ write_param!(b);
+ }
+ }
+ }
+
+ if let Some(bold) = self.bold {
+ if bold {
+ write_param!(1);
+ } else {
+ write_param!(22);
+ }
+ }
+
+ if let Some(italic) = self.italic {
+ if italic {
+ write_param!(3);
+ } else {
+ write_param!(23);
+ }
+ }
+
+ if let Some(underline) = self.underline {
+ if underline {
+ write_param!(4);
+ } else {
+ write_param!(24);
+ }
+ }
+
+ if let Some(inverse) = self.inverse {
+ if inverse {
+ write_param!(7);
+ } else {
+ write_param!(27);
+ }
+ }
+
+ f.write_str("m")?;
+ Ok(())
+ }
+}
+
+#[derive(Debug)]
+pub struct MoveRight {
+ count: u16,
+}
+
+impl MoveRight {
+ pub fn new(count: u16) -> Self {
+ Self { count }
+ }
+}
+
+impl Default for MoveRight {
+ fn default() -> Self {
+ Self { count: 1 }
+ }
+}
+
+impl std::fmt::Display for MoveRight {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self.count {
+ 0 => Ok(()),
+ 1 => f.write_str("\x1b[C"),
+ n => write!(f, "\x1b[{}C", n),
+ }
+ }
+}
+
+#[derive(Debug)]
+pub struct EraseChar {
+ count: u16,
+}
+
+impl Default for EraseChar {
+ fn default() -> Self {
+ Self { count: 1 }
+ }
+}
+
+impl std::fmt::Display for EraseChar {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self.count {
+ 0 => Ok(()),
+ 1 => f.write_str("\x1b[X"),
+ n => write!(f, "\x1b[{}X", n),
+ }
+ }
+}
+
+#[derive(Default, Debug)]
+pub struct HideCursor {
+ hide: bool,
+}
+
+impl HideCursor {
+ pub fn new(hide: bool) -> Self {
+ Self { hide }
+ }
+}
+
+impl std::fmt::Display for HideCursor {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ if self.hide {
+ f.write_str("\x1b[?25l")
+ } else {
+ f.write_str("\x1b[?25h")
+ }
+ }
+}
diff --git a/tests/text.rs b/tests/text.rs
index 245a87f..5c16039 100644
--- a/tests/text.rs
+++ b/tests/text.rs
@@ -64,7 +64,7 @@ fn wide() {
);
assert_eq!(
parser.screen().contents_diff(&screen),
- "\x1b[m\x1b[1;1Haデbネ".as_bytes()
+ "\x1b[m\x1b[Haデbネ".as_bytes()
);
let screen = parser.screen().clone();
@@ -77,7 +77,7 @@ fn wide() {
);
assert_eq!(
parser.screen().contents_diff(&screen),
- "\x1b[m\x1b[1;1H\x1b[3Cc".as_bytes()
+ "\x1b[m\x1b[H\x1b[3Cc".as_bytes()
);
let screen = parser.screen().clone();
@@ -90,7 +90,7 @@ fn wide() {
);
assert_eq!(
parser.screen().contents_diff(&screen),
- "\x1b[m\x1b[1;1H\x1b[6Cfoobar".as_bytes()
+ "\x1b[m\x1b[H\x1b[6Cfoobar".as_bytes()
);
let screen = parser.screen().clone();
@@ -103,7 +103,7 @@ fn wide() {
);
assert_eq!(
parser.screen().contents_diff(&screen),
- "\x1b[m\x1b[1;1Hデcネfo\x1b[1Cbar\x1b[X\x1b[C\x1b[1;12H".as_bytes()
+ "\x1b[m\x1b[Hデcネfo\x1b[Cbar\x1b[X\x1b[C\x1b[1;12H".as_bytes()
);
let screen = parser.screen().clone();
@@ -116,7 +116,7 @@ fn wide() {
);
assert_eq!(
parser.screen().contents_diff(&screen),
- "\x1b[m\x1b[1;1Haデcネf\x1b[1Cobar".as_bytes()
+ "\x1b[m\x1b[Haデcネf\x1b[Cobar".as_bytes()
);
let screen = parser.screen().clone();
@@ -129,7 +129,7 @@ fn wide() {
);
assert_eq!(
parser.screen().contents_diff(&screen),
- "\x1b[m\x1b[1;1Hデcネfo\x1b[1Cbar\x1b[X\x1b[C\x1b[1;12H".as_bytes()
+ "\x1b[m\x1b[Hデcネfo\x1b[Cbar\x1b[X\x1b[C\x1b[1;12H".as_bytes()
);
}
diff --git a/tests/window_contents.rs b/tests/window_contents.rs
index 851b128..6c0e7db 100644
--- a/tests/window_contents.rs
+++ b/tests/window_contents.rs
@@ -87,7 +87,7 @@ fn cursor_positioning() {
parser.screen().contents_formatted(),
b"\x1b[?25h\x1b[m\x1b[H\x1b[J:"
);
- assert_eq!(parser.screen().contents_diff(&screen1), b"\x1b[m\x1b[1;1H:");
+ assert_eq!(parser.screen().contents_diff(&screen1), b"\x1b[m\x1b[H:");
parser.process(b"a");
let screen3 = parser.screen().clone();
@@ -98,7 +98,7 @@ fn cursor_positioning() {
);
assert_eq!(
parser.screen().contents_diff(&screen2),
- b"\x1b[m\x1b[1;1H\x1b[1Ca"
+ b"\x1b[m\x1b[H\x1b[Ca"
);
parser.process(b"\x1b[1;2H\x1b[K");
@@ -109,7 +109,7 @@ fn cursor_positioning() {
);
assert_eq!(
parser.screen().contents_diff(&screen3),
- b"\x1b[m\x1b[1;1H\x1b[1C\x1b[X\x1b[C\x1b[1;2H"
+ b"\x1b[m\x1b[H\x1b[C\x1b[X\x1b[C\x1b[1;2H"
);
}
@@ -429,23 +429,20 @@ fn diff() {
let screen2 = parser.screen().clone();
assert_eq!(
screen2.contents_diff(&screen1),
- b"\x1b[m\x1b[1;1H\x1b[5C\x1b[32m bar"
+ b"\x1b[m\x1b[H\x1b[5C\x1b[32m bar"
);
compare_diff(&screen1, &screen2, b"");
parser.process(b"\x1b[H\x1b[31mfoo");
let screen3 = parser.screen().clone();
- assert_eq!(
- screen3.contents_diff(&screen2),
- b"\x1b[m\x1b[1;1H\x1b[31mfoo"
- );
+ assert_eq!(screen3.contents_diff(&screen2), b"\x1b[m\x1b[H\x1b[31mfoo");
compare_diff(&screen2, &screen3, b"\x1b[5C\x1b[32m bar");
parser.process(b"\x1b[1;7H\x1b[32mbaz");
let screen4 = parser.screen().clone();
assert_eq!(
screen4.contents_diff(&screen3),
- b"\x1b[m\x1b[1;1H\x1b[8C\x1b[32mz"
+ b"\x1b[m\x1b[H\x1b[8C\x1b[32mz"
);
compare_diff(&screen3, &screen4, b"\x1b[5C\x1b[32m bar\x1b[H\x1b[31mfoo");
@@ -453,7 +450,7 @@ fn diff() {
let screen5 = parser.screen().clone();
assert_eq!(
screen5.contents_diff(&screen4),
- b"\x1b[m\x1b[1;1H\x1b[7C\x1b[X\x1b[C\x1b[1;8H"
+ b"\x1b[m\x1b[H\x1b[7C\x1b[X\x1b[C\x1b[1;8H"
);
compare_diff(
&screen4,