1
1
mirror of https://github.com/wez/wezterm.git synced 2024-11-27 12:23:46 +03:00

move surface::Line into its own module

This commit is contained in:
Wez Furlong 2018-08-05 08:21:53 -07:00
parent 72d88091bc
commit e781fdd43b
2 changed files with 165 additions and 160 deletions

156
termwiz/src/surface/line.rs Normal file
View File

@ -0,0 +1,156 @@
use cell::{Cell, CellAttributes};
use surface::Change;
#[derive(Debug, Clone)]
pub struct Line {
cells: Vec<Cell>,
}
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<Item = usize>, 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<Item = (usize, &Cell)> {
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<Change> {
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
}
}

View File

@ -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<AttributeChange> for Change {
}
}
#[derive(Debug, Clone)]
struct Line {
cells: Vec<Cell>,
}
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<Item = (usize, &Cell)> {
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<Change> {
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