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:
parent
e26d634da1
commit
614900f85c
@ -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.
|
||||
|
@ -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!(
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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()?;
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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));
|
||||
|
@ -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)
|
||||
|
@ -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()));
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user