quinjet 0.0.69

A fast, live, keyboard-first Git source-control interface for the terminal
use std::sync::{Arc, OnceLock};

use serde::Serialize;

mod attach;
mod decode;
mod detect;
mod source;
#[cfg(test)]
mod tests;

pub(crate) use attach::{attach_image_previews, file_footer, header_identity};
pub(crate) use detect::detect_protocol_from_vars;
#[cfg(test)]
pub(crate) use source::MapBlobSource;
pub(crate) use source::{BlobOrigin, DiffBlobSource, LoadedBlob, RevisionBlobSource};

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "kebab-case")]
pub(crate) enum ImageSide {
    Previous,
    New,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "kebab-case")]
pub(crate) enum ImagePreviewKind {
    Raster,
    Svg,
    Skipped,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "kebab-case")]
pub(crate) enum ImageProtocol {
    Halfblocks,
    Sixel,
    Kitty,
    Iterm2,
}

impl ImageProtocol {
    pub(crate) fn detect() -> Self {
        static PROTOCOL: OnceLock<ImageProtocol> = OnceLock::new();
        *PROTOCOL.get_or_init(|| detect_protocol_from_vars(std::env::vars()))
    }

    pub(crate) const fn is_native(self) -> bool {
        !matches!(self, Self::Halfblocks)
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct ImageCell {
    pub upper: [u8; 3],
    pub lower: [u8; 3],
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct ImageRaster {
    pub width: u32,
    pub height: u32,
    pub rgba: Vec<u8>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct ImagePreview {
    pub side: ImageSide,
    pub path: String,
    pub width: Option<u32>,
    pub height: Option<u32>,
    pub kind: ImagePreviewKind,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub reason: Option<String>,
    pub row: u16,
    pub rows: u16,
    #[serde(skip)]
    pub cells: Vec<ImageCell>,
    #[serde(skip)]
    pub raster: Option<Arc<ImageRaster>>,
}

impl ImagePreview {
    pub(crate) fn caption(&self) -> String {
        let side = match self.side {
            ImageSide::Previous => "previous",
            ImageSide::New => "new",
        };
        match (self.width, self.height) {
            (Some(width), Some(height)) => format!("image: {width}x{height} {side}"),
            _ => match self.kind {
                ImagePreviewKind::Svg => format!("image: svg {side}"),
                ImagePreviewKind::Skipped => self.reason.as_ref().map_or_else(
                    || format!("image: {side}"),
                    |reason| format!("image: {side} ({reason})"),
                ),
                ImagePreviewKind::Raster => format!("image: {side}"),
            },
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum SniffedImage {
    Raster,
    Svg,
}