mod detect_support;
mod image_renderer;
mod terminal_esc;
pub use image_renderer::*;
use crate::display::cell_size_in_pixels;
use {
crate::{
app::AppContext,
display::W,
errors::ProgramError,
image::SourceImage,
kitty::detect_support::is_tmux,
},
crokey::crossterm::style::Color,
once_cell::sync::Lazy,
std::{
io::Write,
path::Path,
sync::Mutex,
},
termimad::Area,
};
pub type KittyImageId = usize;
static MANAGER: Lazy<Mutex<KittyManager>> = Lazy::new(|| {
let manager = KittyManager {
rendered_images: Vec::new(),
renderer: MaybeRenderer::Untested,
};
Mutex::new(manager)
});
pub fn manager() -> &'static Mutex<KittyManager> {
&MANAGER
}
#[derive(Debug)]
pub struct KittyManager {
rendered_images: Vec<RenderedImage>,
renderer: MaybeRenderer,
}
#[derive(Debug)]
struct RenderedImage {
image_id: KittyImageId,
drawing_count: usize,
}
#[derive(Debug)]
enum MaybeRenderer {
Untested,
Disabled,
Enabled { renderer: KittyImageRenderer },
}
impl KittyManager {
pub fn renderer_if_tested(&mut self) -> Option<&mut KittyImageRenderer> {
match &mut self.renderer {
MaybeRenderer::Enabled { renderer } => Some(renderer),
_ => None,
}
}
pub fn delete_temp_files(&mut self) {
if let MaybeRenderer::Enabled { renderer } = &mut self.renderer {
renderer.delete_temp_files();
}
}
pub fn renderer(
&mut self,
con: &AppContext,
) -> Option<&mut KittyImageRenderer> {
if matches!(self.renderer, MaybeRenderer::Disabled) {
return None;
}
if matches!(self.renderer, MaybeRenderer::Enabled { .. }) {
return self.renderer_if_tested();
}
let options = KittyImageRendererOptions {
display: con.kitty_graphics_display,
transmission_medium: con.kitty_graphics_transmission,
kept_temp_files: con.kept_kitty_temp_files,
is_tmux: is_tmux(),
};
match KittyImageRenderer::new(options) {
Some(renderer) => {
self.renderer = MaybeRenderer::Enabled { renderer };
self.renderer_if_tested()
}
None => {
self.renderer = MaybeRenderer::Disabled;
None
}
}
}
pub fn keep(
&mut self,
kept_id: KittyImageId,
drawing_count: usize,
) {
for image in &mut self.rendered_images {
if image.image_id == kept_id {
image.drawing_count = drawing_count;
}
}
}
#[allow(clippy::too_many_arguments)] pub fn try_print_image(
&mut self,
w: &mut W,
src: &SourceImage,
src_path: &Path, area: &Area,
bg: Color,
drawing_count: usize,
con: &AppContext,
) -> Result<Option<KittyImageId>, ProgramError> {
if let Some(renderer) = self.renderer(con) {
let (cell_width, cell_height) = cell_size_in_pixels()?;
let area_width = area.width as u32 * cell_width;
let area_height = area.height as u32 * cell_height;
let img = src.fitting(area_width, area_height, None)?;
let new_id = renderer.print(w, &img, src_path, area, bg)?;
self.rendered_images.push(RenderedImage {
image_id: new_id,
drawing_count,
});
Ok(Some(new_id))
} else {
Ok(None)
}
}
pub fn erase_images_before(
&mut self,
w: &mut W,
drawing_count: usize,
) -> Result<(), ProgramError> {
let mut kept_images = Vec::new();
let is_tmux = detect_support::is_tmux();
let tmux_nest_count = if is_tmux {
detect_support::get_tmux_nest_count()
} else {
0
};
let tmux_header = is_tmux.then_some(terminal_esc::get_tmux_header(tmux_nest_count));
let tmux_tail = is_tmux.then_some(terminal_esc::get_tmux_tail(tmux_nest_count));
let esc = terminal_esc::get_esc_seq(tmux_nest_count);
for image in self.rendered_images.drain(..) {
if image.drawing_count >= drawing_count {
kept_images.push(image);
} else {
let id = image.image_id;
debug!("erase kitty image {id}");
if let Some(s) = &tmux_header {
write!(w, "{s}")?;
}
write!(w, "{}_Ga=d,d=I,i={}{}\\", &esc, id, &esc)?;
if let Some(s) = &tmux_tail {
write!(w, "{s}")?;
}
}
}
self.rendered_images = kept_images;
Ok(())
}
}