1
1
mirror of https://github.com/wez/wezterm.git synced 2024-12-23 13:21:38 +03:00

add Pane::get_logical_lines

refs: https://github.com/wez/wezterm/issues/527
refs: https://github.com/wez/wezterm/issues/408
This commit is contained in:
Wez Furlong 2021-03-26 13:18:44 -07:00
parent e0f5b41446
commit 57b0911504
3 changed files with 550 additions and 0 deletions

1
Cargo.lock generated
View File

@ -2153,6 +2153,7 @@ dependencies = [
"crossbeam",
"downcast-rs",
"filedescriptor",
"k9",
"lazy_static",
"libc",
"log",

View File

@ -37,3 +37,6 @@ wezterm-term = { path = "../term", features=["use_serde"] }
[target.'cfg(all(windows, target_os="linux", target_os="macos"))'.dependencies]
sysinfo = "0.16"
[dev-dependencies]
k9 = "0.11"

View File

@ -69,6 +69,34 @@ fn schedule_next_paste(paste: &Arc<Mutex<Paste>>) {
.detach();
}
#[derive(Debug, Clone, PartialEq)]
pub struct LogicalLine {
pub physical_lines: Vec<Line>,
pub logical: Line,
pub first_row: StableRowIndex,
}
impl LogicalLine {
pub fn logical_x_to_physical_coord(&self, x: usize) -> (StableRowIndex, usize) {
let mut y = self.first_row;
let mut idx = 0;
for line in &self.physical_lines {
let x_off = x - idx;
let line_len = line.cells().len();
if x_off < line_len {
return (y, x_off);
}
y += 1;
idx += line_len;
}
panic!(
"x={} is outside of this logical line of len {}",
x,
self.logical.cells().len()
);
}
}
/// A Pane represents a view on a terminal
#[async_trait(?Send)]
pub trait Pane: Downcast {
@ -99,6 +127,69 @@ pub trait Pane: Downcast {
/// have its dirty bit set appropriately.
fn get_lines(&self, lines: Range<StableRowIndex>) -> (StableRowIndex, Vec<Line>);
fn get_logical_lines(&self, lines: Range<StableRowIndex>) -> Vec<LogicalLine> {
let (mut first, mut phys) = self.get_lines(lines);
// Look backwards to find the start of the first logical line
while first > 0 {
let (prior, back) = self.get_lines(first - 1..first);
if prior == first {
break;
}
if !back[0].last_cell_was_wrapped() {
break;
}
first = prior;
for (idx, line) in back.into_iter().enumerate() {
phys.insert(idx, line);
}
}
// Look forwards to find the end of the last logical line
while let Some(last) = phys.last() {
if !last.last_cell_was_wrapped() {
break;
}
let next_row = first + phys.len() as StableRowIndex;
let (last_row, mut ahead) = self.get_lines(next_row..next_row + 1);
if last_row != next_row {
break;
}
phys.append(&mut ahead);
}
// Now process this stuff into logical lines
let mut lines = vec![];
for (idx, line) in phys.into_iter().enumerate() {
match lines.last_mut() {
None => {
let logical = line.clone();
lines.push(LogicalLine {
physical_lines: vec![line],
logical,
first_row: first + idx as StableRowIndex,
});
}
Some(prior) => {
if prior.logical.last_cell_was_wrapped() {
prior.logical.set_last_cell_was_wrapped(false);
prior.logical.append_line(line.clone());
prior.physical_lines.push(line);
} else {
let logical = line.clone();
lines.push(LogicalLine {
physical_lines: vec![line],
logical,
first_row: first + idx as StableRowIndex,
});
}
}
}
}
lines
}
/// Returns render related dimensions
fn get_dimensions(&self) -> RenderableDimensions;
@ -170,3 +261,458 @@ pub trait Pane: Downcast {
}
}
impl_downcast!(Pane);
#[cfg(test)]
mod test {
use super::*;
use k9::snapshot;
struct FakePane {
lines: Vec<Line>,
}
impl Pane for FakePane {
fn pane_id(&self) -> PaneId {
unimplemented!()
}
fn get_cursor_position(&self) -> StableCursorPosition {
unimplemented!()
}
fn get_dirty_lines(&self, _: Range<StableRowIndex>) -> RangeSet<StableRowIndex> {
unimplemented!()
}
fn get_lines(&self, lines: Range<StableRowIndex>) -> (StableRowIndex, Vec<Line>) {
let first = lines.start;
(
first,
self.lines
.iter()
.skip(lines.start as usize)
.take((lines.end - lines.start) as usize)
.cloned()
.collect(),
)
}
fn get_dimensions(&self) -> RenderableDimensions {
unimplemented!()
}
fn get_title(&self) -> String {
unimplemented!()
}
fn send_paste(&self, _: &str) -> anyhow::Result<()> {
unimplemented!()
}
fn reader(&self) -> anyhow::Result<Box<dyn std::io::Read + Send>> {
unimplemented!()
}
fn writer(&self) -> RefMut<dyn std::io::Write> {
unimplemented!()
}
fn resize(&self, _: PtySize) -> anyhow::Result<()> {
unimplemented!()
}
fn mouse_event(&self, _: MouseEvent) -> anyhow::Result<()> {
unimplemented!()
}
fn is_dead(&self) -> bool {
unimplemented!()
}
fn palette(&self) -> ColorPalette {
unimplemented!()
}
fn domain_id(&self) -> DomainId {
unimplemented!()
}
fn is_mouse_grabbed(&self) -> bool {
false
}
fn is_alt_screen_active(&self) -> bool {
false
}
fn get_current_working_dir(&self) -> Option<Url> {
None
}
fn key_down(&self, _: KeyCode, _: KeyModifiers) -> anyhow::Result<()> {
unimplemented!()
}
}
#[test]
fn logical_lines() {
let text = "Hello there this is a long line.\nlogical line two\nanother long line here\nlogical line four\nlogical line five\ncap it off with another long line";
let mut physical_lines = vec![];
let width = 20;
for logical in text.split('\n') {
let chunks = logical
.chars()
.collect::<Vec<char>>()
.chunks(width)
.map(|c| c.into_iter().collect::<String>())
.collect::<Vec<String>>();
let n_chunks = chunks.len();
for (idx, chunk) in chunks.into_iter().enumerate() {
let mut line = Line::from_text(&chunk, &Default::default());
if idx < n_chunks - 1 {
line.set_last_cell_was_wrapped(true);
}
physical_lines.push(line);
}
}
fn text_from_lines(lines: &[Line]) -> Vec<String> {
lines.iter().map(|l| l.as_str()).collect::<Vec<_>>()
}
let line_text = text_from_lines(&physical_lines);
snapshot!(
line_text,
r#"
[
"Hello there this is ",
"a long line.",
"logical line two",
"another long line he",
"re",
"logical line four",
"logical line five",
"cap it off with anot",
"her long line",
]
"#
);
let pane = FakePane {
lines: physical_lines,
};
fn summarize_logical_lines(lines: &[LogicalLine]) -> Vec<(StableRowIndex, String)> {
lines
.iter()
.map(|l| (l.first_row, l.logical.as_str()))
.collect::<Vec<_>>()
}
let logical = pane.get_logical_lines(0..30);
snapshot!(
summarize_logical_lines(&logical),
r#"
[
(
0,
"Hello there this is a long line.",
),
(
2,
"logical line two",
),
(
3,
"another long line here",
),
(
5,
"logical line four",
),
(
6,
"logical line five",
),
(
7,
"cap it off with another long line",
),
]
"#
);
// Now try with offset bounds
let offset = pane.get_logical_lines(1..3);
snapshot!(
summarize_logical_lines(&offset),
r#"
[
(
0,
"Hello there this is a long line.",
),
(
2,
"logical line two",
),
]
"#
);
let offset = pane.get_logical_lines(1..4);
snapshot!(
summarize_logical_lines(&offset),
r#"
[
(
0,
"Hello there this is a long line.",
),
(
2,
"logical line two",
),
(
3,
"another long line here",
),
]
"#
);
let offset = pane.get_logical_lines(1..5);
snapshot!(
summarize_logical_lines(&offset),
r#"
[
(
0,
"Hello there this is a long line.",
),
(
2,
"logical line two",
),
(
3,
"another long line here",
),
]
"#
);
let offset = pane.get_logical_lines(1..6);
snapshot!(
summarize_logical_lines(&offset),
r#"
[
(
0,
"Hello there this is a long line.",
),
(
2,
"logical line two",
),
(
3,
"another long line here",
),
(
5,
"logical line four",
),
]
"#
);
let offset = pane.get_logical_lines(1..7);
snapshot!(
summarize_logical_lines(&offset),
r#"
[
(
0,
"Hello there this is a long line.",
),
(
2,
"logical line two",
),
(
3,
"another long line here",
),
(
5,
"logical line four",
),
(
6,
"logical line five",
),
]
"#
);
let offset = pane.get_logical_lines(1..8);
snapshot!(
summarize_logical_lines(&offset),
r#"
[
(
0,
"Hello there this is a long line.",
),
(
2,
"logical line two",
),
(
3,
"another long line here",
),
(
5,
"logical line four",
),
(
6,
"logical line five",
),
(
7,
"cap it off with another long line",
),
]
"#
);
let line = &offset[0];
let coords = (0..line.logical.cells().len())
.map(|idx| line.logical_x_to_physical_coord(idx))
.collect::<Vec<_>>();
snapshot!(
coords,
"
[
(
0,
0,
),
(
0,
1,
),
(
0,
2,
),
(
0,
3,
),
(
0,
4,
),
(
0,
5,
),
(
0,
6,
),
(
0,
7,
),
(
0,
8,
),
(
0,
9,
),
(
0,
10,
),
(
0,
11,
),
(
0,
12,
),
(
0,
13,
),
(
0,
14,
),
(
0,
15,
),
(
0,
16,
),
(
0,
17,
),
(
0,
18,
),
(
0,
19,
),
(
1,
0,
),
(
1,
1,
),
(
1,
2,
),
(
1,
3,
),
(
1,
4,
),
(
1,
5,
),
(
1,
6,
),
(
1,
7,
),
(
1,
8,
),
(
1,
9,
),
(
1,
10,
),
(
1,
11,
),
]
"
);
}
}