magi-code 0.96.1

Repository-aware CLI coding agent for terminal work
Documentation
//! Kitty APC commands are written only by the terminal owner, never widgets.
use super::super::images::{ImagePlacement, PreparedImage};
use std::{
    cell::RefCell,
    io::{self, IsTerminal, Write},
    sync::Arc,
};

const FIRST_IMAGE_ID: usize = 1_900_000_000;
const IMAGE_SLOTS: usize = 16;
static GRAPHICS_ACTIVE: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
thread_local! {
    static GRAPHICS: RefCell<GraphicsState> = const { RefCell::new(GraphicsState::new()) };
}

struct RetainedPlacement {
    slot: usize,
    id: usize,
    placement: ImagePlacement,
}

struct GraphicsState {
    images: Vec<Arc<PreparedImage>>,
    placements: Vec<RetainedPlacement>,
}

impl GraphicsState {
    const fn new() -> Self {
        Self {
            images: Vec::new(),
            placements: Vec::new(),
        }
    }

    fn image_slot(
        &mut self,
        out: &mut impl Write,
        placement: &ImagePlacement,
        visible: &[ImagePlacement],
    ) -> io::Result<Option<usize>> {
        if let Some(index) = self
            .images
            .iter()
            .position(|image| Arc::ptr_eq(image, &placement.image))
        {
            return Ok(Some(index));
        }
        let index = if self.images.len() < IMAGE_SLOTS {
            self.images.len()
        } else {
            let Some(index) = self
                .images
                .iter()
                .position(|image| !visible.iter().any(|p| Arc::ptr_eq(image, &p.image)))
            else {
                return Ok(None);
            };
            write!(out, "\x1b_Ga=d,d=I,i={},q=2;\x1b\\", FIRST_IMAGE_ID + index)?;
            index
        };
        upload(out, FIRST_IMAGE_ID + index, &placement.image)?;
        if index == self.images.len() {
            self.images.push(Arc::clone(&placement.image));
        } else {
            self.images[index] = Arc::clone(&placement.image);
        }
        Ok(Some(index))
    }

    fn draw(&mut self, out: &mut impl Write, rows: &[ImagePlacement]) -> io::Result<()> {
        let grouped = group_contiguous_rows(rows);
        let mut next = Vec::<RetainedPlacement>::new();
        for placement in &grouped {
            let Some(slot) = self.image_slot(out, placement, &grouped)? else {
                continue;
            };
            // Placement IDs are local to an image, so other images appearing or
            // disappearing cannot renumber its retained placements.
            let id = next.iter().filter(|p| p.slot == slot).count() + 1;
            next.push(RetainedPlacement {
                slot,
                id,
                placement: placement.clone(),
            });
        }
        let mut changed = false;
        for old in &self.placements {
            if !next.iter().any(|p| p.slot == old.slot && p.id == old.id) {
                write!(
                    out,
                    "\x1b_Ga=d,d=i,i={},p={},q=2;\x1b\\",
                    FIRST_IMAGE_ID + old.slot,
                    old.id
                )?;
                changed = true;
            }
        }
        let mut saved_cursor = false;
        for placement in &next {
            if self.placements.iter().any(|old| {
                old.slot == placement.slot
                    && old.id == placement.id
                    && same_placement(&old.placement, &placement.placement)
            }) {
                continue;
            }
            if !saved_cursor {
                crossterm::queue!(out, crossterm::cursor::SavePosition)?;
                saved_cursor = true;
            }
            place(
                out,
                FIRST_IMAGE_ID + placement.slot,
                placement.id,
                &placement.placement,
            )?;
            changed = true;
        }
        if saved_cursor {
            crossterm::queue!(out, crossterm::cursor::RestorePosition)?;
        }
        if changed {
            out.flush()?;
        }
        self.placements = next;
        Ok(())
    }
}

fn same_placement(left: &ImagePlacement, right: &ImagePlacement) -> bool {
    Arc::ptr_eq(&left.image, &right.image)
        && left.area == right.area
        && left.source_y == right.source_y
        && left.source_height == right.source_height
}

fn group_contiguous_rows(rows: &[ImagePlacement]) -> Vec<ImagePlacement> {
    let mut grouped = Vec::<ImagePlacement>::new();
    for row in rows {
        if let Some(previous) = grouped.last_mut()
            && Arc::ptr_eq(&previous.image, &row.image)
            && previous.area.x == row.area.x
            && previous.area.width == row.area.width
            && previous.area.y.checked_add(previous.area.height) == Some(row.area.y)
            && previous.source_y.checked_add(previous.source_height) == Some(row.source_y)
            && u64::from(previous.source_height) * u64::from(row.area.height)
                == u64::from(row.source_height) * u64::from(previous.area.height)
            && let Some(height) = previous.area.height.checked_add(row.area.height)
            && let Some(source_height) = previous.source_height.checked_add(row.source_height)
        {
            previous.area.height = height;
            previous.source_height = source_height;
        } else {
            grouped.push(row.clone());
        }
    }
    grouped
}

pub(in crate::tui) fn detect_cell_pixels() -> Option<(u16, u16)> {
    let term = std::env::var("TERM").unwrap_or_default();
    let program = std::env::var("TERM_PROGRAM").unwrap_or_default();
    if !io::stdout().is_terminal()
        || !io::stdin().is_terminal()
        || !supported_terminal(
            &term,
            &program,
            std::env::var_os("TMUX").is_some(),
            std::env::var_os("STY").is_some(),
        )
    {
        return None;
    }
    current_cell_pixels()
}

pub(in crate::tui) fn current_cell_pixels() -> Option<(u16, u16)> {
    // ioctl, not a stdin query. Missing pixel dimensions fail closed.
    let size = crossterm::terminal::window_size().ok()?;
    let width = size.width.checked_div(size.columns)?;
    let height = size.height.checked_div(size.rows)?;
    (width > 0 && height > 0).then_some((width, height))
}

fn supported_terminal(term: &str, program: &str, tmux: bool, screen: bool) -> bool {
    !tmux && !screen && (term == "xterm-kitty" || program == "ghostty")
}

pub(in crate::tui) fn draw_images(placements: &[ImagePlacement]) -> io::Result<()> {
    if !placements.is_empty() {
        GRAPHICS_ACTIVE.store(true, std::sync::atomic::Ordering::Relaxed);
    }
    GRAPHICS.with_borrow_mut(|graphics| graphics.draw(&mut io::stdout().lock(), placements))
}

fn upload(out: &mut impl Write, id: usize, image: &PreparedImage) -> io::Result<()> {
    let mut chunks = image.encoded.as_bytes().chunks(4096).peekable();
    let mut first = true;
    while let Some(chunk) = chunks.next() {
        if first {
            write!(
                out,
                "\x1b_Ga=t,t=d,f=32,i={id},s={},v={},q=2,m={};",
                image.width,
                image.height,
                usize::from(chunks.peek().is_some())
            )?;
            first = false;
        } else {
            write!(out, "\x1b_Gq=2,m={};", usize::from(chunks.peek().is_some()))?;
        }
        out.write_all(chunk)?;
        out.write_all(b"\x1b\\")?;
    }
    Ok(())
}

fn place(
    out: &mut impl Write,
    id: usize,
    placement_id: usize,
    placement: &ImagePlacement,
) -> io::Result<()> {
    crossterm::queue!(
        out,
        crossterm::cursor::MoveTo(placement.area.x, placement.area.y)
    )?;
    write!(
        out,
        "\x1b_Ga=p,i={id},p={placement_id},q=2,C=1,c={},r={},x=0,y={},w={},h={};\x1b\\",
        placement.area.width,
        placement.area.height,
        placement.source_y,
        placement.image.width,
        placement.source_height
    )
}

fn delete_image_data(out: &mut impl Write) {
    for index in 0..IMAGE_SLOTS {
        let _ = write!(out, "\x1b_Ga=d,d=I,i={},q=2;\x1b\\", FIRST_IMAGE_ID + index);
    }
}

pub(in crate::tui) fn cleanup() {
    // Do not borrow thread-local caches here: a panic may occur during a borrow.
    // Delete only our fixed namespace, including partially uploaded images.
    if !GRAPHICS_ACTIVE.swap(false, std::sync::atomic::Ordering::Relaxed) {
        return;
    }
    let mut out = io::stdout().lock();
    delete_image_data(&mut out);
    let _ = out.flush();
    let _ = GRAPHICS.try_with(|graphics| {
        if let Ok(mut graphics) = graphics.try_borrow_mut() {
            *graphics = GraphicsState::new();
        }
    });
}