little-kitty 0.0.3

A low-level interface for the Kitty Graphics Protocol
Documentation
use {
    base64::{engine::general_purpose::*, write::*},
    std::io::*,
};

/// Kitty graphics protocol command start.
pub const COMMAND_START: &[u8] = b"\x1b_G";

/// tmux
/// [passthrough start](https://github.com/tmux/tmux/wiki/FAQ#what-is-the-passthrough-escape-sequence-and-how-do-i-use-it) +
/// escaped Kitty graphics protocol command start.
pub const COMMAND_START_TMUX: &[u8] = b"\x1bPtmux;\x1b\x1b_G";

/// tmux
/// [passthrough start](https://github.com/tmux/tmux/wiki/FAQ#what-is-the-passthrough-escape-sequence-and-how-do-i-use-it) +
/// escaped ANSI set cursor position start.
pub const POSITION_START_TMUX: &[u8] = b"\x1bPtmux;\x1b\x1b[";

/// ANSI set cursor position end + escaped Kitty graphics protocol command start.
pub const POSITION_END_COMMAND_START_TMUX: &[u8] = b"H\x1b\x1b_G";

/// Kitty graphics protocol command end.
pub const COMMAND_END: &[u8] = b"\x1b\\";

/// Escaped Kitty graphics protocol command end + tmux
/// [passthrough end](https://github.com/tmux/tmux/wiki/FAQ#what-is-the-passthrough-escape-sequence-and-how-do-i-use-it).
pub const COMMAND_END_TMUX: &[u8] = b"\x1b\x1b\\\x1b\\";

/// Kitty graphics protocol command end + VT100
/// [device attributes query](https://vt100.net/docs/vt510-rm/DA1.html).
pub const COMMAND_END_AND_DEVICE_QUERY: &[u8] = b"\x1b\\\x1b[c";

/// Escaped Kitty graphics protocol command end + escaped VT100
/// [device attributes query](https://vt100.net/docs/vt510-rm/DA1.html) + tmux
/// [passthrough end](https://github.com/tmux/tmux/wiki/FAQ#what-is-the-passthrough-escape-sequence-and-how-do-i-use-it).
pub const COMMAND_END_AND_DEVICE_QUERY_TMUX: &[u8] = b"\x1b\x1b\\\x1b\x1b[c\x1b\\";

//
// KittyCommandWriter
//

/// Kitty graphics protocol command writer utilities.
pub trait KittyCommandWriter: Sized {
    /// Write start of Kitty graphics protocol command.
    ///
    /// Can use [tmux](https://github.com/tmux/tmux) passthrough mode.
    ///
    /// If in_tmux is true and position is [Some] will also pass through an ANSI cursor move
    /// command.
    fn write_start(&mut self, in_tmux: bool, position: Option<(usize, usize)>) -> Result<()>;

    /// Write end of Kitty graphics protocol command.
    ///
    /// Can use [tmux](https://github.com/tmux/tmux) passthrough mode.
    fn write_end(&mut self, in_tmux: bool) -> Result<()>;

    /// Write end of Kitty graphics protocol command + VT100 device attributes query.
    ///
    /// Can use [tmux](https://github.com/tmux/tmux) passthrough mode.
    fn write_end_and_device_query(&mut self, in_tmux: bool) -> Result<()>;

    /// Write as Base64.
    fn write_base64(self, data: &[u8]) -> Result<Self>;
}

impl<WriteT> KittyCommandWriter for WriteT
where
    WriteT: Write,
{
    fn write_start(&mut self, in_tmux: bool, position: Option<(usize, usize)>) -> Result<()> {
        if in_tmux && let Some((x, y)) = position {
            self.write_all(POSITION_START_TMUX)?;
            self.write_all((y + 1).to_string().as_bytes())?;
            self.write_all(b";")?;
            self.write_all((x + 1).to_string().as_bytes())?;
            self.write_all(POSITION_END_COMMAND_START_TMUX)
        } else {
            self.write_all(if in_tmux { COMMAND_START_TMUX } else { COMMAND_START })
        }
    }

    fn write_end(&mut self, in_tmux: bool) -> Result<()> {
        self.write_all(if in_tmux { COMMAND_END_TMUX } else { COMMAND_END })?;
        self.flush()
    }

    fn write_end_and_device_query(&mut self, in_tmux: bool) -> Result<()> {
        self.write_all(if in_tmux { COMMAND_END_AND_DEVICE_QUERY_TMUX } else { COMMAND_END_AND_DEVICE_QUERY })?;
        self.flush()
    }

    fn write_base64(self, data: &[u8]) -> Result<Self> {
        // Note: the specification says it must be padded
        let mut encoder = EncoderWriter::new(self, &STANDARD);
        encoder.write_all(data)?;
        encoder.finish()
    }
}