use std::sync::mpsc::channel;
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;
use eyre::eyre;
use itertools::Itertools;
use lib::testing::Git;
use portable_pty::{native_pty_system, CommandBuilder, PtySize};
pub fn trim_lines(output: String) -> String {
output
.lines()
.flat_map(|line| vec![line.trim_end(), "\n"].into_iter())
.collect()
}
pub enum PtyAction<'a> {
Write(&'a str),
WaitUntilContains(&'a str),
}
pub fn run_in_pty(git: &Git, args: &[&str], inputs: &[PtyAction]) -> eyre::Result<()> {
let pty_system = native_pty_system();
let pty_size = PtySize::default();
let mut pty = pty_system
.openpty(pty_size)
.map_err(|e| eyre!("Could not open pty: {}", e))?;
let mut cmd = CommandBuilder::new(&git.path_to_git);
cmd.env_clear();
for (k, v) in git.get_base_env(0) {
cmd.env(k, v);
}
cmd.env("TERM", "xterm");
cmd.args(args);
cmd.cwd(&git.repo_path);
let mut child = pty
.slave
.spawn_command(cmd)
.map_err(|e| eyre!("Could not spawn child: {}", e))?;
let reader = pty
.master
.try_clone_reader()
.map_err(|e| eyre!("Could not clone reader: {}", e))?;
let reader = Arc::new(Mutex::new(reader));
let parser = vt100::Parser::new(pty_size.rows, pty_size.cols, 0);
let parser = Arc::new(Mutex::new(parser));
for action in inputs {
match action {
PtyAction::WaitUntilContains(value) => {
let (finished_tx, finished_rx) = channel();
let wait_thread = {
let parser = Arc::clone(&parser);
let reader = Arc::clone(&reader);
let value = value.to_string();
thread::spawn(move || -> anyhow::Result<()> {
loop {
{
let parser = parser.lock().unwrap();
if parser.screen().contents().contains(&value) {
break;
}
}
let mut reader = reader.lock().unwrap();
const BUF_SIZE: usize = 4096;
let mut buffer = [0; BUF_SIZE];
let n = reader.read(&mut buffer)?;
assert!(n < BUF_SIZE, "filled up PTY buffer by reading {} bytes", n);
{
let mut parser = parser.lock().unwrap();
parser.process(&buffer[..n]);
}
}
finished_tx.send(()).unwrap();
Ok(())
})
};
if finished_rx.recv_timeout(Duration::from_secs(5)).is_err() {
panic!(
"\
Timed out waiting for virtual terminal to show string: {:?}
Screen contents:
-----
{}
-----
",
value,
parser.lock().unwrap().screen().contents(),
);
}
wait_thread.join().unwrap().unwrap();
}
PtyAction::Write(value) => {
write!(pty.master, "{}", value)?;
pty.master.flush()?;
}
}
}
let read_remainder_of_pty_output_thread = thread::spawn({
let reader = Arc::clone(&reader);
move || {
let mut reader = reader.lock().unwrap();
let mut buffer = Vec::new();
reader.read_to_end(&mut buffer).expect("finish reading pty");
String::from_utf8(buffer).unwrap()
}
});
child.wait()?;
let _ = read_remainder_of_pty_output_thread;
Ok(())
}
pub fn extract_hint_command(stdout: &str) -> Vec<String> {
let hint_command = stdout
.split_once("disable this hint by running: ")
.map(|(_first, second)| second)
.unwrap()
.split('\n')
.next()
.unwrap();
hint_command
.split(' ')
.skip(1) .filter(|s| s != &"--global")
.map(|s| s.to_owned())
.collect_vec()
}