1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
//! Output-condition waits.
#[cfg(feature = "async")]
use std::sync::Arc;
use std::time::Duration;
use super::Session;
use crate::{WaitCondition, WaitError, WaitOutcome};
impl Session {
/// Wait for a condition to become true and return the matching snapshot
/// alongside any match metadata.
///
/// Accepts any `impl Into<WaitCondition>` so the typed wrappers
/// returned by [`WaitCondition::regex`] and [`WaitCondition::stable`]
/// can be passed without an explicit `.into()` at the call site.
///
/// # Errors
///
/// Returns [`WaitError::Timeout`] when the deadline elapses before the
/// condition matches, [`WaitError::ProcessExitedBeforeMatch`] when the
/// child exits without satisfying the condition,
/// [`WaitError::InvalidRegex`] for malformed regex conditions, and
/// other [`WaitError`] variants for transport-level failures.
pub fn wait(
&self,
condition: impl Into<WaitCondition>,
timeout: Duration,
) -> std::result::Result<WaitOutcome, WaitError> {
crate::wait::wait(self, condition.into(), timeout)
}
/// Asynchronous variant of [`Session::wait`].
///
/// Returns a runtime-agnostic [`Future`](std::future::Future) built on
/// `std` primitives only (no tokio dependency). The future registers
/// a [`Waker`](std::task::Waker) on the session's shared output
/// notifier; the PTY reader thread fires the notifier after each
/// parser tick, prompting the future to re-probe its condition. The
/// future also registers a deadline on the notifier's timer thread so
/// the timeout (and any active [`WaitCondition::stable`] settle
/// window) is enforced without polling. There is no per-future worker
/// thread.
///
/// # Cancellation
///
/// Dropping the returned future removes its waker entries from the
/// notifier. No background thread is spawned on its behalf, so
/// cancellation is immediate and leak-free. This behaves correctly
/// regardless of executor: tokio `JoinHandle::abort`, smol task
/// cancellation, and manual drop all trigger the same path.
///
/// The future holds [`Arc`]s of the underlying terminal and the
/// session's output notifier, so it can finish unwinding even if the
/// originating [`Session`] is dropped while the wait is in flight.
///
/// # Send bound
///
/// The returned future is `Send + 'static` so it can be spawned on
/// any standard executor. This requires `tastty::Terminal: Send`,
/// which holds on every platform tastty currently supports.
#[cfg(feature = "async")]
pub fn wait_async(
&self,
condition: impl Into<WaitCondition>,
timeout: Duration,
) -> impl std::future::Future<Output = std::result::Result<WaitOutcome, WaitError>> + Send + 'static
{
crate::wait::async_future::WaitFuture::new(
Arc::clone(&self.terminal),
Arc::clone(&self.output_notifier),
condition.into(),
timeout,
)
}
}