mirror of
https://github.com/wez/wezterm.git
synced 2024-11-23 23:21:08 +03:00
move surface::Line into its own module
This commit is contained in:
parent
72d88091bc
commit
e781fdd43b
156
termwiz/src/surface/line.rs
Normal file
156
termwiz/src/surface/line.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,9 @@ use std::borrow::Cow;
|
|||||||
use std::cmp::min;
|
use std::cmp::min;
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
|
pub mod line;
|
||||||
|
pub use self::line::Line;
|
||||||
|
|
||||||
/// Position holds 0-based positioning information, where
|
/// Position holds 0-based positioning information, where
|
||||||
/// Absolute(0) is the start of the line or column,
|
/// Absolute(0) is the start of the line or column,
|
||||||
/// Relative(0) is the current position in the line or
|
/// 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.
|
/// SequenceNo indicates a logical position within a stream of changes.
|
||||||
/// The sequence is only meaningful within a given `Surface` instance.
|
/// The sequence is only meaningful within a given `Surface` instance.
|
||||||
pub type SequenceNo = usize;
|
pub type SequenceNo = usize;
|
||||||
@ -388,9 +252,7 @@ impl Surface {
|
|||||||
.clone();
|
.clone();
|
||||||
let cleared = Cell::new(' ', self.attributes.clone());
|
let cleared = Cell::new(' ', self.attributes.clone());
|
||||||
for line in &mut self.lines {
|
for line in &mut self.lines {
|
||||||
for cell in &mut line.cells {
|
line.fill_range(0.., &cleared);
|
||||||
*cell = cleared.clone();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
self.xpos = 0;
|
self.xpos = 0;
|
||||||
self.ypos = 0;
|
self.ypos = 0;
|
||||||
@ -401,17 +263,9 @@ impl Surface {
|
|||||||
.set_background(color.clone())
|
.set_background(color.clone())
|
||||||
.clone();
|
.clone();
|
||||||
let cleared = Cell::new(' ', self.attributes.clone());
|
let cleared = Cell::new(' ', self.attributes.clone());
|
||||||
// Ensure that we have the opportunity to fixup any damage to double-wide
|
self.lines[self.ypos].fill_range(self.xpos.., &cleared);
|
||||||
// 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();
|
|
||||||
}
|
|
||||||
for line in &mut self.lines.iter_mut().skip(self.ypos + 1) {
|
for line in &mut self.lines.iter_mut().skip(self.ypos + 1) {
|
||||||
for cell in &mut line.cells {
|
line.fill_range(0.., &cleared);
|
||||||
*cell = cleared.clone();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -420,12 +274,7 @@ impl Surface {
|
|||||||
.set_background(color.clone())
|
.set_background(color.clone())
|
||||||
.clone();
|
.clone();
|
||||||
let cleared = Cell::new(' ', self.attributes.clone());
|
let cleared = Cell::new(' ', self.attributes.clone());
|
||||||
// Ensure that we have the opportunity to fixup any damage to double-wide
|
self.lines[self.ypos].fill_range(self.xpos.., &cleared);
|
||||||
// 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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn scroll_screen_up(&mut self) {
|
fn scroll_screen_up(&mut self) {
|
||||||
@ -545,7 +394,7 @@ impl Surface {
|
|||||||
pub fn screen_cells(&self) -> Vec<&[Cell]> {
|
pub fn screen_cells(&self) -> Vec<&[Cell]> {
|
||||||
let mut lines = Vec::new();
|
let mut lines = Vec::new();
|
||||||
for line in &self.lines {
|
for line in &self.lines {
|
||||||
lines.push(line.cells.as_slice());
|
lines.push(line.cells());
|
||||||
}
|
}
|
||||||
lines
|
lines
|
||||||
}
|
}
|
||||||
@ -704,7 +553,7 @@ impl Surface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
result.append(&mut changes);
|
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
|
// Remove any trailing sequence of cursor movements, as we're
|
||||||
|
Loading…
Reference in New Issue
Block a user