aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJesse Luehrs <doy@tozt.net>2019-11-29 03:55:02 -0500
committerJesse Luehrs <doy@tozt.net>2019-12-05 12:54:34 -0500
commita9b6d72b24fffa55093201c520075d500712a3ff (patch)
tree5169f448939401b90bfa683121b3e00b05e20499
parent971b744c9c7c2c3dc9f055f69c5630ca11f0a09e (diff)
downloadvt100-rust-a9b6d72b24fffa55093201c520075d500712a3ff.tar.gz
vt100-rust-a9b6d72b24fffa55093201c520075d500712a3ff.zip
track fullwidth continuation cells explicitly
this makes the logic a bit easier to follow
-rw-r--r--src/cell.rs12
-rw-r--r--src/grid.rs35
-rw-r--r--src/row.rs24
-rw-r--r--src/screen.rs123
-rw-r--r--tests/text.rs14
5 files changed, 156 insertions, 52 deletions
diff --git a/src/cell.rs b/src/cell.rs
index ecc44b2..5da4452 100644
--- a/src/cell.rs
+++ b/src/cell.rs
@@ -112,6 +112,10 @@ impl Cell {
self.len & 0x80 == 0x80
}
+ pub fn is_wide_continuation(&self) -> bool {
+ self.len & 0x40 == 0x40
+ }
+
fn set_wide(&mut self, wide: bool) {
if wide {
self.len |= 0x80;
@@ -120,6 +124,14 @@ impl Cell {
}
}
+ pub(crate) fn set_wide_continuation(&mut self, wide: bool) {
+ if wide {
+ self.len |= 0x40;
+ } else {
+ self.len &= 0xbf;
+ }
+ }
+
pub(crate) fn attrs(&self) -> &crate::attrs::Attrs {
&self.attrs
}
diff --git a/src/grid.rs b/src/grid.rs
index 9619443..d4a8405 100644
--- a/src/grid.rs
+++ b/src/grid.rs
@@ -149,6 +149,10 @@ impl Grid {
self.visible_row(pos).and_then(|r| r.get(pos.col))
}
+ pub fn drawing_cell(&self, pos: Pos) -> Option<&crate::cell::Cell> {
+ self.drawing_row(pos).and_then(|r| r.get(pos.col))
+ }
+
pub fn drawing_cell_mut(
&mut self,
pos: Pos,
@@ -156,6 +160,11 @@ impl Grid {
self.drawing_row_mut(pos).and_then(|r| r.get_mut(pos.col))
}
+ pub fn current_cell(&self) -> &crate::cell::Cell {
+ self.drawing_cell(self.pos)
+ .expect("cursor not pointing to a cell")
+ }
+
pub fn current_cell_mut(&mut self) -> &mut crate::cell::Cell {
self.drawing_cell_mut(self.pos)
.expect("cursor not pointing to a cell")
@@ -414,28 +423,38 @@ impl Grid {
}
pub fn erase_row_forward(&mut self, attrs: crate::attrs::Attrs) {
+ let size = self.size;
let pos = self.pos;
let row = self.current_row_mut();
row.wrap(false);
- for cell in row.cells_mut().skip(pos.col as usize) {
- cell.clear(attrs);
+ for col in pos.col..size.cols {
+ row.erase(col as usize, attrs);
}
}
pub fn erase_row_backward(&mut self, attrs: crate::attrs::Attrs) {
+ let size = self.size;
let pos = self.pos;
let row = self.current_row_mut();
- for cell in row.cells_mut().take(pos.col as usize + 1) {
- cell.clear(attrs);
+ for col in 0..=pos.col.min(size.cols - 1) {
+ row.erase(col as usize, attrs);
}
}
pub fn insert_cells(&mut self, count: u16) {
let size = self.size;
let pos = self.pos;
+ let wide =
+ pos.col < size.cols && self.current_cell().is_wide_continuation();
let row = self.current_row_mut();
for _ in 0..count {
+ if wide {
+ row.get_mut(pos.col).unwrap().set_wide_continuation(false);
+ }
row.insert(pos.col as usize, crate::cell::Cell::default());
+ if wide {
+ row.get_mut(pos.col).unwrap().set_wide_continuation(true);
+ }
}
row.truncate(size.cols as usize);
}
@@ -451,12 +470,12 @@ impl Grid {
}
pub fn erase_cells(&mut self, count: u16, attrs: crate::attrs::Attrs) {
+ let size = self.size;
let pos = self.pos;
let row = self.current_row_mut();
- for cell in
- row.cells_mut().skip(pos.col as usize).take(count as usize)
- {
- cell.clear(attrs);
+ row.clear_wide(pos.col);
+ for col in pos.col..((pos.col + count).min(size.cols)) {
+ row.erase(col as usize, attrs);
}
}
diff --git a/src/row.rs b/src/row.rs
index 52b6823..90e92db 100644
--- a/src/row.rs
+++ b/src/row.rs
@@ -30,12 +30,6 @@ impl Row {
self.cells.iter()
}
- pub fn cells_mut(
- &mut self,
- ) -> impl Iterator<Item = &mut crate::cell::Cell> {
- self.cells.iter_mut()
- }
-
pub fn get(&self, col: u16) -> Option<&crate::cell::Cell> {
self.cells.get(col as usize)
}
@@ -49,9 +43,15 @@ impl Row {
}
pub fn remove(&mut self, i: usize) {
+ self.clear_wide(i.try_into().unwrap());
self.cells.remove(i);
}
+ pub fn erase(&mut self, i: usize, attrs: crate::attrs::Attrs) {
+ self.clear_wide(i.try_into().unwrap());
+ self.cells.get_mut(i).unwrap().clear(attrs);
+ }
+
pub fn truncate(&mut self, len: usize) {
self.cells.truncate(len);
}
@@ -68,6 +68,18 @@ impl Row {
self.wrapped
}
+ pub fn clear_wide(&mut self, col: u16) {
+ let cell = self.get(col).unwrap();
+ let other = if cell.is_wide() {
+ self.get_mut(col + 1).unwrap()
+ } else if cell.is_wide_continuation() {
+ self.get_mut(col - 1).unwrap()
+ } else {
+ return;
+ };
+ other.clear(*other.attrs());
+ }
+
pub fn write_contents(
&self,
contents: &mut String,
diff --git a/src/screen.rs b/src/screen.rs
index 07363b1..d350f6f 100644
--- a/src/screen.rs
+++ b/src/screen.rs
@@ -509,6 +509,13 @@ impl Screen {
self.grid().drawing_row(pos)
}
+ fn drawing_cell(
+ &self,
+ pos: crate::grid::Pos,
+ ) -> Option<&crate::cell::Cell> {
+ self.grid().drawing_cell(pos)
+ }
+
fn drawing_cell_mut(
&mut self,
pos: crate::grid::Pos,
@@ -516,6 +523,10 @@ impl Screen {
self.grid_mut().drawing_cell_mut(pos)
}
+ fn current_cell(&self) -> &crate::cell::Cell {
+ self.grid().current_cell()
+ }
+
fn current_cell_mut(&mut self) -> &mut crate::cell::Cell {
self.grid_mut().current_cell_mut()
}
@@ -575,58 +586,84 @@ impl Screen {
impl Screen {
fn text(&mut self, c: char) {
let pos = self.grid().pos();
- if pos.col > 0 {
- let attrs = self.attrs;
+ let size = self.grid().size();
+ let attrs = self.attrs;
+ let drawing_pos = if pos.col < size.cols {
+ pos
+ } else {
+ crate::grid::Pos {
+ row: pos.row + 1,
+ col: 0,
+ }
+ };
+
+ if self
+ .drawing_cell(drawing_pos)
+ .unwrap()
+ .is_wide_continuation()
+ {
let prev_cell = self
.drawing_cell_mut(crate::grid::Pos {
- row: pos.row,
- col: pos.col - 1,
+ row: drawing_pos.row,
+ col: drawing_pos.col - 1,
})
.unwrap();
- if prev_cell.is_wide() {
- prev_cell.clear(attrs);
- }
+ prev_cell.clear(attrs);
}
- let mut wrap = true;
- // it doesn't make any sense to wrap if the last column in a row
- // didn't already have contents
- if pos.col > 1 {
- let mut prev_cell = self
+ if self.drawing_cell(drawing_pos).unwrap().is_wide() {
+ let next_cell = self
.drawing_cell_mut(crate::grid::Pos {
+ row: drawing_pos.row,
+ col: drawing_pos.col + 1,
+ })
+ .unwrap();
+ next_cell.clear(attrs);
+ }
+
+ let width = c.width().unwrap_or(0).try_into().unwrap();
+
+ // zero width characters still cause the cursor to wrap - this doesn't
+ // affect which cell they go into (the "previous cell" for both (row,
+ // max_col + 1) and (row + 1, 0) is (row, max_col)), but does affect
+ // further movement afterwards - writing an `a` at (row, max_col)
+ // followed by a crlf puts the cursor at (row + 1,
+ // 0), but writing a `à` (specifically `a` followed by a combining
+ // grave accent - the normalized U+00E0 "latin small letter a with
+ // grave" behaves the same as `a`) at (row, max_col) followed by a
+ // crlf puts the cursor at (row + 2, 0)
+ let wrap_width = if width == 0 { 1 } else { width };
+
+ // it doesn't make any sense to wrap if the last column in a row
+ // didn't already have contents (but if a wide character wraps because
+ // there was only one column left in the previous row, that should
+ // still count)
+ let mut wrap = false;
+ if pos.col > size.cols - wrap_width {
+ let last_cell = self
+ .drawing_cell(crate::grid::Pos {
row: pos.row,
- col: pos.col - 2,
+ col: size.cols - 1,
})
.unwrap();
- if !prev_cell.is_wide() {
- prev_cell = self
- .drawing_cell_mut(crate::grid::Pos {
+ if last_cell.has_contents() || last_cell.is_wide_continuation() {
+ wrap = true;
+ }
+ if wrap_width > 1 {
+ let last_last_cell = self
+ .drawing_cell(crate::grid::Pos {
row: pos.row,
- col: pos.col - 1,
+ col: size.cols - 2,
})
.unwrap();
- }
- if !prev_cell.has_contents() {
- wrap = false;
+ if last_last_cell.has_contents()
+ || last_last_cell.is_wide_continuation()
+ {
+ wrap = true;
+ }
}
}
-
- let width = c.width().unwrap_or(0).try_into().unwrap();
- let attrs = self.attrs;
-
- self.grid_mut().col_wrap(
- // zero width characters still cause the cursor to wrap - this
- // doesn't affect which cell they go into (the "previous cell" for
- // both (row, max_col + 1) and (row + 1, 0) is (row, max_col)),
- // but does affect further movement afterwards - writing an `a` at
- // (row, max_col) followed by a crlf puts the cursor at (row + 1,
- // 0), but writing a `à` (specifically `a` followed by a combining
- // grave accent - the normalized U+00E0 "latin small letter a with
- // grave" behaves the same as `a`) at (row, max_col) followed by a
- // crlf puts the cursor at (row + 2, 0)
- if width == 0 { 1 } else { width },
- wrap,
- );
+ self.grid_mut().col_wrap(wrap_width, wrap);
if width == 0 {
if pos.col > 0 {
@@ -659,9 +696,19 @@ impl Screen {
cell.set(c, attrs);
self.grid_mut().col_inc(1);
if width > 1 {
- let attrs = self.attrs;
+ let pos = self.grid().pos();
+ if self.current_cell().is_wide() {
+ let next_next_cell = self
+ .drawing_cell_mut(crate::grid::Pos {
+ row: pos.row,
+ col: pos.col + 1,
+ })
+ .unwrap();
+ next_next_cell.clear(attrs);
+ }
let next_cell = self.current_cell_mut();
next_cell.clear(attrs);
+ next_cell.set_wide_continuation(true);
self.grid_mut().col_inc(1);
}
}
diff --git a/tests/text.rs b/tests/text.rs
index 4206925..5f5dbef 100644
--- a/tests/text.rs
+++ b/tests/text.rs
@@ -232,6 +232,20 @@ fn wrap() {
assert_eq!(parser.screen().contents(), " ");
parser.process(b" ");
assert_eq!(parser.screen().contents(), " \n\n\n ");
+
+ parser.process(b"\x1b[H\x1b[J");
+ assert_eq!(parser.screen().contents(), "");
+ let screen = parser.screen().clone();
+ parser.process("ネa\x1b[L\x1b[1;79Hbcd".as_bytes());
+ assert_eq!(parser.screen().contents(), " bcd a");
+ assert_eq!(
+ parser.screen().contents_formatted(),
+ "\x1b[?25h\x1b[m\x1b[H\x1b[J\x1b[78Cbcd\x1b[Ca\x1b[2;2H".as_bytes()
+ );
+ assert_eq!(
+ parser.screen().contents_diff(&screen),
+ "\x1b[78Cbcd\x1b[Ca\x1b[2;2H".as_bytes()
+ );
}
#[test]