1use std::thread::sleep;
8
9use serde::Serialize;
10
11use crate::commands::{SEND_SETTLE, SUBMIT_DELAY};
12use crate::error::{Error, Result};
13use crate::tmux;
14
15const ECHO_TAIL_CHARS: usize = 40;
17
18#[derive(Debug, Clone)]
19pub struct SendArgs {
20 pub session: String,
21 pub prompt: String,
22 pub submit: bool,
23 pub retries: u32,
24}
25
26#[derive(Debug, Serialize)]
27pub struct SendResult {
28 pub ok: bool,
29 pub attempts: u32,
30}
31
32pub fn run(args: SendArgs) -> Result<SendResult> {
33 crate::session::validate_name(&args.session)?;
34 let needle = echo_needle(&args.prompt);
35
36 for attempt in 1..=args.retries {
37 tmux::send_key(&args.session, "C-u")?;
39 tmux::send_literal(&args.session, &args.prompt)?;
40 sleep(SEND_SETTLE);
41
42 let pane = tmux::capture_pane(&args.session)?;
43 if echo_present(&pane, &needle) {
44 if args.submit {
45 sleep(SUBMIT_DELAY);
46 tmux::send_key(&args.session, "Enter")?;
47 }
48 return Ok(SendResult {
49 ok: true,
50 attempts: attempt,
51 });
52 }
53 }
54
55 Err(Error::InputNotAccepted(args.retries))
56}
57
58fn echo_needle(prompt: &str) -> String {
60 let normalized = normalize(prompt);
61 let tail: Vec<char> = normalized.chars().rev().take(ECHO_TAIL_CHARS).collect();
62 tail.into_iter().rev().collect()
63}
64
65fn echo_present(pane: &str, needle: &str) -> bool {
66 !needle.is_empty() && normalize(pane).contains(needle)
67}
68
69fn normalize(text: &str) -> String {
72 text.chars()
73 .filter(|c| c.is_alphanumeric())
74 .flat_map(char::to_lowercase)
75 .collect()
76}
77
78#[cfg(test)]
79mod tests {
80 use super::*;
81
82 #[test]
83 fn echo_matches_despite_wrapping() {
84 let prompt = "Ask me one clarifying question before doing anything.";
85 let needle = echo_needle(prompt);
86 let pane = "│ Ask me one clarifying question\n│ before doing anything. │";
88 assert!(echo_present(pane, &needle));
89 }
90
91 #[test]
92 fn echo_absent_when_not_typed() {
93 let needle = echo_needle("hello world this is a fairly long prompt to type");
94 assert!(!echo_present("│ > │ (empty input box)", &needle));
95 }
96}