use std::time::Duration;
use tokio::io::AsyncWriteExt;
use crate::config::LineEnding;
use crate::error::Result;
use crate::types::ControlChar;
pub trait BasicSend: Send {
fn send_bytes(&mut self, data: &[u8]) -> impl std::future::Future<Output = Result<()>> + Send;
fn send_str(&mut self, s: &str) -> impl std::future::Future<Output = Result<()>> + Send {
async move { self.send_bytes(s.as_bytes()).await }
}
fn send_line_with(
&mut self,
line: &str,
ending: LineEnding,
) -> impl std::future::Future<Output = Result<()>> + Send {
async move {
self.send_str(line).await?;
self.send_str(ending.as_str()).await
}
}
fn send_line(&mut self, line: &str) -> impl std::future::Future<Output = Result<()>> + Send {
self.send_line_with(line, LineEnding::Lf)
}
fn send_control(
&mut self,
ctrl: ControlChar,
) -> impl std::future::Future<Output = Result<()>> + Send {
async move { self.send_bytes(&[ctrl.as_byte()]).await }
}
fn send_interrupt(&mut self) -> impl std::future::Future<Output = Result<()>> + Send {
self.send_control(ControlChar::CtrlC)
}
fn send_eof(&mut self) -> impl std::future::Future<Output = Result<()>> + Send {
self.send_control(ControlChar::CtrlD)
}
fn send_suspend(&mut self) -> impl std::future::Future<Output = Result<()>> + Send {
self.send_control(ControlChar::CtrlZ)
}
fn send_escape(&mut self) -> impl std::future::Future<Output = Result<()>> + Send {
self.send_control(ControlChar::Escape)
}
fn send_tab(&mut self) -> impl std::future::Future<Output = Result<()>> + Send {
self.send_control(ControlChar::CtrlI)
}
fn send_backspace(&mut self) -> impl std::future::Future<Output = Result<()>> + Send {
self.send_control(ControlChar::CtrlH)
}
}
pub struct Sender<W> {
writer: W,
line_ending: LineEnding,
char_delay: Option<Duration>,
}
impl<W: AsyncWriteExt + Unpin + Send> Sender<W> {
pub const fn new(writer: W) -> Self {
Self {
writer,
line_ending: LineEnding::Lf,
char_delay: None,
}
}
pub const fn set_line_ending(&mut self, ending: LineEnding) {
self.line_ending = ending;
}
pub const fn set_char_delay(&mut self, delay: Option<Duration>) {
self.char_delay = delay;
}
#[must_use]
pub const fn line_ending(&self) -> LineEnding {
self.line_ending
}
pub async fn send_with_delay(&mut self, data: &[u8]) -> Result<()> {
if let Some(delay) = self.char_delay {
for byte in data {
self.writer
.write_all(&[*byte])
.await
.map_err(crate::error::ExpectError::Io)?;
self.writer
.flush()
.await
.map_err(crate::error::ExpectError::Io)?;
tokio::time::sleep(delay).await;
}
} else {
self.writer
.write_all(data)
.await
.map_err(crate::error::ExpectError::Io)?;
self.writer
.flush()
.await
.map_err(crate::error::ExpectError::Io)?;
}
Ok(())
}
pub const fn writer_mut(&mut self) -> &mut W {
&mut self.writer
}
}
impl<W: AsyncWriteExt + Unpin + Send> BasicSend for Sender<W> {
async fn send_bytes(&mut self, data: &[u8]) -> Result<()> {
self.send_with_delay(data).await
}
async fn send_line(&mut self, line: &str) -> Result<()> {
self.send_line_with(line, self.line_ending).await
}
}
pub struct AnsiSequences;
impl AnsiSequences {
pub const CURSOR_UP: &'static [u8] = b"\x1b[A";
pub const CURSOR_DOWN: &'static [u8] = b"\x1b[B";
pub const CURSOR_RIGHT: &'static [u8] = b"\x1b[C";
pub const CURSOR_LEFT: &'static [u8] = b"\x1b[D";
pub const HOME: &'static [u8] = b"\x1b[H";
pub const END: &'static [u8] = b"\x1b[F";
pub const PAGE_UP: &'static [u8] = b"\x1b[5~";
pub const PAGE_DOWN: &'static [u8] = b"\x1b[6~";
pub const INSERT: &'static [u8] = b"\x1b[2~";
pub const DELETE: &'static [u8] = b"\x1b[3~";
pub const F1: &'static [u8] = b"\x1bOP";
pub const F2: &'static [u8] = b"\x1bOQ";
pub const F3: &'static [u8] = b"\x1bOR";
pub const F4: &'static [u8] = b"\x1bOS";
pub const F5: &'static [u8] = b"\x1b[15~";
pub const F6: &'static [u8] = b"\x1b[17~";
pub const F7: &'static [u8] = b"\x1b[18~";
pub const F8: &'static [u8] = b"\x1b[19~";
pub const F9: &'static [u8] = b"\x1b[20~";
pub const F10: &'static [u8] = b"\x1b[21~";
pub const F11: &'static [u8] = b"\x1b[23~";
pub const F12: &'static [u8] = b"\x1b[24~";
#[must_use]
pub fn cursor_move(rows: i32, cols: i32) -> Vec<u8> {
let mut result = Vec::new();
if rows != 0 {
let dir = if rows > 0 { 'B' } else { 'A' };
let count = rows.unsigned_abs();
result.extend(format!("\x1b[{count}{dir}").as_bytes());
}
if cols != 0 {
let dir = if cols > 0 { 'C' } else { 'D' };
let count = cols.unsigned_abs();
result.extend(format!("\x1b[{count}{dir}").as_bytes());
}
result
}
#[must_use]
pub fn cursor_position(row: u32, col: u32) -> Vec<u8> {
format!("\x1b[{row};{col}H").into_bytes()
}
}
pub trait AnsiSend: BasicSend {
fn send_cursor_up(&mut self) -> impl std::future::Future<Output = Result<()>> + Send
where
Self: Send,
{
async move { self.send_bytes(AnsiSequences::CURSOR_UP).await }
}
fn send_cursor_down(&mut self) -> impl std::future::Future<Output = Result<()>> + Send
where
Self: Send,
{
async move { self.send_bytes(AnsiSequences::CURSOR_DOWN).await }
}
fn send_cursor_right(&mut self) -> impl std::future::Future<Output = Result<()>> + Send
where
Self: Send,
{
async move { self.send_bytes(AnsiSequences::CURSOR_RIGHT).await }
}
fn send_cursor_left(&mut self) -> impl std::future::Future<Output = Result<()>> + Send
where
Self: Send,
{
async move { self.send_bytes(AnsiSequences::CURSOR_LEFT).await }
}
fn send_home(&mut self) -> impl std::future::Future<Output = Result<()>> + Send
where
Self: Send,
{
async move { self.send_bytes(AnsiSequences::HOME).await }
}
fn send_end(&mut self) -> impl std::future::Future<Output = Result<()>> + Send
where
Self: Send,
{
async move { self.send_bytes(AnsiSequences::END).await }
}
fn send_delete(&mut self) -> impl std::future::Future<Output = Result<()>> + Send
where
Self: Send,
{
async move { self.send_bytes(AnsiSequences::DELETE).await }
}
fn send_function_key(&mut self, n: u8) -> impl std::future::Future<Output = Result<()>> + Send
where
Self: Send,
{
async move {
let seq = match n {
1 => AnsiSequences::F1,
2 => AnsiSequences::F2,
3 => AnsiSequences::F3,
4 => AnsiSequences::F4,
5 => AnsiSequences::F5,
6 => AnsiSequences::F6,
7 => AnsiSequences::F7,
8 => AnsiSequences::F8,
9 => AnsiSequences::F9,
10 => AnsiSequences::F10,
11 => AnsiSequences::F11,
12 => AnsiSequences::F12,
_ => return Ok(()),
};
self.send_bytes(seq).await
}
}
}
impl<T: BasicSend> AnsiSend for T {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ansi_cursor_move() {
assert_eq!(AnsiSequences::cursor_move(3, 0), b"\x1b[3B");
assert_eq!(AnsiSequences::cursor_move(-2, 0), b"\x1b[2A");
assert_eq!(AnsiSequences::cursor_move(0, 5), b"\x1b[5C");
assert_eq!(AnsiSequences::cursor_move(0, -4), b"\x1b[4D");
}
#[test]
fn ansi_cursor_position() {
assert_eq!(AnsiSequences::cursor_position(1, 1), b"\x1b[1;1H");
assert_eq!(AnsiSequences::cursor_position(10, 20), b"\x1b[10;20H");
}
}