feat(terminal): port Screen, ScreenSet, Tabstops and Selection
Ports ZIG/src/terminal/{Screen,ScreenSet,Tabstops,Selection}.zig on top
of the page/PageList layer. The cursor holds a TrackedPin; Zig's cached
page_row/page_cell pointers become accessor methods. Selection methods
take the PageList explicitly because tracked bounds are handles.
Screen tests use minimal plain-text dump/selection-string helpers that
follow the formatter's semantics; they will delegate to the real
formatter once formatter.zig is ported.
Gaps found in the PageList layer are recorded for the parity pass in
docs/rewrite/notes/ghostty-terminal-pagelist-requests.md; three resize
tests are #[ignore]d pending those fixes (PageList::resize keeps
trailing blank rows Zig drops). Kitty image storage and StringMap are
placeholders.
Test Plan:
- cargo test -p ghostty-terminal (443 passed, 3 ignored; Screen 183/184,
Selection 18/18, Tabstops 4/5 - skipped tests use allocator failure
injection)
This commit is contained in:
1 parent
99d3ff9d41
commit
271f5e7bb5
16 files changed
+10070
No files matched your search
@@ -0,0 +1,148 @@
|
||||
//! Pin-based iterators.
|
||||
//!
|
||||
//! Zig's `PageList.Pin` has `rowIterator`, `cellIterator` and
|
||||
//! `promptIterator` methods that start from a pin and use an inclusive limit
|
||||
//! *pin* (the Rust `PageList` iterators are point-based). These are local
|
||||
//! equivalents used by `Screen` and `Selection`.
|
||||
//!
|
||||
//! TODO(integration): replace with `Pin` methods in `page_list.rs` once
|
||||
//! available (see docs/rewrite/notes/ghostty-terminal-pagelist-requests.md).
|
||||
use crate::{
|
||||
page_list::{Direction, PageList, Pin, PromptIterator},
|
||||
point::{Coordinate, Point, Tag},
|
||||
};
|
||||
|
||||
fn same_row(a: Pin, b: Pin) -> bool {
|
||||
a.node == b.node && a.y == b.y
|
||||
}
|
||||
|
||||
/// Iterates the rows (x = 0) from `start` toward `limit` (inclusive).
|
||||
pub struct RowIter<'a> {
|
||||
list: &'a PageList,
|
||||
current: Option<Pin>,
|
||||
limit: Option<Pin>,
|
||||
direction: Direction,
|
||||
}
|
||||
|
||||
impl Iterator for RowIter<'_> {
|
||||
type Item = Pin;
|
||||
|
||||
fn next(&mut self) -> Option<Pin> {
|
||||
let cur = self.current?;
|
||||
let result = Pin { x: 0, ..cur };
|
||||
if self.limit.is_some_and(|l| same_row(l, cur)) {
|
||||
self.current = None;
|
||||
} else {
|
||||
self.current = match self.direction {
|
||||
Direction::RightDown => cur.down(self.list, 1),
|
||||
Direction::LeftUp => cur.up(self.list, 1),
|
||||
};
|
||||
}
|
||||
Some(result)
|
||||
}
|
||||
}
|
||||
|
||||
/// Equivalent of `Pin.rowIterator(direction, limit)`. The limit is inclusive.
|
||||
pub fn row_iterator(
|
||||
list: &PageList,
|
||||
start: Pin,
|
||||
direction: Direction,
|
||||
limit: Option<Pin>,
|
||||
) -> RowIter<'_> {
|
||||
RowIter {
|
||||
list,
|
||||
current: Some(start),
|
||||
limit,
|
||||
direction,
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterates cells from `start` toward the end of the `limit` row.
|
||||
///
|
||||
/// Note that, as in Zig, the limit only applies at row granularity: iteration
|
||||
/// continues to the end (or start, for `LeftUp`) of the limit row.
|
||||
pub struct CellIter<'a> {
|
||||
list: &'a PageList,
|
||||
cell: Option<Pin>,
|
||||
limit: Option<Pin>,
|
||||
direction: Direction,
|
||||
}
|
||||
|
||||
impl Iterator for CellIter<'_> {
|
||||
type Item = Pin;
|
||||
|
||||
fn next(&mut self) -> Option<Pin> {
|
||||
let cell = self.cell?;
|
||||
let cols = self
|
||||
.list
|
||||
.node(cell.node)
|
||||
.map_or(self.list.cols, |n| n.data.size.cols);
|
||||
let at_limit = self.limit.is_some_and(|l| same_row(l, cell));
|
||||
self.cell = match self.direction {
|
||||
Direction::RightDown => {
|
||||
if cell.x + 1 < cols {
|
||||
Some(Pin {
|
||||
x: cell.x + 1,
|
||||
..cell
|
||||
})
|
||||
} else if at_limit {
|
||||
None
|
||||
} else {
|
||||
cell.down(self.list, 1).map(|p| Pin { x: 0, ..p })
|
||||
}
|
||||
}
|
||||
Direction::LeftUp => {
|
||||
if cell.x > 0 {
|
||||
Some(Pin {
|
||||
x: cell.x - 1,
|
||||
..cell
|
||||
})
|
||||
} else if at_limit {
|
||||
None
|
||||
} else {
|
||||
cell.up(self.list, 1).map(|p| {
|
||||
let cols = self.list.node(p.node).map_or(cols, |n| n.data.size.cols);
|
||||
Pin { x: cols - 1, ..p }
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
Some(cell)
|
||||
}
|
||||
}
|
||||
|
||||
/// Equivalent of `Pin.cellIterator(direction, limit)`.
|
||||
pub fn cell_iterator(
|
||||
list: &PageList,
|
||||
start: Pin,
|
||||
direction: Direction,
|
||||
limit: Option<Pin>,
|
||||
) -> CellIter<'_> {
|
||||
CellIter {
|
||||
list,
|
||||
cell: Some(start),
|
||||
limit,
|
||||
direction,
|
||||
}
|
||||
}
|
||||
|
||||
/// Equivalent of `Pin.promptIterator(direction, limit)`.
|
||||
pub fn prompt_iterator(
|
||||
list: &PageList,
|
||||
start: Pin,
|
||||
direction: Direction,
|
||||
limit: Option<Pin>,
|
||||
) -> PromptIterator<'_> {
|
||||
let pt = |p: Pin| {
|
||||
list.point_from_pin(Tag::Screen, p)
|
||||
.unwrap_or(Point::Screen(Coordinate::default()))
|
||||
};
|
||||
match direction {
|
||||
Direction::RightDown => list.prompt_iterator(direction, pt(start), limit.map(pt)),
|
||||
Direction::LeftUp => list.prompt_iterator(
|
||||
direction,
|
||||
limit.map_or(Point::Screen(Coordinate::default()), pt),
|
||||
Some(pt(start)),
|
||||
),
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user