use std::time::{Duration, Instant};
use crate::state::pty::PtyShell;
pub(super) const INPUT_QUIET: Duration = Duration::from_millis(750);
const PROMPT_FRAGMENTS: [&str; 8] = [
"password",
"passphrase",
"[y/n]",
"(y/n)",
"yes/no",
"press enter",
"press return",
"press any key",
];
pub(super) struct InputWaitWatch {
last_activity: u64,
quiet_since: Instant,
inspected_activity: Option<u64>,
}
impl InputWaitWatch {
pub(super) fn new(shell: &PtyShell, now: Instant) -> Self {
Self { last_activity: shell.activity_seq(), quiet_since: now, inspected_activity: None }
}
pub(super) fn awaiting_input(&mut self, shell: &PtyShell, now: Instant) -> bool {
let activity = shell.activity_seq();
if activity != self.last_activity {
self.last_activity = activity;
self.quiet_since = now;
return false;
}
if now.duration_since(self.quiet_since) < INPUT_QUIET
|| self.inspected_activity == Some(activity)
{
return false;
}
self.inspected_activity = Some(activity);
shell.live_in_alt_screen() || line_awaits_input(&shell.live_cursor_line_prefix())
}
}
pub(super) fn line_awaits_input(prefix: &str) -> bool {
let text = prefix.trim_end();
if text.is_empty() {
return false;
}
let lower = text.to_ascii_lowercase();
if PROMPT_FRAGMENTS.iter().any(|fragment| lower.contains(fragment)) {
return true;
}
let mut tail = text.chars().rev();
match tail.next() {
Some(':' | '?' | '#' | '$') => true,
Some('>') => !matches!(tail.next(), Some('=' | '-')),
_ => false,
}
}
#[cfg(test)]
mod tests {
use super::line_awaits_input;
#[test]
fn line_prompts_are_recognized() {
for prompt in [
"Password: ",
"[sudo] password for user:",
"Enter passphrase for key '/home/u/.ssh/id_ed25519': ",
"Continue? [Y/n] ",
"Are you sure you want to continue connecting (yes/no/[fingerprint])? ",
"Proceed (y/n)",
"Press ENTER to continue",
">>> ",
"> ",
"postgres=# ",
"user@host:~$ ",
"Name: ",
] {
assert!(line_awaits_input(prompt), "{prompt:?} should read as a prompt");
}
}
#[test]
fn progress_and_ordinary_output_are_not_prompts() {
for line in [
"",
" ",
"Building [=======================> ] 450/451: winx-code-agent(bin)",
"Downloading 45%",
"[=====>",
"progress -->",
" Compiling serde v1.0.0",
] {
assert!(!line_awaits_input(line), "{line:?} should not read as a prompt");
}
}
}