use std::time::{Duration, Instant};
use tastty::Terminal;
use crate::snapshot::snapshot_from_screen;
use crate::wait::condition::WaitCondition;
use crate::wait::error::WaitError;
use crate::wait::outcome::WaitOutcome;
use crate::wait::output::OutputNotifier;
use crate::wait::probe::{CompiledCondition, Probe, probe};
use crate::{ExitStatus, Session, Snapshot};
pub(crate) fn wait(
session: &Session,
condition: WaitCondition,
timeout: Duration,
) -> Result<WaitOutcome, WaitError> {
wait_blocking(
&session.terminal_handle(),
&session.output_notifier(),
condition,
timeout,
)
}
pub(super) fn wait_blocking(
terminal: &Terminal,
notifier: &OutputNotifier,
condition: WaitCondition,
timeout: Duration,
) -> Result<WaitOutcome, WaitError> {
let poll = condition.poll;
let mut compiled = CompiledCondition::compile(&condition)?;
let start = Instant::now();
let deadline = start + timeout;
let exit_short_circuits = compiled.is_exit_or_stable();
loop {
let probe_result = terminal.with_screen(|screen| probe(terminal, screen, &mut compiled));
match probe_result? {
Probe::NotYet => {}
Probe::Matched(wait_match) => {
let snapshot = snapshot(terminal);
let exit_status = try_wait(terminal).ok().flatten();
return Ok(WaitOutcome {
snapshot,
elapsed: start.elapsed(),
wait_match,
exit_status,
});
}
}
if Instant::now() >= deadline {
return Err(WaitError::Timeout {
condition,
elapsed: start.elapsed(),
snapshot: Box::new(snapshot(terminal)),
});
}
if !exit_short_circuits && let Ok(Some(exit_status)) = try_wait(terminal) {
return Err(WaitError::ProcessExitedBeforeMatch {
condition,
exit_status,
snapshot: Box::new(snapshot(terminal)),
});
}
let remaining = deadline.saturating_duration_since(Instant::now());
notifier.wait_tick_blocking(poll.min(remaining));
}
}
pub(super) fn snapshot(terminal: &Terminal) -> Snapshot {
terminal.with_screen(snapshot_from_screen)
}
pub(super) fn try_wait(terminal: &Terminal) -> tastty::Result<Option<ExitStatus>> {
terminal
.try_wait()
.map(|maybe| maybe.map(ExitStatus::from_tastty))
}