From e781fdd43b57f83a36d940b016ad379459db4420 Mon Sep 17 00:00:00 2001 From: Wez Furlong Date: Sun, 5 Aug 2018 08:21:53 -0700 Subject: [PATCH] move surface::Line into its own module --- termwiz/src/surface/line.rs | 156 +++++++++++++++++++++++++++++++++ termwiz/src/surface/mod.rs | 169 ++---------------------------------- 2 files changed, 165 insertions(+), 160 deletions(-) create mode 100644 termwiz/src/surface/line.rs diff --git a/termwiz/src/surface/line.rs b/termwiz/src/surface/line.rs new file mode 100644 index 000000000..66f1e922e --- /dev/null +++ b/termwiz/src/surface/line.rs @@ -0,0 +1,156 @@ +use cell::{Cell, CellAttributes}; +use surface::Change; + +#[derive(Debug, Clone)] +pub struct Line { + cells: Vec, +} + +impl Line { + pub fn with_width(width: usize) -> Self { + let mut cells = Vec::with_capacity(width); + cells.resize(width, Cell::default()); + Self { cells } + } + + pub fn resize(&mut self, width: usize) { + self.cells.resize(width, Cell::default()); + } + + /// If we're about to modify a cell obscured by a double-width + /// character ahead of that cell, we need to nerf that sequence + /// of cells to avoid partial rendering concerns. + /// Similarly, when we assign a cell, we need to blank out those + /// occluded successor cells. + /// Note that an invalid index will be silently ignored; attempting + /// to assign to an out of bounds index will not extend the cell array, + /// and it will not flag an error. + pub fn set_cell(&mut self, idx: usize, cell: Cell) { + // Assumption: that the width of a grapheme is never > 2. + // This constrains the amount of look-back that we need to do here. + if idx > 0 { + let prior = idx - 1; + let width = self.cells[prior].width(); + if width > 1 { + let attrs = self.cells[prior].attrs().clone(); + for nerf in prior..prior + width { + self.cells[nerf] = Cell::new(' ', attrs.clone()); + } + } + } + + // For double-wide or wider chars, ensure that the cells that + // are overlapped by this one are blanked out. + let width = cell.width(); + for i in 1..=width.saturating_sub(1) { + self.cells + .get_mut(idx + i) + .map(|target| *target = Cell::new(' ', cell.attrs().clone())); + } + + self.cells.get_mut(idx).map(|target| *target = cell); + } + + pub fn fill_range(&mut self, cols: impl Iterator, cell: &Cell) { + let max_col = self.cells.len(); + for x in cols { + if x >= max_col { + break; + } + // FIXME: we can skip the look-back for second and subsequent iterations + self.set_cell(x, cell.clone()); + } + } + + /// Iterates the visible cells, respecting the width of the cell. + /// For instance, a double-width cell overlaps the following (blank) + /// cell, so that blank cell is omitted from the iterator results. + /// The iterator yields (column_index, Cell). Column index is the + /// index into Self::cells, and due to the possibility of skipping + /// the characters that follow wide characters, the column index may + /// skip some positions. It is returned as a convenience to the consumer + /// as using .enumerate() on this iterator wouldn't be as useful. + pub fn visible_cells(&self) -> impl Iterator { + let mut skip_width = 0; + self.cells.iter().enumerate().filter(move |(_idx, cell)| { + if skip_width > 0 { + skip_width -= 1; + false + } else { + skip_width = cell.width().saturating_sub(1); + true + } + }) + } + + pub fn cells(&self) -> &[Cell] { + &self.cells + } + + /// Given a starting attribute value, produce a series of Change + /// entries to recreate the current line + pub fn changes(&self, start_attr: &CellAttributes) -> Vec { + let mut result = Vec::new(); + let mut attr = start_attr.clone(); + let mut text_run = String::new(); + + for (_, cell) in self.visible_cells() { + if *cell.attrs() == attr { + text_run.push_str(cell.str()); + } else { + // flush out the current text run + if text_run.len() > 0 { + result.push(Change::Text(text_run.clone())); + text_run.clear(); + } + + attr = cell.attrs().clone(); + result.push(Change::AllAttributes(attr.clone())); + text_run.push_str(cell.str()); + } + } + + // flush out any remaining text run + if text_run.len() > 0 { + // if this is just spaces then it is likely cheaper + // to emit ClearToEndOfLine instead. + if attr == CellAttributes::default() + .set_background(attr.background) + .clone() + { + let left = text_run.trim_right_matches(' ').to_string(); + let num_trailing_spaces = text_run.len() - left.len(); + + if num_trailing_spaces > 0 { + if left.len() > 0 { + result.push(Change::Text(left.to_string())); + } else if result.len() == 1 { + // if the only queued result prior to clearing + // to the end of the line is an attribute change, + // we can prune it out and return just the line + // clearing operation + match result[0] { + Change::AllAttributes(_) => result.clear(), + _ => {} + } + } + + // Since this function is only called in the full repaint + // case, and we always emit a clear screen with the default + // background color, we don't need to emit an instruction + // to clear the remainder of the line unless it has a different + // background color. + if attr.background != Default::default() { + result.push(Change::ClearToEndOfLine(attr.background)); + } + } else { + result.push(Change::Text(text_run.clone())); + } + } else { + result.push(Change::Text(text_run.clone())); + } + } + + result + } +} diff --git a/termwiz/src/surface/mod.rs b/termwiz/src/surface/mod.rs index 3573689f1..75095c4c7 100644 --- a/termwiz/src/surface/mod.rs +++ b/termwiz/src/surface/mod.rs @@ -4,6 +4,9 @@ use std::borrow::Cow; use std::cmp::min; use unicode_segmentation::UnicodeSegmentation; +pub mod line; +pub use self::line::Line; + /// Position holds 0-based positioning information, where /// Absolute(0) is the start of the line or column, /// Relative(0) is the current position in the line or @@ -106,145 +109,6 @@ impl From for Change { } } -#[derive(Debug, Clone)] -struct Line { - cells: Vec, -} - -impl Line { - fn with_width(width: usize) -> Self { - let mut cells = Vec::with_capacity(width); - cells.resize(width, Cell::default()); - Self { cells } - } - - fn resize(&mut self, width: usize) { - self.cells.resize(width, Cell::default()); - } - - /// If we're about to modify a cell obscured by a double-width - /// character ahead of that cell, we need to nerf that sequence - /// of cells to avoid partial rendering concerns. - /// Similarly, when we assign a cell, we need to blank out those - /// occluded successor cells. - /// Note that an invalid index will be silently ignored; attempting - /// to assign to an out of bounds index will not extend the cell array, - /// and it will not flag an error. - fn set_cell(&mut self, idx: usize, cell: Cell) { - // Assumption: that the width of a grapheme is never > 2. - // This constrains the amount of look-back that we need to do here. - if idx > 0 { - let prior = idx - 1; - let width = self.cells[prior].width(); - if width > 1 { - let attrs = self.cells[prior].attrs().clone(); - for nerf in prior..prior + width { - self.cells[nerf] = Cell::new(' ', attrs.clone()); - } - } - } - - // For double-wide or wider chars, ensure that the cells that - // are overlapped by this one are blanked out. - let width = cell.width(); - for i in 1..=width.saturating_sub(1) { - self.cells - .get_mut(idx + i) - .map(|target| *target = Cell::new(' ', cell.attrs().clone())); - } - - self.cells.get_mut(idx).map(|target| *target = cell); - } - - /// Iterates the visible cells, respecting the width of the cell. - /// For instance, a double-width cell overlaps the following (blank) - /// cell, so that blank cell is omitted from the iterator results. - /// The iterator yields (column_index, Cell). Column index is the - /// index into Self::cells, and due to the possibility of skipping - /// the characters that follow wide characters, the column index may - /// skip some positions. It is returned as a convenience to the consumer - /// as using .enumerate() on this iterator wouldn't be as useful. - fn visible_cells(&self) -> impl Iterator { - let mut skip_width = 0; - self.cells.iter().enumerate().filter(move |(_idx, cell)| { - if skip_width > 0 { - skip_width -= 1; - false - } else { - skip_width = cell.width().saturating_sub(1); - true - } - }) - } - - /// Given a starting attribute value, produce a series of Change - /// entries to recreate the current line - fn changes(&self, start_attr: &CellAttributes) -> Vec { - let mut result = Vec::new(); - let mut attr = start_attr.clone(); - let mut text_run = String::new(); - - for (_, cell) in self.visible_cells() { - if *cell.attrs() == attr { - text_run.push_str(cell.str()); - } else { - // flush out the current text run - if text_run.len() > 0 { - result.push(Change::Text(text_run.clone())); - text_run.clear(); - } - - attr = cell.attrs().clone(); - result.push(Change::AllAttributes(attr.clone())); - text_run.push_str(cell.str()); - } - } - - // flush out any remaining text run - if text_run.len() > 0 { - // if this is just spaces then it is likely cheaper - // to emit ClearToEndOfLine instead. - if attr == CellAttributes::default() - .set_background(attr.background) - .clone() - { - let left = text_run.trim_right_matches(' ').to_string(); - let num_trailing_spaces = text_run.len() - left.len(); - - if num_trailing_spaces > 0 { - if left.len() > 0 { - result.push(Change::Text(left.to_string())); - } else if result.len() == 1 { - // if the only queued result prior to clearing - // to the end of the line is an attribute change, - // we can prune it out and return just the line - // clearing operation - match result[0] { - Change::AllAttributes(_) => result.clear(), - _ => {} - } - } - - // Since this function is only called in the full repaint - // case, and we always emit a clear screen with the default - // background color, we don't need to emit an instruction - // to clear the remainder of the line unless it has a different - // background color. - if attr.background != Default::default() { - result.push(Change::ClearToEndOfLine(attr.background)); - } - } else { - result.push(Change::Text(text_run.clone())); - } - } else { - result.push(Change::Text(text_run.clone())); - } - } - - result - } -} - /// SequenceNo indicates a logical position within a stream of changes. /// The sequence is only meaningful within a given `Surface` instance. pub type SequenceNo = usize; @@ -388,9 +252,7 @@ impl Surface { .clone(); let cleared = Cell::new(' ', self.attributes.clone()); for line in &mut self.lines { - for cell in &mut line.cells { - *cell = cleared.clone(); - } + line.fill_range(0.., &cleared); } self.xpos = 0; self.ypos = 0; @@ -401,17 +263,9 @@ impl Surface { .set_background(color.clone()) .clone(); let cleared = Cell::new(' ', self.attributes.clone()); - // Ensure that we have the opportunity to fixup any damage to double-wide - // chararcters that overlap the start of the current position. - self.lines[self.ypos].set_cell(self.xpos, cleared.clone()); - // Now we can safely directly poke all remaining cells with a space - for cell in self.lines[self.ypos].cells.iter_mut().skip(self.xpos + 1) { - *cell = cleared.clone(); - } + self.lines[self.ypos].fill_range(self.xpos.., &cleared); for line in &mut self.lines.iter_mut().skip(self.ypos + 1) { - for cell in &mut line.cells { - *cell = cleared.clone(); - } + line.fill_range(0.., &cleared); } } @@ -420,12 +274,7 @@ impl Surface { .set_background(color.clone()) .clone(); let cleared = Cell::new(' ', self.attributes.clone()); - // Ensure that we have the opportunity to fixup any damage to double-wide - // chararcters that overlap the start of the current position. - self.lines[self.ypos].set_cell(self.xpos, cleared.clone()); - for cell in self.lines[self.ypos].cells.iter_mut().skip(self.xpos + 1) { - *cell = cleared.clone(); - } + self.lines[self.ypos].fill_range(self.xpos.., &cleared); } fn scroll_screen_up(&mut self) { @@ -545,7 +394,7 @@ impl Surface { pub fn screen_cells(&self) -> Vec<&[Cell]> { let mut lines = Vec::new(); for line in &self.lines { - lines.push(line.cells.as_slice()); + lines.push(line.cells()); } lines } @@ -704,7 +553,7 @@ impl Surface { } result.append(&mut changes); - attr = line.cells[self.width - 1].attrs().clone(); + attr = line.cells()[self.width - 1].attrs().clone(); } // Remove any trailing sequence of cursor movements, as we're