cursive-image 0.0.6

Image view for the Cursive TUI library
Documentation
use super::{code::*, io::*, read::*};

use std::{
    io::{self, Write},
    path::*,
};

const MAX_PAYLOAD_CHUNK_SIZE: usize = 4096;
const MAX_PAYLOAD_CHUNK_SIZE_U64: u64 = MAX_PAYLOAD_CHUNK_SIZE as u64;

//
// Command
//

/// Kitty graphics protocol APC (Application Programming Command).
///
/// See [documentation](https://sw.kovidgoyal.net/kitty/graphics-protocol/).
#[derive(Clone, Debug, Default)]
pub struct Command {
    control: Vec<(char, Code)>,
}

impl Command {
    /// Add control code.
    pub fn add<CodeT>(&mut self, key: char, code: CodeT)
    where
        CodeT: Into<Code>,
    {
        self.control.push((key, code.into()));
    }

    /// Add control code.
    ///
    /// Chainable.
    pub fn with<CodeT>(mut self, key: char, code: CodeT) -> Self
    where
        CodeT: Into<Code>,
    {
        self.add(key, code);
        self
    }

    /// Execute.
    pub fn execute(&self) -> io::Result<()> {
        let mut writer = io::stdout();
        writer.write_start()?;
        self.write_control(&mut writer)?;
        writer.write_end()
    }

    /// Execute with payload.
    pub fn execute_with_payload(&self, payload: &[u8]) -> io::Result<()> {
        if payload.len() > MAX_PAYLOAD_CHUNK_SIZE {
            self.execute_with_payload_from(payload)
        } else {
            let mut writer = io::stdout();
            writer.write_start()?;
            self.write_control(&mut writer)?;
            write!(writer, ";")?;
            writer = writer.write_base64(payload)?;
            writer.write_end()
        }
    }

    /// Execute with payload from a reader.
    pub fn execute_with_payload_from<ReadT>(&self, mut payload: ReadT) -> io::Result<()>
    where
        ReadT: io::Read,
    {
        let mut writer = io::stdout();

        writer.write_start()?;
        if !self.control.is_empty() {
            self.write_control(&mut writer)?;
            write!(writer, ",")?;
        }
        write!(writer, "m=1;")?;

        let mut chunk = Vec::with_capacity(MAX_PAYLOAD_CHUNK_SIZE);

        loop {
            // Read next chunk
            chunk.clear();
            payload = payload.read_chunk(MAX_PAYLOAD_CHUNK_SIZE_U64, &mut chunk)?;

            if chunk.is_empty() {
                // End chunk
                writer.write_end()?;

                // End payload
                writer.write_start()?;
                write!(writer, "m=0;")?;
                return writer.write_end();
            }

            // End chunk
            writer = writer.write_base64(&chunk)?;
            writer.write_end()?;

            // Start new chunk
            writer.write_start()?;
            write!(writer, "m=1;")?;
        }
    }

    /// Execute with path payload.
    pub fn execute_with_path_payload<PathT>(&self, path: PathT) -> io::Result<()>
    where
        PathT: AsRef<Path>,
    {
        // Notes:
        // * The protocol specification doesn't mention string encoding
        // * It also doesn't mention that the path must be absolute, but implementations seem to expect it
        self.execute_with_payload(absolute(path)?.as_os_str().as_encoded_bytes())
    }

    fn write_control<WriteT>(&self, mut writer: WriteT) -> io::Result<()>
    where
        WriteT: io::Write,
    {
        let mut iterator = self.control.iter().peekable();
        while let Some((key, code)) = iterator.next() {
            write!(writer, "{}=", *key)?;
            code.write(&mut writer)?;
            if iterator.peek().is_some() {
                write!(writer, ",")?;
            }
        }
        Ok(())
    }
}