rust_expect/send/
basic.rs

1//! Basic send operations.
2//!
3//! This module provides fundamental send operations for writing data
4//! to a session, including raw bytes, strings, lines, and control characters.
5
6use std::time::Duration;
7
8use tokio::io::AsyncWriteExt;
9
10use crate::config::LineEnding;
11use crate::error::Result;
12use crate::types::ControlChar;
13
14/// Trait for basic send operations.
15pub trait BasicSend: Send {
16    /// Send raw bytes.
17    fn send_bytes(&mut self, data: &[u8]) -> impl std::future::Future<Output = Result<()>> + Send;
18
19    /// Send a string.
20    fn send_str(&mut self, s: &str) -> impl std::future::Future<Output = Result<()>> + Send {
21        async move { self.send_bytes(s.as_bytes()).await }
22    }
23
24    /// Send a line with the specified line ending.
25    fn send_line_with(
26        &mut self,
27        line: &str,
28        ending: LineEnding,
29    ) -> impl std::future::Future<Output = Result<()>> + Send {
30        async move {
31            self.send_str(line).await?;
32            self.send_str(ending.as_str()).await
33        }
34    }
35
36    /// Send a line with LF ending.
37    fn send_line(&mut self, line: &str) -> impl std::future::Future<Output = Result<()>> + Send {
38        self.send_line_with(line, LineEnding::Lf)
39    }
40
41    /// Send a control character.
42    fn send_control(
43        &mut self,
44        ctrl: ControlChar,
45    ) -> impl std::future::Future<Output = Result<()>> + Send {
46        async move { self.send_bytes(&[ctrl.as_byte()]).await }
47    }
48
49    /// Send Ctrl+C (interrupt).
50    fn send_interrupt(&mut self) -> impl std::future::Future<Output = Result<()>> + Send {
51        self.send_control(ControlChar::CtrlC)
52    }
53
54    /// Send Ctrl+D (EOF).
55    fn send_eof(&mut self) -> impl std::future::Future<Output = Result<()>> + Send {
56        self.send_control(ControlChar::CtrlD)
57    }
58
59    /// Send Ctrl+Z (suspend).
60    fn send_suspend(&mut self) -> impl std::future::Future<Output = Result<()>> + Send {
61        self.send_control(ControlChar::CtrlZ)
62    }
63
64    /// Send Escape.
65    fn send_escape(&mut self) -> impl std::future::Future<Output = Result<()>> + Send {
66        self.send_control(ControlChar::Escape)
67    }
68
69    /// Send Tab (Ctrl+I).
70    fn send_tab(&mut self) -> impl std::future::Future<Output = Result<()>> + Send {
71        self.send_control(ControlChar::CtrlI)
72    }
73
74    /// Send Backspace (Ctrl+H).
75    fn send_backspace(&mut self) -> impl std::future::Future<Output = Result<()>> + Send {
76        self.send_control(ControlChar::CtrlH)
77    }
78}
79
80/// A sender that wraps an async writer.
81pub struct Sender<W> {
82    writer: W,
83    line_ending: LineEnding,
84    /// Optional delay between characters.
85    char_delay: Option<Duration>,
86}
87
88impl<W: AsyncWriteExt + Unpin + Send> Sender<W> {
89    /// Create a new sender.
90    pub const fn new(writer: W) -> Self {
91        Self {
92            writer,
93            line_ending: LineEnding::Lf,
94            char_delay: None,
95        }
96    }
97
98    /// Set the line ending.
99    pub const fn set_line_ending(&mut self, ending: LineEnding) {
100        self.line_ending = ending;
101    }
102
103    /// Set character delay for slow typing.
104    pub const fn set_char_delay(&mut self, delay: Option<Duration>) {
105        self.char_delay = delay;
106    }
107
108    /// Get the line ending.
109    #[must_use]
110    pub const fn line_ending(&self) -> LineEnding {
111        self.line_ending
112    }
113
114    /// Send bytes with optional character delay.
115    pub async fn send_with_delay(&mut self, data: &[u8]) -> Result<()> {
116        if let Some(delay) = self.char_delay {
117            for byte in data {
118                self.writer
119                    .write_all(&[*byte])
120                    .await
121                    .map_err(crate::error::ExpectError::Io)?;
122                self.writer
123                    .flush()
124                    .await
125                    .map_err(crate::error::ExpectError::Io)?;
126                tokio::time::sleep(delay).await;
127            }
128        } else {
129            self.writer
130                .write_all(data)
131                .await
132                .map_err(crate::error::ExpectError::Io)?;
133            self.writer
134                .flush()
135                .await
136                .map_err(crate::error::ExpectError::Io)?;
137        }
138        Ok(())
139    }
140
141    /// Get mutable access to the underlying writer.
142    pub const fn writer_mut(&mut self) -> &mut W {
143        &mut self.writer
144    }
145}
146
147impl<W: AsyncWriteExt + Unpin + Send> BasicSend for Sender<W> {
148    async fn send_bytes(&mut self, data: &[u8]) -> Result<()> {
149        self.send_with_delay(data).await
150    }
151
152    async fn send_line(&mut self, line: &str) -> Result<()> {
153        self.send_line_with(line, self.line_ending).await
154    }
155}
156
157/// ANSI escape sequence helpers.
158pub struct AnsiSequences;
159
160impl AnsiSequences {
161    /// Cursor up.
162    pub const CURSOR_UP: &'static [u8] = b"\x1b[A";
163    /// Cursor down.
164    pub const CURSOR_DOWN: &'static [u8] = b"\x1b[B";
165    /// Cursor right.
166    pub const CURSOR_RIGHT: &'static [u8] = b"\x1b[C";
167    /// Cursor left.
168    pub const CURSOR_LEFT: &'static [u8] = b"\x1b[D";
169    /// Home key.
170    pub const HOME: &'static [u8] = b"\x1b[H";
171    /// End key.
172    pub const END: &'static [u8] = b"\x1b[F";
173    /// Page up.
174    pub const PAGE_UP: &'static [u8] = b"\x1b[5~";
175    /// Page down.
176    pub const PAGE_DOWN: &'static [u8] = b"\x1b[6~";
177    /// Insert key.
178    pub const INSERT: &'static [u8] = b"\x1b[2~";
179    /// Delete key.
180    pub const DELETE: &'static [u8] = b"\x1b[3~";
181    /// F1 key.
182    pub const F1: &'static [u8] = b"\x1bOP";
183    /// F2 key.
184    pub const F2: &'static [u8] = b"\x1bOQ";
185    /// F3 key.
186    pub const F3: &'static [u8] = b"\x1bOR";
187    /// F4 key.
188    pub const F4: &'static [u8] = b"\x1bOS";
189    /// F5 key.
190    pub const F5: &'static [u8] = b"\x1b[15~";
191    /// F6 key.
192    pub const F6: &'static [u8] = b"\x1b[17~";
193    /// F7 key.
194    pub const F7: &'static [u8] = b"\x1b[18~";
195    /// F8 key.
196    pub const F8: &'static [u8] = b"\x1b[19~";
197    /// F9 key.
198    pub const F9: &'static [u8] = b"\x1b[20~";
199    /// F10 key.
200    pub const F10: &'static [u8] = b"\x1b[21~";
201    /// F11 key.
202    pub const F11: &'static [u8] = b"\x1b[23~";
203    /// F12 key.
204    pub const F12: &'static [u8] = b"\x1b[24~";
205
206    /// Generate cursor movement sequence.
207    #[must_use]
208    pub fn cursor_move(rows: i32, cols: i32) -> Vec<u8> {
209        let mut result = Vec::new();
210
211        if rows != 0 {
212            let dir = if rows > 0 { 'B' } else { 'A' };
213            let count = rows.unsigned_abs();
214            result.extend(format!("\x1b[{count}{dir}").as_bytes());
215        }
216
217        if cols != 0 {
218            let dir = if cols > 0 { 'C' } else { 'D' };
219            let count = cols.unsigned_abs();
220            result.extend(format!("\x1b[{count}{dir}").as_bytes());
221        }
222
223        result
224    }
225
226    /// Generate cursor position sequence.
227    #[must_use]
228    pub fn cursor_position(row: u32, col: u32) -> Vec<u8> {
229        format!("\x1b[{row};{col}H").into_bytes()
230    }
231}
232
233/// Extension trait for sending ANSI sequences.
234pub trait AnsiSend: BasicSend {
235    /// Send cursor up.
236    fn send_cursor_up(&mut self) -> impl std::future::Future<Output = Result<()>> + Send
237    where
238        Self: Send,
239    {
240        async move { self.send_bytes(AnsiSequences::CURSOR_UP).await }
241    }
242
243    /// Send cursor down.
244    fn send_cursor_down(&mut self) -> impl std::future::Future<Output = Result<()>> + Send
245    where
246        Self: Send,
247    {
248        async move { self.send_bytes(AnsiSequences::CURSOR_DOWN).await }
249    }
250
251    /// Send cursor right.
252    fn send_cursor_right(&mut self) -> impl std::future::Future<Output = Result<()>> + Send
253    where
254        Self: Send,
255    {
256        async move { self.send_bytes(AnsiSequences::CURSOR_RIGHT).await }
257    }
258
259    /// Send cursor left.
260    fn send_cursor_left(&mut self) -> impl std::future::Future<Output = Result<()>> + Send
261    where
262        Self: Send,
263    {
264        async move { self.send_bytes(AnsiSequences::CURSOR_LEFT).await }
265    }
266
267    /// Send home key.
268    fn send_home(&mut self) -> impl std::future::Future<Output = Result<()>> + Send
269    where
270        Self: Send,
271    {
272        async move { self.send_bytes(AnsiSequences::HOME).await }
273    }
274
275    /// Send end key.
276    fn send_end(&mut self) -> impl std::future::Future<Output = Result<()>> + Send
277    where
278        Self: Send,
279    {
280        async move { self.send_bytes(AnsiSequences::END).await }
281    }
282
283    /// Send delete key.
284    fn send_delete(&mut self) -> impl std::future::Future<Output = Result<()>> + Send
285    where
286        Self: Send,
287    {
288        async move { self.send_bytes(AnsiSequences::DELETE).await }
289    }
290
291    /// Send a function key.
292    fn send_function_key(&mut self, n: u8) -> impl std::future::Future<Output = Result<()>> + Send
293    where
294        Self: Send,
295    {
296        async move {
297            let seq = match n {
298                1 => AnsiSequences::F1,
299                2 => AnsiSequences::F2,
300                3 => AnsiSequences::F3,
301                4 => AnsiSequences::F4,
302                5 => AnsiSequences::F5,
303                6 => AnsiSequences::F6,
304                7 => AnsiSequences::F7,
305                8 => AnsiSequences::F8,
306                9 => AnsiSequences::F9,
307                10 => AnsiSequences::F10,
308                11 => AnsiSequences::F11,
309                12 => AnsiSequences::F12,
310                _ => return Ok(()),
311            };
312            self.send_bytes(seq).await
313        }
314    }
315}
316
317impl<T: BasicSend> AnsiSend for T {}
318
319#[cfg(test)]
320mod tests {
321    use super::*;
322
323    #[test]
324    fn ansi_cursor_move() {
325        assert_eq!(AnsiSequences::cursor_move(3, 0), b"\x1b[3B");
326        assert_eq!(AnsiSequences::cursor_move(-2, 0), b"\x1b[2A");
327        assert_eq!(AnsiSequences::cursor_move(0, 5), b"\x1b[5C");
328        assert_eq!(AnsiSequences::cursor_move(0, -4), b"\x1b[4D");
329    }
330
331    #[test]
332    fn ansi_cursor_position() {
333        assert_eq!(AnsiSequences::cursor_position(1, 1), b"\x1b[1;1H");
334        assert_eq!(AnsiSequences::cursor_position(10, 20), b"\x1b[10;20H");
335    }
336}