Skip to main content

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 the platform's default line ending.
37    ///
38    /// Implementors that carry a configured [`LineEnding`] should override this and
39    /// use it; this default only knows the platform. It must not hardcode LF, which
40    /// `ConPTY` discards outright.
41    fn send_line(&mut self, line: &str) -> impl std::future::Future<Output = Result<()>> + Send {
42        self.send_line_with(line, LineEnding::default())
43    }
44
45    /// Send a control character.
46    fn send_control(
47        &mut self,
48        ctrl: ControlChar,
49    ) -> impl std::future::Future<Output = Result<()>> + Send {
50        async move { self.send_bytes(&[ctrl.as_byte()]).await }
51    }
52
53    /// Send Ctrl+C (interrupt).
54    fn send_interrupt(&mut self) -> impl std::future::Future<Output = Result<()>> + Send {
55        self.send_control(ControlChar::CtrlC)
56    }
57
58    /// Send Ctrl+D (EOF).
59    fn send_eof(&mut self) -> impl std::future::Future<Output = Result<()>> + Send {
60        self.send_control(ControlChar::CtrlD)
61    }
62
63    /// Send Ctrl+Z (suspend).
64    fn send_suspend(&mut self) -> impl std::future::Future<Output = Result<()>> + Send {
65        self.send_control(ControlChar::CtrlZ)
66    }
67
68    /// Send Escape.
69    fn send_escape(&mut self) -> impl std::future::Future<Output = Result<()>> + Send {
70        self.send_control(ControlChar::Escape)
71    }
72
73    /// Send Tab (Ctrl+I).
74    fn send_tab(&mut self) -> impl std::future::Future<Output = Result<()>> + Send {
75        self.send_control(ControlChar::CtrlI)
76    }
77
78    /// Send Backspace (Ctrl+H).
79    fn send_backspace(&mut self) -> impl std::future::Future<Output = Result<()>> + Send {
80        self.send_control(ControlChar::CtrlH)
81    }
82
83    /// Send text using bracketed paste mode.
84    ///
85    /// Wraps the content in `\x1b[200~` and `\x1b[201~` markers. Terminal
86    /// applications that have enabled bracketed paste (DECSET 2004) treat
87    /// the enclosed content as pasted input rather than typed input — this
88    /// suppresses autocomplete, command-history scanning, and per-character
89    /// interpretation that can otherwise mangle multi-line or special-key
90    /// content (e.g. a leading `/` triggering a slash-command popup).
91    ///
92    /// The application must have requested bracketed paste; if it hasn't,
93    /// most terminals will ignore the markers and the inner text will be
94    /// delivered as-is, so the call is safe to use unconditionally.
95    fn send_paste(&mut self, text: &str) -> impl std::future::Future<Output = Result<()>> + Send {
96        async move {
97            self.send_bytes(b"\x1b[200~").await?;
98            self.send_str(text).await?;
99            self.send_bytes(b"\x1b[201~").await
100        }
101    }
102}
103
104/// A sender that wraps an async writer.
105pub struct Sender<W> {
106    writer: W,
107    line_ending: LineEnding,
108    /// Optional delay between characters.
109    char_delay: Option<Duration>,
110}
111
112impl<W: AsyncWriteExt + Unpin + Send> Sender<W> {
113    /// Create a new sender.
114    pub const fn new(writer: W) -> Self {
115        Self {
116            writer,
117            line_ending: LineEnding::Lf,
118            char_delay: None,
119        }
120    }
121
122    /// Set the line ending.
123    pub const fn set_line_ending(&mut self, ending: LineEnding) {
124        self.line_ending = ending;
125    }
126
127    /// Set character delay for slow typing.
128    pub const fn set_char_delay(&mut self, delay: Option<Duration>) {
129        self.char_delay = delay;
130    }
131
132    /// Get the line ending.
133    #[must_use]
134    pub const fn line_ending(&self) -> LineEnding {
135        self.line_ending
136    }
137
138    /// Send bytes with optional character delay.
139    pub async fn send_with_delay(&mut self, data: &[u8]) -> Result<()> {
140        if let Some(delay) = self.char_delay {
141            for byte in data {
142                self.writer
143                    .write_all(&[*byte])
144                    .await
145                    .map_err(crate::error::ExpectError::Io)?;
146                self.writer
147                    .flush()
148                    .await
149                    .map_err(crate::error::ExpectError::Io)?;
150                tokio::time::sleep(delay).await;
151            }
152        } else {
153            self.writer
154                .write_all(data)
155                .await
156                .map_err(crate::error::ExpectError::Io)?;
157            self.writer
158                .flush()
159                .await
160                .map_err(crate::error::ExpectError::Io)?;
161        }
162        Ok(())
163    }
164
165    /// Get mutable access to the underlying writer.
166    pub const fn writer_mut(&mut self) -> &mut W {
167        &mut self.writer
168    }
169}
170
171impl<W: AsyncWriteExt + Unpin + Send> BasicSend for Sender<W> {
172    async fn send_bytes(&mut self, data: &[u8]) -> Result<()> {
173        self.send_with_delay(data).await
174    }
175
176    async fn send_line(&mut self, line: &str) -> Result<()> {
177        self.send_line_with(line, self.line_ending).await
178    }
179}
180
181/// ANSI escape sequence helpers.
182pub struct AnsiSequences;
183
184impl AnsiSequences {
185    /// Cursor up.
186    pub const CURSOR_UP: &'static [u8] = b"\x1b[A";
187    /// Cursor down.
188    pub const CURSOR_DOWN: &'static [u8] = b"\x1b[B";
189    /// Cursor right.
190    pub const CURSOR_RIGHT: &'static [u8] = b"\x1b[C";
191    /// Cursor left.
192    pub const CURSOR_LEFT: &'static [u8] = b"\x1b[D";
193    /// Home key.
194    pub const HOME: &'static [u8] = b"\x1b[H";
195    /// End key.
196    pub const END: &'static [u8] = b"\x1b[F";
197    /// Page up.
198    pub const PAGE_UP: &'static [u8] = b"\x1b[5~";
199    /// Page down.
200    pub const PAGE_DOWN: &'static [u8] = b"\x1b[6~";
201    /// Insert key.
202    pub const INSERT: &'static [u8] = b"\x1b[2~";
203    /// Delete key.
204    pub const DELETE: &'static [u8] = b"\x1b[3~";
205    /// F1 key.
206    pub const F1: &'static [u8] = b"\x1bOP";
207    /// F2 key.
208    pub const F2: &'static [u8] = b"\x1bOQ";
209    /// F3 key.
210    pub const F3: &'static [u8] = b"\x1bOR";
211    /// F4 key.
212    pub const F4: &'static [u8] = b"\x1bOS";
213    /// F5 key.
214    pub const F5: &'static [u8] = b"\x1b[15~";
215    /// F6 key.
216    pub const F6: &'static [u8] = b"\x1b[17~";
217    /// F7 key.
218    pub const F7: &'static [u8] = b"\x1b[18~";
219    /// F8 key.
220    pub const F8: &'static [u8] = b"\x1b[19~";
221    /// F9 key.
222    pub const F9: &'static [u8] = b"\x1b[20~";
223    /// F10 key.
224    pub const F10: &'static [u8] = b"\x1b[21~";
225    /// F11 key.
226    pub const F11: &'static [u8] = b"\x1b[23~";
227    /// F12 key.
228    pub const F12: &'static [u8] = b"\x1b[24~";
229
230    /// Generate cursor movement sequence.
231    #[must_use]
232    pub fn cursor_move(rows: i32, cols: i32) -> Vec<u8> {
233        let mut result = Vec::new();
234
235        if rows != 0 {
236            let dir = if rows > 0 { 'B' } else { 'A' };
237            let count = rows.unsigned_abs();
238            result.extend(format!("\x1b[{count}{dir}").as_bytes());
239        }
240
241        if cols != 0 {
242            let dir = if cols > 0 { 'C' } else { 'D' };
243            let count = cols.unsigned_abs();
244            result.extend(format!("\x1b[{count}{dir}").as_bytes());
245        }
246
247        result
248    }
249
250    /// Generate cursor position sequence.
251    #[must_use]
252    pub fn cursor_position(row: u32, col: u32) -> Vec<u8> {
253        format!("\x1b[{row};{col}H").into_bytes()
254    }
255}
256
257/// Extension trait for sending ANSI sequences.
258pub trait AnsiSend: BasicSend {
259    /// Send cursor up.
260    fn send_cursor_up(&mut self) -> impl std::future::Future<Output = Result<()>> + Send
261    where
262        Self: Send,
263    {
264        async move { self.send_bytes(AnsiSequences::CURSOR_UP).await }
265    }
266
267    /// Send cursor down.
268    fn send_cursor_down(&mut self) -> impl std::future::Future<Output = Result<()>> + Send
269    where
270        Self: Send,
271    {
272        async move { self.send_bytes(AnsiSequences::CURSOR_DOWN).await }
273    }
274
275    /// Send cursor right.
276    fn send_cursor_right(&mut self) -> impl std::future::Future<Output = Result<()>> + Send
277    where
278        Self: Send,
279    {
280        async move { self.send_bytes(AnsiSequences::CURSOR_RIGHT).await }
281    }
282
283    /// Send cursor left.
284    fn send_cursor_left(&mut self) -> impl std::future::Future<Output = Result<()>> + Send
285    where
286        Self: Send,
287    {
288        async move { self.send_bytes(AnsiSequences::CURSOR_LEFT).await }
289    }
290
291    /// Send home key.
292    fn send_home(&mut self) -> impl std::future::Future<Output = Result<()>> + Send
293    where
294        Self: Send,
295    {
296        async move { self.send_bytes(AnsiSequences::HOME).await }
297    }
298
299    /// Send end key.
300    fn send_end(&mut self) -> impl std::future::Future<Output = Result<()>> + Send
301    where
302        Self: Send,
303    {
304        async move { self.send_bytes(AnsiSequences::END).await }
305    }
306
307    /// Send delete key.
308    fn send_delete(&mut self) -> impl std::future::Future<Output = Result<()>> + Send
309    where
310        Self: Send,
311    {
312        async move { self.send_bytes(AnsiSequences::DELETE).await }
313    }
314
315    /// Send a function key.
316    fn send_function_key(&mut self, n: u8) -> impl std::future::Future<Output = Result<()>> + Send
317    where
318        Self: Send,
319    {
320        async move {
321            let seq = match n {
322                1 => AnsiSequences::F1,
323                2 => AnsiSequences::F2,
324                3 => AnsiSequences::F3,
325                4 => AnsiSequences::F4,
326                5 => AnsiSequences::F5,
327                6 => AnsiSequences::F6,
328                7 => AnsiSequences::F7,
329                8 => AnsiSequences::F8,
330                9 => AnsiSequences::F9,
331                10 => AnsiSequences::F10,
332                11 => AnsiSequences::F11,
333                12 => AnsiSequences::F12,
334                _ => return Ok(()),
335            };
336            self.send_bytes(seq).await
337        }
338    }
339}
340
341impl<T: BasicSend> AnsiSend for T {}
342
343#[cfg(test)]
344mod tests {
345    use super::*;
346
347    #[test]
348    fn ansi_cursor_move() {
349        assert_eq!(AnsiSequences::cursor_move(3, 0), b"\x1b[3B");
350        assert_eq!(AnsiSequences::cursor_move(-2, 0), b"\x1b[2A");
351        assert_eq!(AnsiSequences::cursor_move(0, 5), b"\x1b[5C");
352        assert_eq!(AnsiSequences::cursor_move(0, -4), b"\x1b[4D");
353    }
354
355    #[test]
356    fn ansi_cursor_position() {
357        assert_eq!(AnsiSequences::cursor_position(1, 1), b"\x1b[1;1H");
358        assert_eq!(AnsiSequences::cursor_position(10, 20), b"\x1b[10;20H");
359    }
360}