From a9b6d72b24fffa55093201c520075d500712a3ff Mon Sep 17 00:00:00 2001 From: Jesse Luehrs Date: Fri, 29 Nov 2019 03:55:02 -0500 Subject: track fullwidth continuation cells explicitly this makes the logic a bit easier to follow --- src/cell.rs | 12 ++++++ src/grid.rs | 35 +++++++++++++---- src/row.rs | 24 +++++++++--- src/screen.rs | 123 ++++++++++++++++++++++++++++++++++++++++------------------ tests/text.rs | 14 +++++++ 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 { - 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] -- cgit v1.2.3