1
1
mirror of https://github.com/wez/wezterm.git synced 2025-01-03 11:11:43 +03:00

line: introduce possibility of alternate cell backing

Uses an enum as a way to use an alternative to Vec<Cell>, but
doesn't provide that alternative in this commit.
This commit is contained in:
Wez Furlong 2022-07-23 08:18:34 -07:00
parent e26d634da1
commit 614900f85c
20 changed files with 222 additions and 173 deletions

View File

@ -416,7 +416,7 @@ macro_rules! pdu {
/// The overall version of the codec.
/// This must be bumped when backwards incompatible changes
/// are made to the types and protocol.
pub const CODEC_VERSION: usize = 25;
pub const CODEC_VERSION: usize = 26;
// Defines the Pdu enum.
// Each struct has an explicit identifying number.

View File

@ -149,7 +149,7 @@ impl LogicalLine {
if phys_y == y {
return offset + x;
}
offset += line.cells().len();
offset += line.len();
}
// Allow selecting off the end of the line
offset + x
@ -160,17 +160,14 @@ impl LogicalLine {
let mut idx = 0;
for line in &self.physical_lines {
let x_off = x - idx;
let line_len = line.cells().len();
let line_len = line.len();
if x_off < line_len {
return (y, x_off);
}
y += 1;
idx += line_len;
}
(
y - 1,
x - idx + self.physical_lines.last().unwrap().cells().len(),
)
(y - 1, x - idx + self.physical_lines.last().unwrap().len())
}
pub fn apply_hyperlink_rules(&mut self, rules: &[Rule]) {
@ -185,7 +182,7 @@ impl LogicalLine {
let mut line = self.logical.clone();
let num_phys = self.physical_lines.len();
for (idx, phys) in self.physical_lines.iter_mut().enumerate() {
let len = phys.cells().len();
let len = phys.len();
let seq = seq.max(phys.current_seqno());
let remainder = line.split_off(len, seq);
*phys = line;
@ -251,10 +248,10 @@ pub trait Pane: Downcast {
if !back[0].last_cell_was_wrapped() {
break;
}
if back[0].cells().len() + back_len > MAX_LOGICAL_LINE_LEN {
if back[0].len() + back_len > MAX_LOGICAL_LINE_LEN {
break;
}
back_len += back[0].cells().len();
back_len += back[0].len();
first = prior;
for (idx, line) in back.into_iter().enumerate() {
phys.insert(idx, line);
@ -266,7 +263,7 @@ pub trait Pane: Downcast {
if !last.last_cell_was_wrapped() {
break;
}
if last.cells().len() > MAX_LOGICAL_LINE_LEN {
if last.len() > MAX_LOGICAL_LINE_LEN {
break;
}
@ -292,7 +289,7 @@ pub trait Pane: Downcast {
}
Some(prior) => {
if prior.logical.last_cell_was_wrapped()
&& prior.logical.cells().len() <= MAX_LOGICAL_LINE_LEN
&& prior.logical.len() <= MAX_LOGICAL_LINE_LEN
{
let seqno = prior.logical.current_seqno().max(line.current_seqno());
prior.logical.set_last_cell_was_wrapped(false, seqno);
@ -794,7 +791,7 @@ mod test {
);
let line = &offset[0];
let coords = (0..line.logical.cells().len())
let coords = (0..line.logical.len())
.map(|idx| line.logical_x_to_physical_coord(idx))
.collect::<Vec<_>>();
snapshot!(

View File

@ -124,7 +124,7 @@ impl Screen {
}
Some(mut prior) => {
if phys_idx == cursor_y {
logical_cursor_x = Some(cursor_x + prior.cells().len());
logical_cursor_x = Some(cursor_x + prior.len());
}
prior.append_line(line, seqno);
prior
@ -142,7 +142,7 @@ impl Screen {
adjusted_cursor = (last_x, rewrapped.len() + num_lines);
}
if line.cells().len() <= physical_cols {
if line.len() <= physical_cols {
rewrapped.push_back(line);
} else {
for line in line.wrap(physical_cols, seqno) {
@ -358,7 +358,7 @@ impl Screen {
let line = self.line_mut(line_idx);
line.update_last_change_seqno(seqno);
line.insert_cell(x, Cell::default(), right_margin, seqno);
if line.cells().len() > phys_cols {
if line.len() > phys_cols {
// Don't allow the line width to grow beyond
// the physical width
line.resize(phys_cols, seqno);
@ -400,10 +400,10 @@ impl Screen {
line.cells_mut().get_mut(x)
}
pub fn get_cell(&self, x: usize, y: VisibleRowIndex) -> Option<&Cell> {
pub fn get_cell(&mut self, x: usize, y: VisibleRowIndex) -> Option<&Cell> {
let line_idx = self.phys_row(y);
let line = self.lines.get(line_idx)?;
line.cells().get(x)
let line = self.lines.get_mut(line_idx)?;
line.cells_mut().get(x)
}
pub fn clear_line(
@ -541,7 +541,7 @@ impl Screen {
// Copy the source cells first
let cells = {
self.lines[src_row]
.cells()
.cells_mut()
.iter()
.skip(left_and_right_margins.start)
.take(left_and_right_margins.end - left_and_right_margins.start)
@ -554,7 +554,7 @@ impl Screen {
dest_row.update_last_change_seqno(seqno);
let dest_range =
left_and_right_margins.start..left_and_right_margins.start + cells.len();
if dest_row.cells().len() < dest_range.end {
if dest_row.len() < dest_range.end {
dest_row.resize(dest_range.end, seqno);
}
@ -780,7 +780,7 @@ impl Screen {
// Copy the source cells first
let cells = {
self.lines[src_row]
.cells()
.cells_mut()
.iter()
.skip(left_and_right_margins.start)
.take(left_and_right_margins.end - left_and_right_margins.start)
@ -793,7 +793,7 @@ impl Screen {
dest_row.update_last_change_seqno(seqno);
let dest_range =
left_and_right_margins.start..left_and_right_margins.start + cells.len();
if dest_row.cells().len() < dest_range.end {
if dest_row.len() < dest_range.end {
dest_row.resize(dest_range.end, seqno);
}
let tail_range = dest_range.end..left_and_right_margins.end;

View File

@ -168,7 +168,7 @@ impl TerminalState {
);
remain_x = remain_x.saturating_sub(cell_pixel_width);
let mut cell = self
.screen()
.screen_mut()
.get_cell(cursor_x + x, cursor_y)
.cloned()
.unwrap_or_else(Cell::blank);

View File

@ -1803,13 +1803,8 @@ impl TerminalState {
for y in top..=bottom {
let line_idx = screen.phys_row(VisibleRowIndex::from(y_origin + y));
let line = screen.line_mut(line_idx);
for (col, cell) in line
.cells()
.iter()
.enumerate()
.skip(x_origin + left as usize)
{
if col > x_origin + right as usize {
for cell in line.visible_cells().skip(x_origin + left as usize) {
if cell.cell_index() > x_origin + right as usize {
break;
}
@ -2059,13 +2054,13 @@ impl TerminalState {
let line_idx = screen.phys_row(y);
let line = screen.line_mut(line_idx);
match line.cells().get(to_copy).cloned() {
match line.cells_mut().get(to_copy).cloned() {
None => Cell::blank(),
Some(candidate) => {
if candidate.str() == " " && to_copy > 0 {
// It's a blank. It may be the second part of
// a double-wide pair; look ahead of it.
let prior = &line.cells()[to_copy - 1];
let prior = &line.cells_mut()[to_copy - 1];
if prior.width() > 1 {
prior.clone()
} else {

View File

@ -226,8 +226,8 @@ fn assert_lines_equal(
};
if compare.contains(Compare::ATTRS) {
let line_attrs: Vec<_> = line.cells().iter().map(|c| c.attrs().clone()).collect();
let expect_attrs: Vec<_> = expect.cells().iter().map(|c| c.attrs().clone()).collect();
let line_attrs: Vec<_> = line.visible_cells().map(|c| c.attrs().clone()).collect();
let expect_attrs: Vec<_> = expect.visible_cells().map(|c| c.attrs().clone()).collect();
assert_eq!(
expect_attrs,
line_attrs,

View File

@ -74,12 +74,18 @@ pub struct ZoneRange {
#[cfg_attr(feature = "use_serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq)]
pub struct Line {
cells: Vec<Cell>,
cells: CellStorage,
zones: Vec<ZoneRange>,
seqno: SequenceNo,
bits: LineBits,
}
#[cfg_attr(feature = "use_serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq)]
enum CellStorage {
V(Vec<Cell>),
}
#[derive(Debug, Clone, PartialEq)]
pub enum DoubleClickRange {
Range(Range<usize>),
@ -93,7 +99,7 @@ impl Line {
let bits = LineBits::NONE;
Self {
bits,
cells,
cells: CellStorage::V(cells),
seqno,
zones: vec![],
}
@ -103,7 +109,7 @@ impl Line {
let bits = LineBits::NONE;
Self {
bits,
cells,
cells: CellStorage::V(cells),
seqno,
zones: vec![],
}
@ -115,7 +121,7 @@ impl Line {
let bits = LineBits::NONE;
Self {
bits,
cells,
cells: CellStorage::V(cells),
seqno,
zones: vec![],
}
@ -139,7 +145,7 @@ impl Line {
}
Line {
cells,
cells: CellStorage::V(cells),
bits: LineBits::NONE,
seqno,
zones: vec![],
@ -152,7 +158,7 @@ impl Line {
seqno: SequenceNo,
) -> Line {
let mut line = Self::from_text(s, attrs, seqno, None);
line.cells
line.cells_mut()
.last_mut()
.map(|cell| cell.attrs_mut().set_wrapped(true));
line
@ -164,19 +170,21 @@ impl Line {
seqno: SequenceNo,
blank_attr: CellAttributes,
) {
for c in &mut self.cells {
{
let cells = self.coerce_vec_storage();
for c in cells.iter_mut() {
*c = Cell::blank_with_attrs(blank_attr.clone());
}
self.cells
.resize_with(width, || Cell::blank_with_attrs(blank_attr.clone()));
self.cells.shrink_to_fit();
cells.resize_with(width, || Cell::blank_with_attrs(blank_attr.clone()));
cells.shrink_to_fit();
}
self.update_last_change_seqno(seqno);
self.invalidate_zones();
self.bits = LineBits::NONE;
}
pub fn resize(&mut self, width: usize, seqno: SequenceNo) {
self.cells.resize_with(width, Cell::blank);
self.coerce_vec_storage().resize_with(width, Cell::blank);
self.update_last_change_seqno(seqno);
self.invalidate_zones();
}
@ -184,20 +192,21 @@ impl Line {
/// Wrap the line so that it fits within the provided width.
/// Returns the list of resultant line(s)
pub fn wrap(mut self, width: usize, seqno: SequenceNo) -> Vec<Self> {
if let Some(end_idx) = self.cells.iter().rposition(|c| c.str() != " ") {
self.cells.resize_with(end_idx + 1, Cell::blank);
let cells = self.coerce_vec_storage();
if let Some(end_idx) = cells.iter().rposition(|c| c.str() != " ") {
cells.resize_with(end_idx + 1, Cell::blank);
let mut lines: Vec<_> = self
.cells
let mut lines: Vec<_> = cells
.chunks_mut(width)
.map(|chunk| {
let chunk_len = chunk.len();
let mut line = Line {
cells: chunk.to_vec(),
cells: CellStorage::V(chunk.to_vec()),
bits: LineBits::NONE,
seqno: seqno,
zones: vec![],
};
if line.cells.len() == width {
if chunk_len == width {
// Ensure that we don't forget that we wrapped
line.set_last_cell_was_wrapped(true, seqno);
}
@ -384,11 +393,12 @@ impl Line {
// with other zones as a result of clear-to-eol and
// clear-to-end-of-screen sequences. We don't want
// those to affect the zones that we compute here
let last_non_blank = self
.cells()
.iter()
.rposition(|cell| *cell != blank_cell)
.unwrap_or(self.cells().len());
let mut last_non_blank = self.len();
for cell in self.visible_cells() {
if cell.str() != blank_cell.str() || cell.attrs() != blank_cell.attrs() {
last_non_blank = cell.cell_index();
}
}
for cell in self.visible_cells() {
if cell.cell_index() > last_non_blank {
@ -446,7 +456,8 @@ impl Line {
return;
}
for cell in &mut self.cells {
let cells = self.coerce_vec_storage();
for cell in cells {
let replace = match cell.attrs().hyperlink() {
Some(ref link) if link.is_implicit() => Some(Cell::new_grapheme(
cell.str(),
@ -500,18 +511,23 @@ impl Line {
// string.
let mut cell_idx = 0;
for (byte_idx, _grapheme) in line.grapheme_indices(true) {
let cell = &mut self.cells[cell_idx];
let cells = self.coerce_vec_storage();
let cell = &mut cells[cell_idx];
let mut matched = false;
for m in &matches {
if m.range.contains(&byte_idx) {
let attrs = cell.attrs_mut();
// Don't replace existing links
if attrs.hyperlink().is_none() {
attrs.set_hyperlink(Some(Arc::clone(&m.link)));
self.bits |= LineBits::HAS_IMPLICIT_HYPERLINKS;
matched = true;
}
}
}
cell_idx += cell.width();
if matched {
self.bits |= LineBits::HAS_IMPLICIT_HYPERLINKS;
}
}
}
@ -532,10 +548,11 @@ impl Line {
}
pub fn split_off(&mut self, idx: usize, seqno: SequenceNo) -> Self {
let cells = self.cells.split_off(idx);
let my_cells = self.coerce_vec_storage();
let cells = my_cells.split_off(idx);
Self {
bits: self.bits,
cells,
cells: CellStorage::V(cells),
seqno,
zones: vec![],
}
@ -546,7 +563,7 @@ impl Line {
click_col: usize,
is_word: F,
) -> DoubleClickRange {
let len = self.cells.len();
let len = self.len();
if click_col >= len {
return DoubleClickRange::Range(click_col..click_col);
@ -557,23 +574,33 @@ impl Line {
// TODO: look back and look ahead for cells that are hidden by
// a preceding multi-wide cell
for (idx, cell) in self.cells.iter().enumerate().skip(click_col) {
if !is_word(cell.str()) {
break;
}
upper = idx + 1;
}
for (idx, cell) in self.cells.iter().enumerate().rev() {
if idx > click_col {
let cells = self.visible_cells().collect::<Vec<_>>();
for cell in &cells {
if cell.cell_index() < click_col {
continue;
}
if !is_word(cell.str()) {
break;
}
lower = idx;
upper = cell.cell_index() + 1;
}
for cell in cells.iter().rev() {
if cell.cell_index() > click_col {
continue;
}
if !is_word(cell.str()) {
break;
}
lower = cell.cell_index();
}
if upper > lower && self.cells[upper.min(len) - 1].attrs().wrapped() {
if upper > lower
&& upper >= len
&& cells
.last()
.map(|cell| cell.attrs().wrapped())
.unwrap_or(false)
{
DoubleClickRange::RangeWithWrap(lower..upper)
} else {
DoubleClickRange::Range(lower..upper)
@ -608,7 +635,7 @@ impl Line {
}
Self {
bits: LineBits::NONE,
cells,
cells: CellStorage::V(cells),
seqno: self.current_seqno(),
zones: vec![],
}
@ -633,8 +660,9 @@ impl Line {
}
fn raw_set_cell(&mut self, idx: usize, mut cell: Cell, clear: bool) {
let cells = self.coerce_vec_storage();
if !clear {
if let Some(images) = self.cells[idx].attrs().images() {
if let Some(images) = cells[idx].attrs().images() {
for image in images {
if image.has_placement_id() {
cell.attrs_mut().attach_image(Box::new(image));
@ -642,7 +670,7 @@ impl Line {
}
}
}
self.cells[idx] = cell;
cells[idx] = cell;
}
fn set_cell_impl(&mut self, idx: usize, cell: Cell, clear: bool, seqno: SequenceNo) -> &Cell {
@ -655,8 +683,11 @@ impl Line {
let width = cell.width().max(1);
// if the line isn't wide enough, pad it out with the default attributes.
if idx + width > self.cells.len() {
self.cells.resize_with(idx + width, Cell::blank);
{
let cells = self.coerce_vec_storage();
if idx + width > cells.len() {
cells.resize_with(idx + width, Cell::blank);
}
}
self.invalidate_implicit_hyperlinks(seqno);
@ -674,7 +705,7 @@ impl Line {
}
self.raw_set_cell(idx, cell, clear);
&self.cells[idx]
&self.cells_mut()[idx]
}
/// Place text starting at the specified column index.
@ -702,11 +733,12 @@ impl Line {
// 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();
let cells = self.coerce_vec_storage();
let width = cells[prior].width();
if width > 1 {
let attrs = self.cells[prior].attrs().clone();
let attrs = cells[prior].attrs().clone();
for nerf in prior..prior + width {
self.cells[nerf] = Cell::blank_with_attrs(attrs.clone());
cells[nerf] = Cell::blank_with_attrs(attrs.clone());
}
}
}
@ -715,48 +747,51 @@ impl Line {
pub fn insert_cell(&mut self, x: usize, cell: Cell, right_margin: usize, seqno: SequenceNo) {
self.invalidate_implicit_hyperlinks(seqno);
if right_margin <= self.cells.len() {
self.cells.remove(right_margin - 1);
let cells = self.coerce_vec_storage();
if right_margin <= cells.len() {
cells.remove(right_margin - 1);
}
if x >= self.cells.len() {
self.cells.resize_with(x, Cell::blank);
if x >= cells.len() {
cells.resize_with(x, Cell::blank);
}
// If we're inserting a wide cell, we should also insert the overlapped cells.
// We insert them first so that the grapheme winds up left-most.
let width = cell.width();
for _ in 1..=width.saturating_sub(1) {
self.cells
.insert(x, Cell::blank_with_attrs(cell.attrs().clone()));
cells.insert(x, Cell::blank_with_attrs(cell.attrs().clone()));
}
self.cells.insert(x, cell);
cells.insert(x, cell);
self.update_last_change_seqno(seqno);
self.invalidate_zones();
}
pub fn erase_cell(&mut self, x: usize, seqno: SequenceNo) {
if x >= self.cells.len() {
if x >= self.len() {
// Already implicitly erased
return;
}
self.invalidate_implicit_hyperlinks(seqno);
self.invalidate_grapheme_at_or_before(x);
self.cells.remove(x);
self.cells.push(Cell::default());
{
let cells = self.coerce_vec_storage();
cells.remove(x);
cells.push(Cell::default());
}
self.update_last_change_seqno(seqno);
self.invalidate_zones();
}
pub fn remove_cell(&mut self, x: usize, seqno: SequenceNo) {
if x >= self.cells.len() {
if x >= self.len() {
// Already implicitly removed
return;
}
self.invalidate_implicit_hyperlinks(seqno);
self.invalidate_grapheme_at_or_before(x);
self.cells.remove(x);
self.coerce_vec_storage().remove(x);
self.update_last_change_seqno(seqno);
self.invalidate_zones();
}
@ -769,14 +804,14 @@ impl Line {
blank_attr: CellAttributes,
) {
self.invalidate_implicit_hyperlinks(seqno);
if x < self.cells.len() {
if x < self.len() {
self.invalidate_grapheme_at_or_before(x);
self.cells.remove(x);
self.coerce_vec_storage().remove(x);
}
if right_margin <= self.cells.len() + 1
if right_margin <= self.len() + 1
/* we just removed one */
{
self.cells
self.coerce_vec_storage()
.insert(right_margin - 1, Cell::blank_with_attrs(blank_attr));
}
self.update_last_change_seqno(seqno);
@ -785,12 +820,12 @@ impl Line {
pub fn prune_trailing_blanks(&mut self, seqno: SequenceNo) {
let def_attr = CellAttributes::blank();
if let Some(end_idx) = self
.cells
let cells = self.coerce_vec_storage();
if let Some(end_idx) = cells
.iter()
.rposition(|c| c.str() != " " || c.attrs() != &def_attr)
{
self.cells.resize_with(end_idx + 1, Cell::blank);
cells.resize_with(end_idx + 1, Cell::blank);
self.update_last_change_seqno(seqno);
self.invalidate_zones();
}
@ -804,6 +839,12 @@ impl Line {
self.prune_trailing_blanks(seqno);
}
pub fn len(&self) -> usize {
match &self.cells {
CellStorage::V(cells) => cells.len(),
}
}
/// 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.
@ -812,9 +853,11 @@ impl Line {
/// 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 = CellIter> {
pub fn visible_cells(&self) -> impl DoubleEndedIterator<Item = CellIter> {
let mut skip_width = 0;
self.cells
match &self.cells {
CellStorage::V(cells) => {
cells
.iter()
.enumerate()
.filter_map(move |(cell_index, cell)| {
@ -827,28 +870,37 @@ impl Line {
}
})
}
pub fn cluster(&self, bidi_hint: Option<ParagraphDirectionHint>) -> Vec<CellCluster> {
CellCluster::make_cluster(self.cells.len(), self.visible_cells(), bidi_hint)
}
}
pub fn cells(&self) -> &[Cell] {
&self.cells
pub fn get_cell(&self, cell_index: usize) -> Option<CellIter> {
self.visible_cells()
.find(|cell| cell.cell_index() == cell_index)
}
pub fn cluster(&self, bidi_hint: Option<ParagraphDirectionHint>) -> Vec<CellCluster> {
CellCluster::make_cluster(self.len(), self.visible_cells(), bidi_hint)
}
fn coerce_vec_storage(&mut self) -> &mut Vec<Cell> {
match &mut self.cells {
CellStorage::V(cells) => cells,
}
}
pub fn cells_mut(&mut self) -> &mut [Cell] {
&mut self.cells
self.coerce_vec_storage().as_mut_slice()
}
/// Return true if the line consists solely of whitespace cells
pub fn is_whitespace(&self) -> bool {
self.cells.iter().all(|c| c.str() == " ")
self.visible_cells().all(|c| c.str() == " ")
}
/// Return true if the last cell in the line has the wrapped attribute,
/// indicating that the following line is logically a part of this one.
pub fn last_cell_was_wrapped(&self) -> bool {
self.cells
self.visible_cells()
.last()
.map(|c| c.attrs().wrapped())
.unwrap_or(false)
@ -857,7 +909,8 @@ impl Line {
/// Adjust the value of the wrapped attribute on the last cell of this
/// line.
pub fn set_last_cell_was_wrapped(&mut self, wrapped: bool, seqno: SequenceNo) {
if let Some(cell) = self.cells.last_mut() {
let cells = self.coerce_vec_storage();
if let Some(cell) = cells.last_mut() {
cell.attrs_mut().set_wrapped(wrapped);
self.update_last_change_seqno(seqno);
}
@ -868,7 +921,8 @@ impl Line {
/// This function is used by rewrapping logic when joining wrapped
/// lines back together.
pub fn append_line(&mut self, mut other: Line, seqno: SequenceNo) {
self.cells.append(&mut other.cells);
self.coerce_vec_storage()
.append(&mut other.coerce_vec_storage());
self.update_last_change_seqno(seqno);
self.invalidate_zones();
}
@ -878,7 +932,7 @@ impl Line {
/// Use set_cell if you need to modify the textual content of the
/// cell, so that important invariants are upheld.
pub fn cells_mut_for_attr_changes_only(&mut self) -> &mut [Cell] {
&mut self.cells
self.coerce_vec_storage().as_mut_slice()
}
/// Given a starting attribute value, produce a series of Change
@ -1026,7 +1080,7 @@ mod test {
line.scan_and_create_hyperlinks(&rules);
assert!(line.has_hyperlink());
assert_eq!(
line.cells().to_vec(),
line.coerce_vec_storage().to_vec(),
vec![
Cell::new_grapheme("", CellAttributes::default(), None),
Cell::new(' ', CellAttributes::default()), // double width spacer

View File

@ -521,10 +521,10 @@ impl Surface {
/// Returns the cell data for the screen.
/// This is intended to be used for testing purposes.
pub fn screen_cells(&self) -> Vec<&[Cell]> {
pub fn screen_cells(&mut self) -> Vec<&mut [Cell]> {
let mut lines = Vec::new();
for line in &self.lines {
lines.push(line.cells());
for line in &mut self.lines {
lines.push(line.cells_mut());
}
lines
}
@ -692,7 +692,7 @@ impl Surface {
}
result.append(&mut changes);
if let Some(c) = line.cells().last() {
if let Some(c) = line.visible_cells().last() {
attr = c.attrs().clone();
}
}

View File

@ -251,15 +251,15 @@ impl RenderableInner {
let text_line = Line::from_text(text, &attrs, SEQ_ZERO, None);
if row == 0 {
for cell in text_line.cells() {
line.set_cell(self.cursor_position.x, cell.clone(), SEQ_ZERO);
for cell in text_line.visible_cells() {
line.set_cell(self.cursor_position.x, cell.as_cell(), SEQ_ZERO);
self.cursor_position.x += cell.width();
}
} else {
// The pasted line replaces the data for the existing line
line.resize_and_clear(0, SEQ_ZERO, CellAttributes::default());
line.append_line(text_line, SEQ_ZERO);
self.cursor_position.x = line.cells().len();
self.cursor_position.x = line.len();
}
}

View File

@ -3638,6 +3638,16 @@ impl BlockKey {
})
}
pub fn from_cell_iter(cell: termwiz::surface::line::CellIter) -> Option<Self> {
let mut chars = cell.str().chars();
let first_char = chars.next()?;
if chars.next().is_some() {
None
} else {
Self::from_char(first_char)
}
}
pub fn from_cell(cell: &termwiz::cell::Cell) -> Option<Self> {
let mut chars = cell.str().chars();
let first_char = chars.next()?;

View File

@ -577,9 +577,9 @@ impl CopyRenderable {
if let Some(line) = lines.get(0) {
self.cursor.y = top;
self.cursor.x = 0;
for (x, cell) in line.cells().iter().enumerate().rev() {
for cell in line.visible_cells().rev() {
if cell.str() != " " {
self.cursor.x = x;
self.cursor.x = cell.cell_index();
break;
}
}
@ -593,9 +593,9 @@ impl CopyRenderable {
if let Some(line) = lines.get(0) {
self.cursor.y = top;
self.cursor.x = 0;
for (x, cell) in line.cells().iter().enumerate() {
for cell in line.visible_cells() {
if cell.str() != " " {
self.cursor.x = x;
self.cursor.x = cell.cell_index();
break;
}
}
@ -645,7 +645,7 @@ impl CopyRenderable {
if let Some(line) = lines.get(0) {
self.cursor.y = top;
if self.cursor.x == usize::max_value() {
self.cursor.x = line.cells().len().saturating_sub(1);
self.cursor.x = line.len().saturating_sub(1);
}
let s = line.columns_as_str(0..self.cursor.x.saturating_add(1));
@ -693,7 +693,7 @@ impl CopyRenderable {
let (top, lines) = self.delegate.get_lines(y..y + 1);
if let Some(line) = lines.get(0) {
self.cursor.y = top;
let width = line.cells().len();
let width = line.len();
let s = line.columns_as_str(self.cursor.x..width + 1);
let mut words = s.split_word_bounds();

View File

@ -383,7 +383,7 @@ impl LauncherState {
}
let mut line = crate::tabbar::parse_status_text(&entry.label, attr.clone());
if line.cells().len() > max_width {
if line.len() > max_width {
line.resize(max_width, termwiz::surface::SEQ_ZERO);
}
changes.append(&mut line.changes(&attr));

View File

@ -499,8 +499,7 @@ impl Pane for QuickSelectOverlay {
}
for (idx, c) in m.label.chars().enumerate() {
let mut attr = line
.cells()
.get(idx)
.get_cell(idx)
.map(|cell| cell.attrs().clone())
.unwrap_or_else(|| CellAttributes::default());
attr.set_background(AnsiColor::Black)

View File

@ -270,8 +270,8 @@ fn lines_to_escapes(lines: Vec<Line>) -> anyhow::Result<String> {
for line in lines {
changes.append(&mut line.changes(&attr));
changes.push(Change::Text("\r\n".to_string()));
if let Some(a) = line.cells().last().map(|cell| cell.attrs()) {
attr = a.clone();
if let Some(a) = line.visible_cells().last().map(|cell| cell.attrs().clone()) {
attr = a;
}
}
changes.push(Change::AllAttributes(CellAttributes::blank()));

View File

@ -174,7 +174,7 @@ mod test {
let cell_idx = cluster.byte_to_cell_idx(info.cluster as usize);
let num_cells = cluster.byte_to_cell_width(info.cluster as usize);
let followed_by_space = match line.cells().get(cell_idx + 1) {
let followed_by_space = match line.get_cell(cell_idx + 1) {
Some(cell) => cell.str() == " ",
None => false,
};

View File

@ -74,7 +74,7 @@ fn call_format_tab_title(
Ok(Some(TitleText {
items,
len: line.cells().len(),
len: line.len(),
}))
}
_ => {
@ -242,7 +242,7 @@ impl TabBarState {
let number_of_tabs = tab_titles.len();
let available_cells =
title_width.saturating_sub(number_of_tabs.saturating_sub(1) + new_tab.cells().len());
title_width.saturating_sub(number_of_tabs.saturating_sub(1) + new_tab.len());
let tab_width_max = if config.use_fancy_tab_bar || available_cells >= titles_len {
// We can render each title with its full width
usize::max_value()
@ -294,11 +294,11 @@ impl TabBarState {
);
let title = tab_line.clone();
if tab_line.cells().len() > tab_width_max {
if tab_line.len() > tab_width_max {
tab_line.resize(tab_width_max, SEQ_ZERO);
}
let width = tab_line.cells().len();
let width = tab_line.len();
items.push(TabEntry {
item: TabBarItem::Tab { tab_idx, active },
@ -313,12 +313,12 @@ impl TabBarState {
// New tab button
{
let hover = is_tab_hover(mouse_x, x, new_tab_hover.cells().len());
let hover = is_tab_hover(mouse_x, x, new_tab_hover.len());
let new_tab_button = if hover { &new_tab_hover } else { &new_tab };
let button_start = x;
let width = new_tab_button.cells().len();
let width = new_tab_button.len();
line.append_line(new_tab_button.clone(), SEQ_ZERO);
@ -347,12 +347,12 @@ impl TabBarState {
width: status_space_available,
});
while status_line.cells().len() > status_space_available {
while status_line.len() > status_space_available {
status_line.remove_cell(0, SEQ_ZERO);
}
line.append_line(status_line, SEQ_ZERO);
while line.cells().len() < title_width {
while line.len() < title_width {
line.insert_cell(x, black_cell.clone(), title_width, SEQ_ZERO);
}

View File

@ -663,7 +663,7 @@ impl super::TermWindow {
);
let new_highlight = if top == stable_row {
if let Some(line) = lines.get_mut(0) {
if let Some(cell) = line.cells().get(column) {
if let Some(cell) = line.get_cell(column) {
cell.attrs().hyperlink().cloned()
} else {
None

View File

@ -496,16 +496,14 @@ impl super::TermWindow {
let bg_color = item
.title
.cells()
.get(0)
.get_cell(0)
.and_then(|c| match c.attrs().background() {
ColorAttribute::Default => None,
col => Some(palette.resolve_bg(col)),
});
let fg_color = item
.title
.cells()
.get(0)
.get_cell(0)
.and_then(|c| match c.attrs().foreground() {
ColorAttribute::Default => None,
col => Some(palette.resolve_fg(col)),
@ -1927,8 +1925,7 @@ impl super::TermWindow {
..params.cursor.x
+ params
.line
.cells()
.get(params.cursor.x)
.get_cell(params.cursor.x)
.map(|c| c.width())
.unwrap_or(1)
} else {
@ -2077,8 +2074,7 @@ impl super::TermWindow {
// Consider cursor
if !cursor_range.is_empty() {
let (fg_color, bg_color) = if let Some(c) = params.line.cells().get(cursor_range.start)
{
let (fg_color, bg_color) = if let Some(c) = params.line.get_cell(cursor_range.start) {
let attrs = c.attrs();
let bg_color = params.palette.resolve_bg(attrs.background()).to_linear();
@ -2209,8 +2205,8 @@ impl super::TermWindow {
* height_scale;
if self.config.custom_block_glyphs {
if let Some(cell) = params.line.cells().get(visual_cell_idx) {
if let Some(block) = BlockKey::from_cell(cell) {
if let Some(cell) = params.line.get_cell(visual_cell_idx) {
if let Some(block) = BlockKey::from_cell_iter(cell) {
texture.replace(
gl_state
.glyph_cache
@ -2738,8 +2734,8 @@ impl super::TermWindow {
let cell_idx = cluster.byte_to_cell_idx(info.cluster as usize);
if self.config.custom_block_glyphs {
if let Some(cell) = line.cells().get(cell_idx) {
if BlockKey::from_cell(cell).is_some() {
if let Some(cell) = line.get_cell(cell_idx) {
if BlockKey::from_cell_iter(cell).is_some() {
// Don't bother rendering the glyph from the font, as it can
// have incorrect advance metrics.
// Instead, just use our pixel-perfect cell metrics
@ -2759,7 +2755,7 @@ impl super::TermWindow {
}
}
let followed_by_space = match line.cells().get(cell_idx + 1) {
let followed_by_space = match line.get_cell(cell_idx + 1) {
Some(cell) => cell.str() == " ",
None => false,
};

View File

@ -34,7 +34,7 @@ impl super::TermWindow {
for (idx, phys) in line.physical_lines.iter().enumerate() {
let this_row = line.first_row + idx as StableRowIndex;
if this_row >= first_row && this_row < last_row {
let last_phys_idx = phys.cells().len().saturating_sub(1);
let last_phys_idx = phys.len().saturating_sub(1);
let cols = sel.cols_for_row(this_row, rectangular);
let last_col_idx = cols.end.saturating_sub(1).min(last_phys_idx);
let mut col_span = phys.columns_as_line(cols);
@ -51,8 +51,7 @@ impl super::TermWindow {
last_was_wrapped = last_col_idx == last_phys_idx
&& phys
.cells()
.get(last_col_idx)
.get_cell(last_col_idx)
.map(|c| c.attrs().wrapped())
.unwrap_or(false);
}
@ -85,7 +84,7 @@ impl super::TermWindow {
for (idx, phys) in line.physical_lines.iter().enumerate() {
let this_row = line.first_row + idx as StableRowIndex;
if this_row >= first_row && this_row < last_row {
let last_phys_idx = phys.cells().len().saturating_sub(1);
let last_phys_idx = phys.len().saturating_sub(1);
let cols = sel.cols_for_row(this_row, rectangular);
let last_col_idx = cols.end.saturating_sub(1).min(last_phys_idx);
let col_span = phys.columns_as_str(cols);
@ -99,8 +98,7 @@ impl super::TermWindow {
last_was_wrapped = last_col_idx == last_phys_idx
&& phys
.cells()
.get(last_col_idx)
.get_cell(last_col_idx)
.map(|c| c.attrs().wrapped())
.unwrap_or(false);
}

View File

@ -621,7 +621,7 @@ impl SessionHandler {
let (_, lines) = pane.get_lines(line_idx..line_idx + 1);
'found_data: for line in lines {
if let Some(cell) = line.cells().get(cell_idx) {
if let Some(cell) = line.get_cell(cell_idx) {
if let Some(images) = cell.attrs().images() {
for im in images {
if im.image_data().hash() == data_hash {