Skip to content

Commit a37bace

Browse files
committed
tiny viewport
1 parent 0f558b9 commit a37bace

1 file changed

Lines changed: 142 additions & 0 deletions

File tree

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
use std::{
2+
env,
3+
io::Write,
4+
thread,
5+
time::{Duration, Instant},
6+
};
7+
8+
use portable_pty::CommandBuilder;
9+
use termharness::session::Session;
10+
11+
const PROMPT_BIN: &str = env!("CARGO_BIN_EXE_zsh-pretend");
12+
const TERMINAL_ROWS: u16 = 4;
13+
const TERMINAL_COLS: u16 = 12;
14+
const INPUT_TEXT: &str =
15+
"this input should overflow a tiny terminal viewport and keep wrapping";
16+
const PROMPT_WAIT_TIMEOUT: Duration = Duration::from_secs(2);
17+
const PROMPT_POLL_INTERVAL: Duration = Duration::from_millis(20);
18+
const SETTLE_AFTER_TYPE: Duration = Duration::from_millis(100);
19+
const EXPECTED_SCREEN: &[&str] = &[
20+
" r00 |·terminal·vi|",
21+
" r01 |ewport·and·k|",
22+
" r02 |eep·wrapping|",
23+
" r03 |············|",
24+
];
25+
26+
#[test]
27+
fn readline_tiny_viewport_matches_v0_11_1_snapshot() -> anyhow::Result<()> {
28+
let screen = run_readline(PROMPT_BIN)?;
29+
let expected = EXPECTED_SCREEN
30+
.iter()
31+
.map(|line| (*line).to_string())
32+
.collect::<Vec<_>>();
33+
assert_eq!(
34+
screen,
35+
expected,
36+
"tiny viewport の Readline 描画が v0.11.1 snapshot から変化しました\n\n{}",
37+
render_screen(&screen),
38+
);
39+
Ok(())
40+
}
41+
42+
#[test]
43+
#[ignore = "requires explicit binary paths for the titled readline example"]
44+
fn titled_readline_tiny_viewport_matches_v0_11_1() -> anyhow::Result<()> {
45+
let current = env::var("PROMKIT_CURRENT_TITLE_READLINE_BIN")
46+
.map_err(|_| anyhow::anyhow!("PROMKIT_CURRENT_TITLE_READLINE_BIN is not set"))?;
47+
let old = env::var("PROMKIT_V0_11_1_TITLE_READLINE_BIN")
48+
.map_err(|_| anyhow::anyhow!("PROMKIT_V0_11_1_TITLE_READLINE_BIN is not set"))?;
49+
50+
let expected = run_readline(&old)?;
51+
let actual = run_readline(&current)?;
52+
assert_eq!(
53+
actual,
54+
expected,
55+
"title 付き Readline の tiny viewport 描画が v0.11.1 から変化しました\n\n== v0.11.1 ==\n{}\n\n== current ==\n{}",
56+
render_screen(&expected),
57+
render_screen(&actual),
58+
);
59+
Ok(())
60+
}
61+
62+
fn run_readline(bin: &str) -> anyhow::Result<Vec<String>> {
63+
run_readline_with_moves(bin, TERMINAL_ROWS, TERMINAL_COLS, 0)
64+
}
65+
66+
fn run_readline_with_moves(
67+
bin: &str,
68+
rows: u16,
69+
cols: u16,
70+
left_moves: usize,
71+
) -> anyhow::Result<Vec<String>> {
72+
let cmd = CommandBuilder::new(bin);
73+
let mut session = Session::spawn(
74+
cmd,
75+
(rows, cols),
76+
Some((rows, 1)),
77+
)?;
78+
79+
wait_for_prompt(&session)?;
80+
send_bytes(&mut session, INPUT_TEXT.as_bytes())?;
81+
move_cursor_left(&mut session, left_moves)?;
82+
thread::sleep(SETTLE_AFTER_TYPE);
83+
let screen = screen_to_debug_lines(&session.screen_snapshot());
84+
shutdown(session)?;
85+
Ok(screen)
86+
}
87+
88+
fn wait_for_prompt(session: &Session) -> anyhow::Result<()> {
89+
let deadline = Instant::now() + PROMPT_WAIT_TIMEOUT;
90+
while Instant::now() < deadline {
91+
let screen = session.screen_snapshot();
92+
if screen.iter().any(|line| line.starts_with("❯❯ ")) {
93+
return Ok(());
94+
}
95+
thread::sleep(PROMPT_POLL_INTERVAL);
96+
}
97+
98+
Err(anyhow::anyhow!("timed out waiting for prompt"))
99+
}
100+
101+
fn send_bytes(session: &mut Session, bytes: &[u8]) -> anyhow::Result<()> {
102+
let mut writer = session
103+
.writer
104+
.lock()
105+
.expect("failed to lock session writer");
106+
writer.write_all(bytes)?;
107+
writer.flush()?;
108+
Ok(())
109+
}
110+
111+
fn move_cursor_left(session: &mut Session, times: usize) -> anyhow::Result<()> {
112+
let mut writer = session
113+
.writer
114+
.lock()
115+
.expect("failed to lock session writer");
116+
for _ in 0..times {
117+
writer.write_all(b"\x1b[D")?;
118+
}
119+
writer.flush()?;
120+
Ok(())
121+
}
122+
123+
fn screen_to_debug_lines(screen: &[String]) -> Vec<String> {
124+
screen
125+
.iter()
126+
.enumerate()
127+
.map(|(row, line)| format!(" r{row:02} |{}|", line.replace(' ', "·")))
128+
.collect()
129+
}
130+
131+
fn render_screen(screen: &[String]) -> String {
132+
screen.join("\n")
133+
}
134+
135+
fn shutdown(mut session: Session) -> anyhow::Result<()> {
136+
session.child.kill()?;
137+
session.child.wait()?;
138+
if let Some(reader_thread) = session.reader_thread.take() {
139+
reader_thread.join().expect("reader thread panicked");
140+
}
141+
Ok(())
142+
}

0 commit comments

Comments
 (0)