quinjet 0.0.72

A fast, live, keyboard-first Git source-control interface for the terminal
use std::path::Path;

use super::{ImageProtocol, SniffedImage};
use crate::file_icons;

pub(crate) fn sniff_image(bytes: &[u8]) -> Option<SniffedImage> {
    if looks_like_svg(bytes) {
        return Some(SniffedImage::Svg);
    }
    if is_png(bytes)
        || is_jpeg(bytes)
        || is_gif(bytes)
        || is_webp(bytes)
        || is_bmp(bytes)
        || is_ico(bytes)
        || is_tiff(bytes)
        || is_qoi(bytes)
        || is_pnm(bytes)
    {
        return Some(SniffedImage::Raster);
    }
    None
}

pub(crate) fn looks_like_image(path: &Path, bytes: Option<&[u8]>) -> bool {
    file_icons::is_image_path(path) || bytes.is_some_and(|bytes| sniff_image(bytes).is_some())
}

pub(crate) fn is_svg_path(path: &Path) -> bool {
    path.extension()
        .and_then(|extension| extension.to_str())
        .is_some_and(|extension| extension.eq_ignore_ascii_case("svg"))
}

pub(super) fn looks_like_svg(bytes: &[u8]) -> bool {
    let prefix = bytes.get(..bytes.len().min(2048)).unwrap_or_default();
    let Ok(text) = std::str::from_utf8(prefix) else {
        return false;
    };
    let trimmed = text.trim_start_matches('\u{feff}').trim_start();
    trimmed.starts_with("<svg")
        || (trimmed.starts_with("<?xml") && trimmed.to_ascii_lowercase().contains("<svg"))
}

pub(crate) fn detect_protocol_from_vars<I, K, V>(vars: I) -> ImageProtocol
where
    I: IntoIterator<Item = (K, V)>,
    K: AsRef<str>,
    V: AsRef<str>,
{
    let mut term = String::new();
    let mut term_program = String::new();
    let mut kitty = false;
    let mut iterm_session = false;
    let mut wezterm = false;
    let mut iterm_terminal = false;
    let mut override_protocol = None;
    for (key, value) in vars {
        let key = key.as_ref();
        let value = value.as_ref();
        if value.is_empty() {
            continue;
        }
        match key {
            "TERM" => term = value.to_ascii_lowercase(),
            "TERM_PROGRAM" => term_program = value.to_ascii_lowercase(),
            "KITTY_WINDOW_ID" => kitty = true,
            "ITERM_SESSION_ID" => iterm_session = true,
            "WEZTERM_EXECUTABLE" => wezterm = true,
            "LC_TERMINAL" => iterm_terminal = value.contains("iTerm"),
            "QUINJET_IMAGE_PROTOCOL" => {
                override_protocol = match value.to_ascii_lowercase().as_str() {
                    "kitty" => Some(ImageProtocol::Kitty),
                    "iterm2" => Some(ImageProtocol::Iterm2),
                    "sixel" => Some(ImageProtocol::Sixel),
                    "halfblocks" => Some(ImageProtocol::Halfblocks),
                    _ => None,
                };
            }
            _ => {}
        }
    }
    if let Some(protocol) = override_protocol {
        return protocol;
    }
    if kitty
        || term.contains("kitty")
        || term.contains("ghostty")
        || term_program.contains("ghostty")
    {
        return ImageProtocol::Kitty;
    }
    if iterm_session
        || wezterm
        || iterm_terminal
        || term_program.contains("iterm")
        || term_program.contains("wezterm")
        || term_program.contains("mintty")
        || term_program.contains("vscode")
        || term_program.contains("tabby")
        || term_program.contains("hyper")
        || term_program.contains("warp")
        || term_program.contains("rio")
        || term_program.contains("bobcat")
    {
        return ImageProtocol::Iterm2;
    }
    if term.contains("sixel") || term.contains("mlterm") || term.contains("foot") {
        return ImageProtocol::Sixel;
    }
    ImageProtocol::Halfblocks
}

fn is_png(bytes: &[u8]) -> bool {
    matches_prefix(bytes, &[0x89, b'P', b'N', b'G', 0x0D, 0x0A, 0x1A, 0x0A])
}

fn is_jpeg(bytes: &[u8]) -> bool {
    matches_prefix(bytes, &[0xFF, 0xD8, 0xFF])
}

fn is_gif(bytes: &[u8]) -> bool {
    matches_prefix(bytes, b"GIF87a") || matches_prefix(bytes, b"GIF89a")
}

fn is_webp(bytes: &[u8]) -> bool {
    matches_prefix(bytes, b"RIFF") && bytes.get(8..12) == Some(b"WEBP")
}

fn is_bmp(bytes: &[u8]) -> bool {
    matches_prefix(bytes, b"BM")
}

fn is_ico(bytes: &[u8]) -> bool {
    matches_prefix(bytes, &[0x00, 0x00, 0x01, 0x00])
}

fn is_tiff(bytes: &[u8]) -> bool {
    matches_prefix(bytes, b"II*\0") || matches_prefix(bytes, b"MM\0*")
}

fn is_qoi(bytes: &[u8]) -> bool {
    matches_prefix(bytes, b"qoif")
}

fn is_pnm(bytes: &[u8]) -> bool {
    matches!(
        bytes.get(..2),
        Some(b"P1" | b"P2" | b"P3" | b"P4" | b"P5" | b"P6")
    )
}

fn matches_prefix(bytes: &[u8], prefix: &[u8]) -> bool {
    bytes.get(..prefix.len()) == Some(prefix)
}