diff --git a/codec/src/lib.rs b/codec/src/lib.rs index 1870140e4..e1892856c 100644 --- a/codec/src/lib.rs +++ b/codec/src/lib.rs @@ -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. diff --git a/mux/src/pane.rs b/mux/src/pane.rs index c36517cc6..904c2b359 100644 --- a/mux/src/pane.rs +++ b/mux/src/pane.rs @@ -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::>(); snapshot!( diff --git a/term/src/screen.rs b/term/src/screen.rs index 2539e3574..4fd5ee5ac 100644 --- a/term/src/screen.rs +++ b/term/src/screen.rs @@ -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; diff --git a/term/src/terminalstate/image.rs b/term/src/terminalstate/image.rs index 2b6f0e64e..3659a1b98 100644 --- a/term/src/terminalstate/image.rs +++ b/term/src/terminalstate/image.rs @@ -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); diff --git a/term/src/terminalstate/mod.rs b/term/src/terminalstate/mod.rs index 2f774424e..b52cc750a 100644 --- a/term/src/terminalstate/mod.rs +++ b/term/src/terminalstate/mod.rs @@ -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 { diff --git a/term/src/test/mod.rs b/term/src/test/mod.rs index 3c6ff8cad..f05567bb7 100644 --- a/term/src/test/mod.rs +++ b/term/src/test/mod.rs @@ -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, diff --git a/termwiz/src/surface/line.rs b/termwiz/src/surface/line.rs index 34544bd9b..664abf85e 100644 --- a/termwiz/src/surface/line.rs +++ b/termwiz/src/surface/line.rs @@ -74,12 +74,18 @@ pub struct ZoneRange { #[cfg_attr(feature = "use_serde", derive(Serialize, Deserialize))] #[derive(Debug, Clone, PartialEq)] pub struct Line { - cells: Vec, + cells: CellStorage, zones: Vec, seqno: SequenceNo, bits: LineBits, } +#[cfg_attr(feature = "use_serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq)] +enum CellStorage { + V(Vec), +} + #[derive(Debug, Clone, PartialEq)] pub enum DoubleClickRange { Range(Range), @@ -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 { - *c = Cell::blank_with_attrs(blank_attr.clone()); + { + let cells = self.coerce_vec_storage(); + for c in cells.iter_mut() { + *c = Cell::blank_with_attrs(blank_attr.clone()); + } + cells.resize_with(width, || Cell::blank_with_attrs(blank_attr.clone())); + cells.shrink_to_fit(); } - self.cells - .resize_with(width, || Cell::blank_with_attrs(blank_attr.clone())); - self.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 { - 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::>(); + 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,43 +853,54 @@ 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 { + pub fn visible_cells(&self) -> impl DoubleEndedIterator { let mut skip_width = 0; - self.cells - .iter() - .enumerate() - .filter_map(move |(cell_index, cell)| { - if skip_width > 0 { - skip_width -= 1; - None - } else { - skip_width = cell.width().saturating_sub(1); - Some(CellIter::CellRef { cell_index, cell }) - } - }) + match &self.cells { + CellStorage::V(cells) => { + cells + .iter() + .enumerate() + .filter_map(move |(cell_index, cell)| { + if skip_width > 0 { + skip_width -= 1; + None + } else { + skip_width = cell.width().saturating_sub(1); + Some(CellIter::CellRef { cell_index, cell }) + } + }) + } + } + } + + pub fn get_cell(&self, cell_index: usize) -> Option { + self.visible_cells() + .find(|cell| cell.cell_index() == cell_index) } pub fn cluster(&self, bidi_hint: Option) -> Vec { - CellCluster::make_cluster(self.cells.len(), self.visible_cells(), bidi_hint) + CellCluster::make_cluster(self.len(), self.visible_cells(), bidi_hint) } - pub fn cells(&self) -> &[Cell] { - &self.cells + fn coerce_vec_storage(&mut self) -> &mut Vec { + 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 diff --git a/termwiz/src/surface/mod.rs b/termwiz/src/surface/mod.rs index 7c3860ec1..ac8aec276 100644 --- a/termwiz/src/surface/mod.rs +++ b/termwiz/src/surface/mod.rs @@ -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(); } } diff --git a/wezterm-client/src/pane/renderable.rs b/wezterm-client/src/pane/renderable.rs index 6f9522c54..c79c6ee31 100644 --- a/wezterm-client/src/pane/renderable.rs +++ b/wezterm-client/src/pane/renderable.rs @@ -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(); } } diff --git a/wezterm-gui/src/customglyph.rs b/wezterm-gui/src/customglyph.rs index 3195bcac4..fae45a7de 100644 --- a/wezterm-gui/src/customglyph.rs +++ b/wezterm-gui/src/customglyph.rs @@ -3638,6 +3638,16 @@ impl BlockKey { }) } + pub fn from_cell_iter(cell: termwiz::surface::line::CellIter) -> Option { + 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 { let mut chars = cell.str().chars(); let first_char = chars.next()?; diff --git a/wezterm-gui/src/overlay/copy.rs b/wezterm-gui/src/overlay/copy.rs index b217cf599..14160dbfa 100644 --- a/wezterm-gui/src/overlay/copy.rs +++ b/wezterm-gui/src/overlay/copy.rs @@ -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(); diff --git a/wezterm-gui/src/overlay/launcher.rs b/wezterm-gui/src/overlay/launcher.rs index e684b2567..19e0d525d 100644 --- a/wezterm-gui/src/overlay/launcher.rs +++ b/wezterm-gui/src/overlay/launcher.rs @@ -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)); diff --git a/wezterm-gui/src/overlay/quickselect.rs b/wezterm-gui/src/overlay/quickselect.rs index 42dec1391..ffc724fc2 100644 --- a/wezterm-gui/src/overlay/quickselect.rs +++ b/wezterm-gui/src/overlay/quickselect.rs @@ -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) diff --git a/wezterm-gui/src/scripting/guiwin.rs b/wezterm-gui/src/scripting/guiwin.rs index df0860336..b69e544fa 100644 --- a/wezterm-gui/src/scripting/guiwin.rs +++ b/wezterm-gui/src/scripting/guiwin.rs @@ -270,8 +270,8 @@ fn lines_to_escapes(lines: Vec) -> anyhow::Result { 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())); diff --git a/wezterm-gui/src/shapecache.rs b/wezterm-gui/src/shapecache.rs index 9f8ed6050..0d03b04a1 100644 --- a/wezterm-gui/src/shapecache.rs +++ b/wezterm-gui/src/shapecache.rs @@ -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, }; diff --git a/wezterm-gui/src/tabbar.rs b/wezterm-gui/src/tabbar.rs index bb2d829b0..61e3117fd 100644 --- a/wezterm-gui/src/tabbar.rs +++ b/wezterm-gui/src/tabbar.rs @@ -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); } diff --git a/wezterm-gui/src/termwindow/mouseevent.rs b/wezterm-gui/src/termwindow/mouseevent.rs index f58283446..6602b36ab 100644 --- a/wezterm-gui/src/termwindow/mouseevent.rs +++ b/wezterm-gui/src/termwindow/mouseevent.rs @@ -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 diff --git a/wezterm-gui/src/termwindow/render.rs b/wezterm-gui/src/termwindow/render.rs index 9a11c8964..4f5833447 100644 --- a/wezterm-gui/src/termwindow/render.rs +++ b/wezterm-gui/src/termwindow/render.rs @@ -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, }; diff --git a/wezterm-gui/src/termwindow/selection.rs b/wezterm-gui/src/termwindow/selection.rs index cfedbe192..29988cba3 100644 --- a/wezterm-gui/src/termwindow/selection.rs +++ b/wezterm-gui/src/termwindow/selection.rs @@ -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); } diff --git a/wezterm-mux-server-impl/src/sessionhandler.rs b/wezterm-mux-server-impl/src/sessionhandler.rs index 5f758789c..f05296d2f 100644 --- a/wezterm-mux-server-impl/src/sessionhandler.rs +++ b/wezterm-mux-server-impl/src/sessionhandler.rs @@ -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 {